From b1b172114aac7e1a169b111bb44895ad144848ff Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Sat, 19 Jun 2010 21:12:12 +0000 Subject: [PATCH 001/902] Created 1.2.X release branch. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13361 bcc190cf-cafb-0310-a4f2-bffc1f526a37 From 8f5236fa359d91acbd67993b76b83ce0cff158f2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 21 Jun 2010 12:01:37 +0000 Subject: [PATCH 002/902] [1.2.X] Fixed #13747 -- Reverted documentation suggesting the use of self.stdout/err in management commands in 1.2. Thanks to metamemetics for the report. Strictly, self.stdout/err is a feature addition; however, it's a feature that is required in order to achieve a massive speedup in the tests and to maintain parity between 1.2.X and trunk tests. However, the feature is completely transparent -- the old technique will work fine, it just isn't as testable. Therefore, we'll treat this as an undocumented feature in the 1.2 branch. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13364 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/custom-management-commands.txt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 3f5feaa67ae3..3e7af8a8de38 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -62,14 +62,7 @@ look like this: poll.opened = False poll.save() - self.stdout.write('Successfully closed poll "%s"\n' % poll_id) - -.. note:: - When you are using management commands and wish to provide console - output, you should write to ``self.stdout`` and ``self.stderr``, - instead of printing to ``stdout`` and ``stderr`` directly. By - using these proxies, it becomes much easier to test your custom - command. + print 'Successfully closed poll "%s"' % poll_id The new custom command can be called using ``python manage.py closepoll ``. From 71cecc49525263494a19e37788124e3275323745 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 23 Jun 2010 13:20:38 +0000 Subject: [PATCH 003/902] [1.2.X] Fixed #12803 - Added styling for 'error' and 'warning' messages in admin. Thanks to bboli and sebastian_noack for the report, DrMeers for the patch. Backport of [13393] from trunk. Considered a bug (and therefore backported) due to error and warning messages being styled with green tick previously. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13394 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/media/css/base.css | 8 ++++++++ django/contrib/admin/templates/admin/base.html | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/django/contrib/admin/media/css/base.css b/django/contrib/admin/media/css/base.css index da502f357a46..645f1725b5f5 100644 --- a/django/contrib/admin/media/css/base.css +++ b/django/contrib/admin/media/css/base.css @@ -445,6 +445,14 @@ ul.messagelist li { background: #ffc url(../img/admin/icon_success.gif) 5px .3em no-repeat; } +ul.messagelist li.warning{ + background-image: url(../img/admin/icon_alert.gif); +} + +ul.messagelist li.error{ + background-image: url(../img/admin/icon_error.gif); +} + .errornote { font-size: 12px !important; display: block; diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index 3fd9eb3770b8..4221e2d1a701 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -56,7 +56,9 @@ {% endif %} {% if messages %} -
    {% for message in messages %}
  • {{ message }}
  • {% endfor %}
+
    {% for message in messages %} + {{ message }} + {% endfor %}
{% endif %} From 1b5f812aad107e2ce12fe7c81773113fe39a46c1 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 23 Jun 2010 17:19:56 +0000 Subject: [PATCH 004/902] [1.2.X] Fixed #13779 -- Can now discover GDAL 1.7 from debian-based packages. Thanks to Leo for bug report and patch. Backport of r13396 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13397 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/gdal/libgdal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index 6589c5647c27..a7a5658c499a 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -14,10 +14,10 @@ lib_names = None elif os.name == 'nt': # Windows NT shared library - lib_names = ['gdal16', 'gdal15'] + lib_names = ['gdal17', 'gdal16', 'gdal15'] elif os.name == 'posix': # *NIX library names. - lib_names = ['gdal', 'GDAL', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0'] + lib_names = ['gdal', 'GDAL', 'gdal1.7.0', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0'] else: raise OGRException('Unsupported OS "%s"' % os.name) From 90380b8fcf54842e29f772932bab2a9f6fbb26fd Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 23 Jun 2010 17:38:47 +0000 Subject: [PATCH 005/902] [1.2.X] Fixed #13671, #13748 -- Fixed errata and minor updates to GeoDjango docs. Backport of r13398 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13399 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../contrib/gis/create_template_postgis-1.4.sh | 2 +- docs/ref/contrib/gis/install.txt | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/ref/contrib/gis/create_template_postgis-1.4.sh b/docs/ref/contrib/gis/create_template_postgis-1.4.sh index 74a6ef98d205..57a1373f9604 100755 --- a/docs/ref/contrib/gis/create_template_postgis-1.4.sh +++ b/docs/ref/contrib/gis/create_template_postgis-1.4.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -POSTGIS_SQL_PATH=`pg_config --sharedir`/contrib/postgis-1.4 +POSTGIS_SQL_PATH=`pg_config --sharedir`/contrib createdb -E UTF8 template_postgis # Create the template spatial database. createlang -d template_postgis plpgsql # Adding PLPGSQL language support. psql -d postgres -c "UPDATE pg_database SET datistemplate='true' WHERE datname='template_postgis';" diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 00d4e62e9fa7..0b7954336cb1 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -383,9 +383,9 @@ Typically, SQLite packages are not compiled to include the `R*Tree module`__ -- thus it must be compiled from source. First download the latest amalgamation source archive from the `SQLite download page`__, and extract:: - $ wget http://www.sqlite.org/sqlite-amalgamation-3.6.22.tar.gz - $ tar xzf sqlite-amalgamation-3.6.22.tar.gz - $ cd sqlite-3.6.22 + $ wget http://sqlite.org/sqlite-amalgamation-3.6.23.1.tar.gz + $ tar xzf sqlite-amalgamation-3.6.23.1.tar.gz + $ cd sqlite-3.6.23.1 Next, run the ``configure`` script -- however the ``CFLAGS`` environment variable needs to be customized so that SQLite knows to build the R*Tree module:: @@ -449,12 +449,9 @@ Finally, do the same for the SpatiaLite tools:: .. note:: For Mac OS X users building from source, the SpatiaLite library *and* tools - need to be linked into the existing ``iconv`` library. While this happens - automatically on Linux, the ``configure`` scripts need to know about the - specific location on Mac OS X (via modification of the ``CFLAGS`` and - ``LDFLAGS`` environment variables prior to configuration):: + need to have their ``target`` configured:: - $ CFLAGS=-I/usr/include LDFLAGS="-L/usr/lib -liconv" ./configure + $ ./configure --target=macosx __ http://www.gaia-gis.it/spatialite/sources.html @@ -804,8 +801,8 @@ your ``.profile`` to be able to run the package programs from the command-line:: export PATH=/Library/Frameworks/GDAL.framework/Programs:$PATH export PATH=/usr/local/pgsql/bin:$PATH -__ http://www.kyngchaos.com/wiki/software:frameworks -__ http://www.kyngchaos.com/wiki/software:postgres +__ http://www.kyngchaos.com/software/frameworks +__ http://www.kyngchaos.com/software/postgres .. note:: From 867d6907525e0d36eca609fb50faa62a522abac2 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 24 Jun 2010 11:21:10 +0000 Subject: [PATCH 006/902] [1.2.X] Fixed error in AnonymousUser docs since [12316] Backport of [13401] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13402 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 4e07f73190b6..eae07ed71715 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -435,8 +435,6 @@ Anonymous users instead of ``False``. * :meth:`~django.contrib.auth.models.User.is_authenticated()` returns ``False`` instead of ``True``. - * :meth:`~django.contrib.auth.models.User.has_perm()` always returns - ``False``. * :meth:`~django.contrib.auth.models.User.set_password()`, :meth:`~django.contrib.auth.models.User.check_password()`, :meth:`~django.contrib.auth.models.User.save()`, From 558732c37bf5faaffdd00ab278f77dc6c658ecde Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 5 Jul 2010 16:43:51 +0000 Subject: [PATCH 007/902] [1.2.X] Small corrections/improvements to DB optimization docs. Bakport of [13419] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13420 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/optimization.txt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 6063bc6c2ac7..5d74fc9ce98d 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -5,7 +5,7 @@ Database access optimization ============================ Django's database layer provides various ways to help developers get the most -out of their databases. This documents gathers together links to the relevant +out of their databases. This document gathers together links to the relevant documentation, and adds various tips, organized under an number of headings that outline the steps to take when attempting to optimize your database usage. @@ -108,9 +108,8 @@ Do database work in the database rather than in Python For instance: -* At the most basic level, use :ref:`filter and exclude ` to - filtering in the database to avoid loading data into your Python process, only - to throw much of it away. +* At the most basic level, use :ref:`filter and exclude ` to do + filtering in the database. * Use :ref:`F() object query expressions ` to do filtering against other fields within the same model. @@ -245,9 +244,6 @@ methods of individual instances, which means that any custom behaviour you have added for these methods will not be executed, including anything driven from the normal database object :ref:`signals `. -Don't retrieve things you already have -====================================== - Use foreign key values directly ------------------------------- From b9f085a9de946a442e594e997b1eafaf4dc6a211 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 5 Jul 2010 16:54:37 +0000 Subject: [PATCH 008/902] [1.2.X] Added missing module directive for validators documentation. Backport of [13421] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13422 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/validators.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index bbba84c7f9ff..916c388f0889 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -5,6 +5,8 @@ Validators ========== .. versionadded:: 1.2 +.. module:: django.core.validators + :synopsis: Validation utilities and base classes Writing validators ================== From a84a390c7afc8a09c081b8eff6f11a2452896420 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 5 Jul 2010 17:10:58 +0000 Subject: [PATCH 009/902] [1.2.X] Fixed #13880 - added 2.7 to list of supported versions of Python Thanks Alex Backport of [13423] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13424 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/install.txt | 2 +- docs/topics/install.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro/install.txt b/docs/intro/install.txt index 901bde01c2e1..dcb9c8e0c486 100644 --- a/docs/intro/install.txt +++ b/docs/intro/install.txt @@ -12,7 +12,7 @@ Install Python -------------- Being a Python Web framework, Django requires Python. It works with any Python -version from 2.4 to 2.6 (due to backwards +version from 2.4 to 2.7 (due to backwards incompatibilities in Python 3.0, Django does not currently work with Python 3.0; see :ref:`the Django FAQ ` for more information on supported Python versions and the 3.0 transition), but we recommend installing Python 2.5 or later. If you do so, you won't need to set up a database just yet: Python 2.5 or later includes a lightweight database called SQLite_. diff --git a/docs/topics/install.txt b/docs/topics/install.txt index d53f49de46db..2147a989310c 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -11,7 +11,7 @@ Install Python Being a Python Web framework, Django requires Python. -It works with any Python version from 2.4 to 2.6 (due to backwards +It works with any Python version from 2.4 to 2.7 (due to backwards incompatibilities in Python 3.0, Django does not currently work with Python 3.0; see :ref:`the Django FAQ ` for more information on supported Python versions and the 3.0 transition). From 1fed50033773e76eaa4bcf3c064a9e08899d405d Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 5 Jul 2010 17:22:29 +0000 Subject: [PATCH 010/902] [1.2.X] Doc updates missed in [13424] Refs #13880 Backport of [13425] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13426 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/faq/install.txt | 4 ++-- docs/topics/serialization.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/faq/install.txt b/docs/faq/install.txt index f20b2bc1872e..0139a82a6723 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -46,7 +46,7 @@ Do I lose anything by using Python 2.4 versus newer Python versions, such as Pyt ----------------------------------------------------------------------------------------------- Not in the core framework. Currently, Django itself officially supports any -version of Python from 2.4 through 2.6, inclusive. However, newer versions of +version of Python from 2.4 through 2.7, inclusive. However, newer versions of Python are often faster, have more features, and are better supported. Third-party applications for use with Django are, of course, free to set their own version requirements. @@ -56,7 +56,7 @@ versions as part of a migration which will end with Django running on Python 3 (see below for details). All else being equal, we recommend that you use the latest 2.x release -(currently Python 2.6). This will let you take advantage of the numerous +(currently Python 2.7). This will let you take advantage of the numerous improvements and optimizations to the Python language since version 2.4, and will help ease the process of dropping support for older Python versions on the road to Python 3. diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index 1cf8e8646232..c5155107f026 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -169,7 +169,7 @@ For example:: json_serializer.serialize(queryset, ensure_ascii=False, stream=response) The Django source code includes the simplejson_ module. However, if you're -using Python 2.6 (which includes a builtin version of the module), Django will +using Python 2.6 or later (which includes a builtin version of the module), Django will use the builtin ``json`` module automatically. If you have a system installed version that includes the C-based speedup extension, or your system version is more recent than the version shipped with Django (currently, 2.0.7), the From ab693cea8096149302b3f4ecf8eede6a358a9621 Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Sat, 17 Jul 2010 09:31:09 +0000 Subject: [PATCH 011/902] [1.2.X] Set svnmerge-integrated for 1.2.X branch. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13435 bcc190cf-cafb-0310-a4f2-bffc1f526a37 From 48dd139e5494dd6763131fddde0286d3fd4c8d5a Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Sat, 17 Jul 2010 09:33:27 +0000 Subject: [PATCH 012/902] [1.2.X] Fixed import example code for NON_FIELD_ERRORS. r13434 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13436 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/instances.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index dd14dd1ce726..1e72e0c662e3 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -107,7 +107,7 @@ special key that is used for errors that are tied to the entire model instead of to a specific field. You can access these errors with ``NON_FIELD_ERRORS``:: - from django.core.validators import ValidationError, NON_FIELD_ERRORS + from django.core.exceptions import ValidationError, NON_FIELD_ERRORS try: article.full_clean() except ValidationError, e: From 5dc560636b6e2ee1ff01f4a9c45f485d9d736b29 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Tue, 20 Jul 2010 19:14:23 +0000 Subject: [PATCH 013/902] [1.2.X] Fixed #13934 -- `GeoSQLCompiler.get_default_columns` was missing `local_only` keyword argument. Thanks, Simon Law, for bug report and initial patch. Backport of r13439 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13440 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/db/models/sql/compiler.py | 4 +++- django/contrib/gis/tests/relatedapp/models.py | 5 +++++ django/contrib/gis/tests/relatedapp/tests.py | 10 +++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index 78eeeafe190a..55dc4a66ec4c 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -95,7 +95,7 @@ def get_columns(self, with_aliases=False): return result def get_default_columns(self, with_aliases=False, col_aliases=None, - start_alias=None, opts=None, as_pairs=False): + start_alias=None, opts=None, as_pairs=False, local_only=False): """ Computes the default columns for selecting every field in the base model. Will sometimes be called to pull in related models (e.g. via @@ -121,6 +121,8 @@ def get_default_columns(self, with_aliases=False, col_aliases=None, if start_alias: seen = {None: start_alias} for field, model in opts.get_fields_with_model(): + if local_only and model is not None: + continue if start_alias: try: alias = seen[model] diff --git a/django/contrib/gis/tests/relatedapp/models.py b/django/contrib/gis/tests/relatedapp/models.py index 726f9826c0a2..2e9a62b61f26 100644 --- a/django/contrib/gis/tests/relatedapp/models.py +++ b/django/contrib/gis/tests/relatedapp/models.py @@ -38,6 +38,11 @@ class Author(models.Model): name = models.CharField(max_length=100) objects = models.GeoManager() +class Article(models.Model): + title = models.CharField(max_length=100) + author = models.ForeignKey(Author, unique=True) + objects = models.GeoManager() + class Book(models.Model): title = models.CharField(max_length=100) author = models.ForeignKey(Author, related_name='books', null=True) diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py index 184b65b9c72a..5d3d00f08d97 100644 --- a/django/contrib/gis/tests/relatedapp/tests.py +++ b/django/contrib/gis/tests/relatedapp/tests.py @@ -4,7 +4,7 @@ from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.tests.utils import mysql, oracle, postgis, spatialite, no_mysql, no_oracle, no_spatialite from django.conf import settings -from models import City, Location, DirectoryEntry, Parcel, Book, Author +from models import City, Location, DirectoryEntry, Parcel, Book, Author, Article cities = (('Aurora', 'TX', -97.516111, 33.058333), ('Roswell', 'NM', -104.528056, 33.387222), @@ -291,6 +291,14 @@ def test14_collect(self): self.assertEqual(4, len(coll)) self.assertEqual(ref_geom, coll) + def test15_invalid_select_related(self): + "Testing doing select_related on the related name manager of a unique FK. See #13934." + qs = Article.objects.select_related('author__article') + # This triggers TypeError when `get_default_columns` has no `local_only` + # keyword. The TypeError is swallowed if QuerySet is actually + # evaluated as list generation swallows TypeError in CPython. + sql = str(qs.query) + # TODO: Related tests for KML, GML, and distance lookups. def suite(): From 8c87622f8b80685098b6f7cc8b823ae2118ab439 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 21 Jul 2010 15:28:06 +0000 Subject: [PATCH 014/902] [1.2.X] Fixed #13967 -- MySQL spatial backend now respects when `spatial_index=False`. Thanks, Simon Law, for bug report and patch. Backport of r13443 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13444 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/db/backends/mysql/creation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/gis/db/backends/mysql/creation.py b/django/contrib/gis/db/backends/mysql/creation.py index 93fd2e6166c2..dda77ea6abd3 100644 --- a/django/contrib/gis/db/backends/mysql/creation.py +++ b/django/contrib/gis/db/backends/mysql/creation.py @@ -6,7 +6,7 @@ def sql_indexes_for_field(self, model, f, style): from django.contrib.gis.db.models.fields import GeometryField output = super(MySQLCreation, self).sql_indexes_for_field(model, f, style) - if isinstance(f, GeometryField): + if isinstance(f, GeometryField) and f.spatial_index: qn = self.connection.ops.quote_name db_table = model._meta.db_table idx_name = '%s_%s_id' % (db_table, f.column) From acbf74a72131bd3af4d7341a81635ee700b85b0e Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sat, 24 Jul 2010 10:19:39 +0000 Subject: [PATCH 015/902] [1.2.X] Fixed #13616 - Updated the documentation to be compatible with Sphinx 1.0. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13445 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/Makefile | 67 +++++-- docs/_ext/djangodocs.py | 23 ++- .../djangodocs}/genindex.html | 2 +- .../djangodocs}/layout.html | 4 +- .../djangodocs}/modindex.html | 2 +- .../djangodocs}/search.html | 2 +- .../djangodocs/static}/default.css | 0 .../djangodocs/static}/djangodocs.css | 0 .../static}/docicons-behindscenes.png | Bin .../djangodocs/static}/docicons-note.png | Bin .../static}/docicons-philosophy.png | Bin .../djangodocs/static}/homepage.css | 0 .../djangodocs/static}/reset-fonts-grids.css | 0 docs/_theme/djangodocs/theme.conf | 4 + docs/conf.py | 172 +++++++++++++++--- 15 files changed, 225 insertions(+), 51 deletions(-) rename docs/{_templates => _theme/djangodocs}/genindex.html (68%) rename docs/{_templates => _theme/djangodocs}/layout.html (97%) rename docs/{_templates => _theme/djangodocs}/modindex.html (67%) rename docs/{_templates => _theme/djangodocs}/search.html (69%) rename docs/{_static => _theme/djangodocs/static}/default.css (100%) rename docs/{_static => _theme/djangodocs/static}/djangodocs.css (100%) rename docs/{_static => _theme/djangodocs/static}/docicons-behindscenes.png (100%) rename docs/{_static => _theme/djangodocs/static}/docicons-note.png (100%) rename docs/{_static => _theme/djangodocs/static}/docicons-philosophy.png (100%) rename docs/{_static => _theme/djangodocs/static}/homepage.css (100%) rename docs/{_static => _theme/djangodocs/static}/reset-fonts-grids.css (100%) create mode 100644 docs/_theme/djangodocs/theme.conf diff --git a/docs/Makefile b/docs/Makefile index f6a92b6835a1..93013150405e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -12,20 +12,26 @@ PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* @@ -40,6 +46,11 @@ dirhtml: @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @@ -65,12 +76,42 @@ qthelp: @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django.qhc" +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/django" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index efcd94d4bc8c..4b1edca3f6d4 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -88,7 +88,10 @@ def parse_version_directive(name, arguments, options, content, lineno, if not is_nextversion: if len(arguments) == 1: linktext = 'Please, see the release notes ' % (arguments[0]) - xrefs = sphinx.roles.xfileref_role('ref', linktext, linktext, lineno, state) + try: + xrefs = sphinx.roles.XRefRole()('ref', linktext, linktext, lineno, state) # Sphinx >= 1.0 + except: + xrefs = sphinx.roles.xfileref_role('ref', linktext, linktext, lineno, state) # Sphinx < 1.0 node.extend(xrefs[0]) node['version'] = arguments[0] else: @@ -159,7 +162,7 @@ def depart_literal_block(self, node): # better callout -- the Sphinx default is just a little span, # which is a bit less obvious that I'd like. # - # FIXME: these messages are all hardcoded in English. We need to chanage + # FIXME: these messages are all hardcoded in English. We need to change # that to accomodate other language docs, but I can't work out how to make # that work and I think it'll require Sphinx 0.5 anyway. # @@ -212,7 +215,10 @@ def parse_django_admin_node(env, sig, signode): def parse_django_adminopt_node(env, sig, signode): """A copy of sphinx.directives.CmdoptionDesc.parse_signature()""" from sphinx import addnodes - from sphinx.directives.desc import option_desc_re + try: + from sphinx.domains.std import option_desc_re # Sphinx >= 1.0 + except: + from sphinx.directives.desc import option_desc_re # Sphinx < 1.0 count = 0 firstname = '' for m in option_desc_re.finditer(sig): @@ -278,9 +284,14 @@ def finish(self): self.warn("cannot create templatebuiltins.js due to missing simplejson dependency") return self.info(bold("writing templatebuiltins.js...")) - xrefs = self.env.reftargets.keys() - templatebuiltins = dict([('ttags', [n for (t,n) in xrefs if t == 'ttag']), - ('tfilters', [n for (t,n) in xrefs if t == 'tfilter'])]) + try: + xrefs = self.env.reftargets.keys() + templatebuiltins = dict([('ttags', [n for (t,n) in xrefs if t == 'ttag']), + ('tfilters', [n for (t,n) in xrefs if t == 'tfilter'])]) + except AttributeError: + xrefs = self.env.domaindata["std"]["objects"] + templatebuiltins = dict([('ttags', [n for (t,n) in xrefs if t == 'templatetag']), + ('tfilters', [n for (t,n) in xrefs if t == 'templatefilter'])]) outfilename = os.path.join(self.outdir, "templatebuiltins.js") f = open(outfilename, 'wb') f.write('var django_template_builtins = ') diff --git a/docs/_templates/genindex.html b/docs/_theme/djangodocs/genindex.html similarity index 68% rename from docs/_templates/genindex.html rename to docs/_theme/djangodocs/genindex.html index 60c19efd4547..486994ae911a 100644 --- a/docs/_templates/genindex.html +++ b/docs/_theme/djangodocs/genindex.html @@ -1,4 +1,4 @@ -{% extends "!genindex.html" %} +{% extends "basic/genindex.html" %} {% block bodyclass %}{% endblock %} {% block sidebarwrapper %}{% endblock %} \ No newline at end of file diff --git a/docs/_templates/layout.html b/docs/_theme/djangodocs/layout.html similarity index 97% rename from docs/_templates/layout.html rename to docs/_theme/djangodocs/layout.html index 70e029c3acf2..ef91dd77a92d 100644 --- a/docs/_templates/layout.html +++ b/docs/_theme/djangodocs/layout.html @@ -1,4 +1,4 @@ -{% extends "!layout.html" %} +{% extends "basic/layout.html" %} {%- macro secondnav() %} {%- if prev %} @@ -61,7 +61,7 @@

{{ docstitle }}

Home {{ reldelim2 }} Table of contents {{ reldelim2 }} Index {{ reldelim2 }} - Modules + Modules diff --git a/docs/_templates/modindex.html b/docs/_theme/djangodocs/modindex.html similarity index 67% rename from docs/_templates/modindex.html rename to docs/_theme/djangodocs/modindex.html index 96a1d2080aa7..59a5cb31bdf5 100644 --- a/docs/_templates/modindex.html +++ b/docs/_theme/djangodocs/modindex.html @@ -1,3 +1,3 @@ -{% extends "!modindex.html" %} +{% extends "basic/modindex.html" %} {% block bodyclass %}{% endblock %} {% block sidebarwrapper %}{% endblock %} \ No newline at end of file diff --git a/docs/_templates/search.html b/docs/_theme/djangodocs/search.html similarity index 69% rename from docs/_templates/search.html rename to docs/_theme/djangodocs/search.html index 8bd6dbd33220..943478ce7514 100644 --- a/docs/_templates/search.html +++ b/docs/_theme/djangodocs/search.html @@ -1,3 +1,3 @@ -{% extends "!search.html" %} +{% extends "basic/search.html" %} {% block bodyclass %}{% endblock %} {% block sidebarwrapper %}{% endblock %} \ No newline at end of file diff --git a/docs/_static/default.css b/docs/_theme/djangodocs/static/default.css similarity index 100% rename from docs/_static/default.css rename to docs/_theme/djangodocs/static/default.css diff --git a/docs/_static/djangodocs.css b/docs/_theme/djangodocs/static/djangodocs.css similarity index 100% rename from docs/_static/djangodocs.css rename to docs/_theme/djangodocs/static/djangodocs.css diff --git a/docs/_static/docicons-behindscenes.png b/docs/_theme/djangodocs/static/docicons-behindscenes.png similarity index 100% rename from docs/_static/docicons-behindscenes.png rename to docs/_theme/djangodocs/static/docicons-behindscenes.png diff --git a/docs/_static/docicons-note.png b/docs/_theme/djangodocs/static/docicons-note.png similarity index 100% rename from docs/_static/docicons-note.png rename to docs/_theme/djangodocs/static/docicons-note.png diff --git a/docs/_static/docicons-philosophy.png b/docs/_theme/djangodocs/static/docicons-philosophy.png similarity index 100% rename from docs/_static/docicons-philosophy.png rename to docs/_theme/djangodocs/static/docicons-philosophy.png diff --git a/docs/_static/homepage.css b/docs/_theme/djangodocs/static/homepage.css similarity index 100% rename from docs/_static/homepage.css rename to docs/_theme/djangodocs/static/homepage.css diff --git a/docs/_static/reset-fonts-grids.css b/docs/_theme/djangodocs/static/reset-fonts-grids.css similarity index 100% rename from docs/_static/reset-fonts-grids.css rename to docs/_theme/djangodocs/static/reset-fonts-grids.css diff --git a/docs/_theme/djangodocs/theme.conf b/docs/_theme/djangodocs/theme.conf new file mode 100644 index 000000000000..be43c723ae61 --- /dev/null +++ b/docs/_theme/djangodocs/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = basic +stylesheet = default.css +pygments_style = trac diff --git a/docs/conf.py b/docs/conf.py index 90e0a6bcb5de..d3980a530f92 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,17 +8,21 @@ # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). # -# All configuration values have a default value; values that are commented out -# serve to show the default value. +# All configuration values have a default; values that are commented out +# serve to show the default. import sys import os -# If your extensions are in another directory, add it here. -sys.path.append(os.path.join(os.path.dirname(__file__), "_ext")) +# 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"))) -# General configuration -# --------------------- +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -30,6 +34,9 @@ # The suffix of source filenames. source_suffix = '.txt' +# The encoding of source files. +#source_encoding = 'utf-8-sig' + # The master toctree document. master_doc = 'contents' @@ -37,8 +44,10 @@ project = 'Django' copyright = 'Django Software Foundation and contributors' -# The default replacements for |version| and |release|, also used in various -# other places throughout the built documents. + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. # # The short X.Y version. version = '1.2' @@ -47,14 +56,22 @@ # The next version to be released django_next_version = '1.3' +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' -# List of documents that shouldn't be included in the build. -#unused_docs = [] +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True @@ -75,13 +92,35 @@ # Note: exclude_dirnames is new in Sphinx 0.5 exclude_dirnames = ['.svn'] -# Options for HTML output -# ----------------------- +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "djangodocs" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ["_theme"] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None -# The style sheet to use for HTML and HTML Help pages. A file of that name -# must exist either in Sphinx' static/ path, or in one of the custom paths -# given in html_static_path. -html_style = 'default.css' +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -110,17 +149,38 @@ html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True -# If true, the reST sources are included in the HTML build as _sources/. -html_copy_source = True +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Djangodoc' +modindex_common_prefix = ["django."] + -# Options for LaTeX output -# ------------------------ +# -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' @@ -132,9 +192,24 @@ # (source start file, target name, title, author, document class [howto/manual]). #latex_documents = [] latex_documents = [ - ('contents', 'django.tex', 'Django Documentation', 'Django Software Foundation', 'manual'), + ('contents', 'django.tex', u'Django Documentation', + u'Django Software Foundation', 'manual'), ] +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + # Additional stuff for the LaTeX preamble. #latex_preamble = '' @@ -142,10 +217,53 @@ #latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +#latex_domain_indices = True -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# If this isn't set to True, the LaTex writer can only handle six levels of headers. -latex_use_parts = True +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('contents', 'django', 'Django Documentation', ['Django Software Foundation'], 1) +] + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'Django' +epub_author = u'Django Software Foundation' +epub_publisher = u'Django Software Foundation' +epub_copyright = u'2010, Django Software Foundation' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True From 953f4be2014f0820a887d99a0d991618a6102c63 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 25 Jul 2010 20:58:58 +0000 Subject: [PATCH 016/902] [1.2.X] Fixed #14005 - Removed a few unneeded workarounds in the Sphinx extension. Thanks for the report and patch, Ramiro Morales. Backport of r13447 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13448 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/_ext/djangodocs.py | 152 +++++++++---------------------- docs/conf.py | 2 +- docs/internals/documentation.txt | 5 + 3 files changed, 48 insertions(+), 111 deletions(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index 4b1edca3f6d4..cee14ba6f15e 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -1,9 +1,9 @@ """ Sphinx plugins for Django documentation. """ +import os -import docutils.nodes -import docutils.transforms +from docutils import nodes, transforms try: import json except ImportError: @@ -14,26 +14,12 @@ from django.utils import simplejson as json except ImportError: json = None -import os -import sphinx -import sphinx.addnodes -try: - from sphinx import builders -except ImportError: - import sphinx.builder as builders -try: - import sphinx.builders.html as builders_html -except ImportError: - builders_html = builders + +from sphinx import addnodes, roles +from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.writers.html import SmartyPantsHTMLTranslator from sphinx.util.console import bold -import sphinx.directives -import sphinx.environment -try: - import sphinx.writers.html as sphinx_htmlwriter -except ImportError: - import sphinx.htmlwriter as sphinx_htmlwriter -import sphinx.roles -from docutils import nodes + def setup(app): app.add_crossref_type( @@ -74,24 +60,20 @@ def setup(app): app.add_transform(SuppressBlockquotes) app.add_builder(DjangoStandaloneHTMLBuilder) - # Monkeypatch PickleHTMLBuilder so that it doesn't die in Sphinx 0.4.2 - if sphinx.__version__ == '0.4.2': - monkeypatch_pickle_builder() - def parse_version_directive(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): env = state.document.settings.env is_nextversion = env.config.django_next_version == arguments[0] ret = [] - node = sphinx.addnodes.versionmodified() + node = addnodes.versionmodified() ret.append(node) if not is_nextversion: if len(arguments) == 1: linktext = 'Please, see the release notes ' % (arguments[0]) try: - xrefs = sphinx.roles.XRefRole()('ref', linktext, linktext, lineno, state) # Sphinx >= 1.0 + xrefs = roles.XRefRole()('ref', linktext, linktext, lineno, state) # Sphinx >= 1.0 except: - xrefs = sphinx.roles.xfileref_role('ref', linktext, linktext, lineno, state) # Sphinx < 1.0 + xrefs = roles.xfileref_role('ref', linktext, linktext, lineno, state) # Sphinx < 1.0 node.extend(xrefs[0]) node['version'] = arguments[0] else: @@ -106,29 +88,29 @@ def parse_version_directive(name, arguments, options, content, lineno, env.note_versionchange(node['type'], node['version'], node, lineno) return ret - -class SuppressBlockquotes(docutils.transforms.Transform): + +class SuppressBlockquotes(transforms.Transform): """ Remove the default blockquotes that encase indented list, tables, etc. """ default_priority = 300 - + suppress_blockquote_child_nodes = ( - docutils.nodes.bullet_list, - docutils.nodes.enumerated_list, - docutils.nodes.definition_list, - docutils.nodes.literal_block, - docutils.nodes.doctest_block, - docutils.nodes.line_block, - docutils.nodes.table + nodes.bullet_list, + nodes.enumerated_list, + nodes.definition_list, + nodes.literal_block, + nodes.doctest_block, + nodes.line_block, + nodes.table ) - + def apply(self): - for node in self.document.traverse(docutils.nodes.block_quote): + for node in self.document.traverse(nodes.block_quote): if len(node.children) == 1 and isinstance(node.children[0], self.suppress_blockquote_child_nodes): node.replace_self(node.children[0]) -class DjangoHTMLTranslator(sphinx_htmlwriter.SmartyPantsHTMLTranslator): +class DjangoHTMLTranslator(SmartyPantsHTMLTranslator): """ Django-specific reST to HTML tweaks. """ @@ -136,42 +118,41 @@ class DjangoHTMLTranslator(sphinx_htmlwriter.SmartyPantsHTMLTranslator): # Don't use border=1, which docutils does by default. def visit_table(self, node): self.body.append(self.starttag(node, 'table', CLASS='docutils')) - + # ? Really? def visit_desc_parameterlist(self, node): self.body.append('(') self.first_param = 1 - + def depart_desc_parameterlist(self, node): self.body.append(')') - pass - + # # Don't apply smartypants to literal blocks # def visit_literal_block(self, node): self.no_smarty += 1 - sphinx_htmlwriter.SmartyPantsHTMLTranslator.visit_literal_block(self, node) + SmartyPantsHTMLTranslator.visit_literal_block(self, node) def depart_literal_block(self, node): - sphinx_htmlwriter.SmartyPantsHTMLTranslator.depart_literal_block(self, node) + SmartyPantsHTMLTranslator.depart_literal_block(self, node) self.no_smarty -= 1 - + # - # Turn the "new in version" stuff (versoinadded/versionchanged) into a + # Turn the "new in version" stuff (versionadded/versionchanged) into a # better callout -- the Sphinx default is just a little span, # which is a bit less obvious that I'd like. # # FIXME: these messages are all hardcoded in English. We need to change # that to accomodate other language docs, but I can't work out how to make - # that work and I think it'll require Sphinx 0.5 anyway. + # that work. # version_text = { 'deprecated': 'Deprecated in Django %s', 'versionchanged': 'Changed in Django %s', 'versionadded': 'New in Django %s', } - + def visit_versionmodified(self, node): self.body.append( self.starttag(node, 'div', CLASS=node['type']) @@ -181,40 +162,27 @@ def visit_versionmodified(self, node): len(node) and ":" or "." ) self.body.append('%s ' % title) - + def depart_versionmodified(self, node): self.body.append("\n") - - # Give each section a unique ID -- nice for custom CSS hooks - # This is different on docutils 0.5 vs. 0.4... - - if hasattr(sphinx_htmlwriter.SmartyPantsHTMLTranslator, 'start_tag_with_title') and sphinx.__version__ == '0.4.2': - def start_tag_with_title(self, node, tagname, **atts): - node = { - 'classes': node.get('classes', []), - 'ids': ['s-%s' % i for i in node.get('ids', [])] - } - return self.starttag(node, tagname, **atts) - else: - def visit_section(self, node): - old_ids = node.get('ids', []) - node['ids'] = ['s-' + i for i in old_ids] - if sphinx.__version__ != '0.4.2': - node['ids'].extend(old_ids) - sphinx_htmlwriter.SmartyPantsHTMLTranslator.visit_section(self, node) - node['ids'] = old_ids + # Give each section a unique ID -- nice for custom CSS hooks + def visit_section(self, node): + old_ids = node.get('ids', []) + node['ids'] = ['s-' + i for i in old_ids] + node['ids'].extend(old_ids) + SmartyPantsHTMLTranslator.visit_section(self, node) + node['ids'] = old_ids def parse_django_admin_node(env, sig, signode): command = sig.split(' ')[0] env._django_curr_admin_command = command title = "django-admin.py %s" % sig - signode += sphinx.addnodes.desc_name(title, title) + signode += addnodes.desc_name(title, title) return sig def parse_django_adminopt_node(env, sig, signode): """A copy of sphinx.directives.CmdoptionDesc.parse_signature()""" - from sphinx import addnodes try: from sphinx.domains.std import option_desc_re # Sphinx >= 1.0 except: @@ -234,44 +202,8 @@ def parse_django_adminopt_node(env, sig, signode): raise ValueError return firstname -def monkeypatch_pickle_builder(): - import shutil - from os import path - try: - import cPickle as pickle - except ImportError: - import pickle - - def handle_finish(self): - # dump the global context - outfilename = path.join(self.outdir, 'globalcontext.pickle') - f = open(outfilename, 'wb') - try: - pickle.dump(self.globalcontext, f, 2) - finally: - f.close() - - self.info(bold('dumping search index...')) - self.indexer.prune(self.env.all_docs) - f = open(path.join(self.outdir, 'searchindex.pickle'), 'wb') - try: - self.indexer.dump(f, 'pickle') - finally: - f.close() - - # copy the environment file from the doctree dir to the output dir - # as needed by the web app - shutil.copyfile(path.join(self.doctreedir, builders.ENV_PICKLE_FILENAME), - path.join(self.outdir, builders.ENV_PICKLE_FILENAME)) - - # touch 'last build' file, used by the web application to determine - # when to reload its environment and clear the cache - open(path.join(self.outdir, builders.LAST_BUILD_FILENAME), 'w').close() - - builders.PickleHTMLBuilder.handle_finish = handle_finish - -class DjangoStandaloneHTMLBuilder(builders_html.StandaloneHTMLBuilder): +class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder): """ Subclass to add some extra things we need. """ diff --git a/docs/conf.py b/docs/conf.py index d3980a530f92..606ee6b5adca 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,7 @@ extensions = ["djangodocs"] # Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +# templates_path = [] # The suffix of source filenames. source_suffix = '.txt' diff --git a/docs/internals/documentation.txt b/docs/internals/documentation.txt index 81480abf9ab6..9aa8551266b6 100644 --- a/docs/internals/documentation.txt +++ b/docs/internals/documentation.txt @@ -15,6 +15,11 @@ __ http://docutils.sourceforge.net/ To actually build the documentation locally, you'll currently need to install Sphinx -- ``easy_install Sphinx`` should do the trick. +.. note:: + + Generation of the Django documentation will work with Sphinx version 0.6 + or newer, but we recommend going straigh to Sphinx 1.0 or newer. + Then, building the html is easy; just ``make html`` from the ``docs`` directory. To get started contributing, you'll want to read the `ReStructuredText From 3227cc59511454d311e4dd8f9f5293b0f06664e9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 30 Jul 2010 02:58:51 +0000 Subject: [PATCH 017/902] [1.2.X] Fixed #13773 -- Passed in the current connection in a call to db_type(). Thanks to Suor for the report and patch. Backport of r13451 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13453 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/postgresql/creation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py index af26d0b78f5f..1a821afe07d9 100644 --- a/django/db/backends/postgresql/creation.py +++ b/django/db/backends/postgresql/creation.py @@ -63,7 +63,7 @@ def get_index_sql(index_name, opclass=''): # a second index that specifies their operator class, which is # needed when performing correct LIKE queries outside the # C locale. See #12234. - db_type = f.db_type() + db_type = f.db_type(connection=self.connection) if db_type.startswith('varchar'): output.append(get_index_sql('%s_%s_like' % (db_table, f.column), ' varchar_pattern_ops')) From c61811a8f2679fe0dd9943f228e62756e38fb1dc Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 30 Jul 2010 02:59:14 +0000 Subject: [PATCH 018/902] [1.2.X] Fixed #13730 -- Removed the hard-coding of the requirement that ForeignKeys have an index. Thanks to Suor for the report. Backport of r13452 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13454 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/related.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 5830a794dfed..232d5d5e74ff 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -812,6 +812,9 @@ def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): to_field = to_field or (to._meta.pk and to._meta.pk.name) kwargs['verbose_name'] = kwargs.get('verbose_name', None) + if 'db_index' not in kwargs: + kwargs['db_index'] = True + kwargs['rel'] = rel_class(to, to_field, related_name=kwargs.pop('related_name', None), limit_choices_to=kwargs.pop('limit_choices_to', None), @@ -819,8 +822,6 @@ def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs): parent_link=kwargs.pop('parent_link', False)) Field.__init__(self, **kwargs) - self.db_index = True - def validate(self, value, model_instance): if self.rel.parent_link: return From dc449e9664ff89b2acab9934a5589e04061dc4ec Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 30 Jul 2010 04:07:27 +0000 Subject: [PATCH 019/902] [1.2.X] Fixed #13882 -- Removed an unnecessary nested where clause introduced on __isnull queries. Thanks to Alex Gaynor for the report and patch. Backport of r13456 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13457 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/sql/query.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 0913399e2aa1..ec477447f397 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1090,10 +1090,7 @@ def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, # exclude the "foo__in=[]" case from this handling, because # it's short-circuited in the Where class. # We also need to handle the case where a subquery is provided - entry = self.where_class() - entry.add((Constraint(alias, col, None), 'isnull', True), AND) - entry.negate() - self.where.add(entry, AND) + self.where.add((Constraint(alias, col, None), 'isnull', False), AND) if can_reuse is not None: can_reuse.update(join_list) From 38813c4bbd08f246462222bcd63ec2da55826b23 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 30 Jul 2010 04:16:20 +0000 Subject: [PATCH 020/902] [1.2.X] Fixed #13740 -- Added documentation for the can_delete InlineModelAdmin option. Thanks to Alex Gaynor for the patch. Backport of r13458 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13459 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/admin/index.txt | 110 +++++++++++++++---------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index f7aefa457d7d..4d84dfafbc9e 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1027,90 +1027,88 @@ The difference between these two is merely the template used to render them. The ``InlineModelAdmin`` class is a subclass of ``ModelAdmin`` so it inherits all the same functionality as well as some of its own: -``model`` -~~~~~~~~~ +.. attribute:: InlineModelAdmin.model -The model in which the inline is using. This is required. + The model in which the inline is using. This is required. -``fk_name`` -~~~~~~~~~~~ +.. attribute:: InlineModelAdmin.fk_name -The name of the foreign key on the model. In most cases this will be dealt -with automatically, but ``fk_name`` must be specified explicitly if there are -more than one foreign key to the same parent model. + The name of the foreign key on the model. In most cases this will be dealt + with automatically, but ``fk_name`` must be specified explicitly if there + are more than one foreign key to the same parent model. -``formset`` -~~~~~~~~~~~ +.. attribute:: InlineModelAdmin.formset -This defaults to ``BaseInlineFormSet``. Using your own formset can give you -many possibilities of customization. Inlines are built around -:ref:`model formsets `. + This defaults to ``BaseInlineFormSet``. Using your own formset can give you + many possibilities of customization. Inlines are built around + :ref:`model formsets `. -``form`` -~~~~~~~~ +.. attribute:: InlineModelAdmin.form -The value for ``form`` defaults to ``ModelForm``. This is what is -passed through to ``inlineformset_factory`` when creating the formset for this -inline. + The value for ``form`` defaults to ``ModelForm``. This is what is passed + through to ``inlineformset_factory`` when creating the formset for this + inline. .. _ref-contrib-admin-inline-extra: -``extra`` -~~~~~~~~~ +.. attribute:: InlineModelAdmin.extra -This controls the number of extra forms the formset will display in addition -to the initial forms. See the -:ref:`formsets documentation ` for more information. -.. versionadded:: 1.2 + This controls the number of extra forms the formset will display in addition + to the initial forms. See the + :ref:`formsets documentation ` for more information. + + .. versionadded:: 1.2 -For users with JavaScript-enabled browsers, an "Add another" link is -provided to enable any number of additional inlines to be added in -addition to those provided as a result of the ``extra`` argument. + For users with JavaScript-enabled browsers, an "Add another" link is + provided to enable any number of additional inlines to be added in addition + to those provided as a result of the ``extra`` argument. -The dynamic link will not appear if the number of currently displayed -forms exceeds ``max_num``, or if the user does not have JavaScript -enabled. + The dynamic link will not appear if the number of currently displayed forms + exceeds ``max_num``, or if the user does not have JavaScript enabled. .. _ref-contrib-admin-inline-max-num: -``max_num`` -~~~~~~~~~~~ +.. attribute:: InlineModelAdmin.max_num -This controls the maximum number of forms to show in the inline. This doesn't -directly correlate to the number of objects, but can if the value is small -enough. See :ref:`model-formsets-max-num` for more information. + This controls the maximum number of forms to show in the inline. This + doesn't directly correlate to the number of objects, but can if the value + is small enough. See :ref:`model-formsets-max-num` for more information. -``raw_id_fields`` -~~~~~~~~~~~~~~~~~ +.. attribute:: InlineModelAdmin.raw_id_fields -By default, Django's admin uses a select-box interface () for + fields that are ``ForeignKey``. Sometimes you don't want to incur the + overhead of having to select all the related instances to display in the + drop-down. -``raw_id_fields`` is a list of fields you would like to change -into a ``Input`` widget for either a ``ForeignKey`` or ``ManyToManyField``:: + ``raw_id_fields`` is a list of fields you would like to change into a + ``Input`` widget for either a ``ForeignKey`` or ``ManyToManyField``:: - class BookInline(admin.TabularInline): - model = Book - raw_id_fields = ("pages",) + class BookInline(admin.TabularInline): + model = Book + raw_id_fields = ("pages",) -``template`` -~~~~~~~~~~~~ -The template used to render the inline on the page. +.. attribute:: InlineModelAdmin.template -``verbose_name`` -~~~~~~~~~~~~~~~~ + The template used to render the inline on the page. -An override to the ``verbose_name`` found in the model's inner ``Meta`` class. +.. attribute:: InlineModelAdmin.verbose_name -``verbose_name_plural`` -~~~~~~~~~~~~~~~~~~~~~~~ + An override to the ``verbose_name`` found in the model's inner ``Meta`` + class. + +.. attribute:: InlineModelAdmin.verbose_name_plural + + An override to the ``verbose_name_plural`` found in the model's inner + ``Meta`` class. + +.. attribute:: InlineModelAdmin.can_delete + + Specifies whether or not inline objects can be deleted in the inline. + Defaults to ``True``. -An override to the ``verbose_name_plural`` found in the model's inner ``Meta`` -class. Working with a model with two or more foreign keys to the same parent model --------------------------------------------------------------------------- From c0040b41514d37ef7d31cda9bee8718a64828390 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Tue, 3 Aug 2010 15:37:00 +0000 Subject: [PATCH 021/902] [1.2.X] Fixed #11288: added some tests for the handling of number-like variables in templates. Thanks, Stephen Kelly. Backport of [13460] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13462 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/templates/tests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 5902e8d5e7ae..21afb2dd6f81 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -506,6 +506,17 @@ def get_template_tests(self): 'basic-syntax28': ("{{ a.b }}", {'a': SilentGetItemClass()}, ('', 'INVALID')), 'basic-syntax29': ("{{ a.b }}", {'a': SilentAttrClass()}, ('', 'INVALID')), + # Something that starts like a number but has an extra lookup works as a lookup. + 'basic-syntax30': ("{{ 1.2.3 }}", {"1": {"2": {"3": "d"}}}, "d"), + 'basic-syntax31': ("{{ 1.2.3 }}", {"1": {"2": ("a", "b", "c", "d")}}, "d"), + 'basic-syntax32': ("{{ 1.2.3 }}", {"1": (("x", "x", "x", "x"), ("y", "y", "y", "y"), ("a", "b", "c", "d"))}, "d"), + 'basic-syntax33': ("{{ 1.2.3 }}", {"1": ("xxxx", "yyyy", "abcd")}, "d"), + 'basic-syntax34': ("{{ 1.2.3 }}", {"1": ({"x": "x"}, {"y": "y"}, {"z": "z", "3": "d"})}, "d"), + + # Numbers are numbers even if their digits are in the context. + 'basic-syntax35': ("{{ 1 }}", {"1": "abc"}, "1"), + 'basic-syntax36': ("{{ 1.2 }}", {"1": "abc"}, "1.2"), + # List-index syntax allows a template to access a certain item of a subscriptable object. 'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"), From 4441f7b0f3b4f9a0b67d6c67eeb1c9bacbac7b77 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Tue, 3 Aug 2010 15:37:07 +0000 Subject: [PATCH 022/902] [1.2.X] Fixed #11376: added some extra tests for autoescaping subtleties. Thanks, Stephen Kelly. Backport of [13461] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13463 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/templates/tests.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 21afb2dd6f81..2f2df65e9698 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -603,7 +603,7 @@ def get_template_tests(self): #filters should accept empty string constants 'filter-syntax20': ('{{ ""|default_if_none:"was none" }}', {}, ""), - + ### COMMENT SYNTAX ######################################################## 'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"), 'comment-syntax02': ("{# this is hidden #}hello{# foo #}", {}, "hello"), @@ -1296,7 +1296,8 @@ def get_template_tests(self): # Regression test for #11270. 'cache17': ('{% load cache %}{% cache 10 long_cache_key poem %}Some Content{% endcache %}', {'poem': 'Oh freddled gruntbuggly/Thy micturations are to me/As plurdled gabbleblotchits/On a lurgid bee/That mordiously hath bitled out/Its earted jurtles/Into a rancid festering/Or else I shall rend thee in the gobberwarts with my blurglecruncheon/See if I dont.'}, 'Some Content'), - + + ### AUTOESCAPE TAG ############################################## 'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"), 'autoescape-tag02': ("{% autoescape off %}{{ first }}{% endautoescape %}", {"first": "hello"}, "hello"), @@ -1325,6 +1326,23 @@ def get_template_tests(self): # implementation details (fortunately, the (no)autoescape block # tags can be used in those cases) 'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x"}, template.TemplateSyntaxError), + + # ifqeual compares unescaped vales. + 'autoescape-ifequal01': ('{% ifequal var "this & that" %}yes{% endifequal %}', { "var": "this & that" }, "yes" ), + + # Arguments to filters are 'safe' and manipulate their input unescaped. + 'autoescape-filters01': ('{{ var|cut:"&" }}', { "var": "this & that" }, "this that" ), + 'autoescape-filters02': ('{{ var|join:" & \" }}', { "var": ("Tom", "Dick", "Harry") }, "Tom & Dick & Harry" ), + + # Literal strings are safe. + 'autoescape-literals01': ('{{ "this & that" }}',{}, "this & that" ), + + # Iterating over strings outputs safe characters. + 'autoescape-stringiterations01': ('{% for l in var %}{{ l }},{% endfor %}', {'var': 'K&R'}, "K,&,R," ), + + # Escape requirement survives lookup. + 'autoescape-lookup01': ('{{ var.key }}', { "var": {"key": "this & that" }}, "this & that" ), + } From 972a9c7e18d39f64c3783c361783406c67f1a0f9 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Tue, 3 Aug 2010 15:43:29 +0000 Subject: [PATCH 023/902] [1.2.X] Fixed #11377: the template join filter now correctly escapes the joiner, too. Thanks, Stephen Kelly. Backport of [13464] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13465 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/defaultfilters.py | 6 +++--- tests/regressiontests/templates/filters.py | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 4b720093b32f..d8e7e91efe4a 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -11,9 +11,10 @@ from django.template import Variable, Library from django.conf import settings from django.utils import formats -from django.utils.translation import ugettext, ungettext from django.utils.encoding import force_unicode, iri_to_uri +from django.utils.html import conditional_escape from django.utils.safestring import mark_safe, SafeData +from django.utils.translation import ugettext, ungettext register = Library() @@ -496,10 +497,9 @@ def join(value, arg, autoescape=None): """ value = map(force_unicode, value) if autoescape: - from django.utils.html import conditional_escape value = [conditional_escape(v) for v in value] try: - data = arg.join(value) + data = conditional_escape(arg).join(value) except AttributeError: # fail silently but nicely return value return mark_safe(data) diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py index 3d6284e88175..d351c550b7d3 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/regressiontests/templates/filters.py @@ -328,7 +328,12 @@ def get_filter_tests(): 'join03': (r'{{ a|join:" & " }}', {'a': ['alpha', 'beta & me']}, 'alpha & beta & me'), 'join04': (r'{% autoescape off %}{{ a|join:" & " }}{% endautoescape %}', {'a': ['alpha', 'beta & me']}, 'alpha & beta & me'), - + # Test that joining with unsafe joiners don't result in unsafe strings (#11377) + 'join05': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': ' & '}, 'alpha & beta & me'), + 'join06': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta & me'), + 'join07': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': ' & ' }, 'alpha & beta & me'), + 'join08': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta & me'), + 'date01': (r'{{ d|date:"m" }}', {'d': datetime(2008, 1, 1)}, '01'), 'date02': (r'{{ d|date }}', {'d': datetime(2008, 1, 1)}, 'Jan. 1, 2008'), #Ticket 9520: Make sure |date doesn't blow up on non-dates From 389c72c358a93597cfafc00c3182fe9fce5231cf Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 4 Aug 2010 14:36:44 +0000 Subject: [PATCH 024/902] [1.2.X] Fixed #14025 -- Modified flush to adhere to router sync instructions when emmiting post-sync signals. Thanks to linovia for the patch. Backport of r13466 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13467 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/commands/flush.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 6836fe35cab8..98e3f3151b24 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -1,7 +1,7 @@ from optparse import make_option from django.conf import settings -from django.db import connections, transaction, models, DEFAULT_DB_ALIAS +from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS from django.core.management import call_command from django.core.management.base import NoArgsCommand, CommandError from django.core.management.color import no_style @@ -66,7 +66,13 @@ def handle_noargs(self, **options): # Emit the post sync signal. This allows individual # applications to respond as if the database had been # sync'd from scratch. - emit_post_sync_signal(models.get_models(), verbosity, interactive, db) + all_models = [ + (app.__name__.split('.')[-2], + [m for m in models.get_models(app, include_auto_created=True) + if router.allow_syncdb(db, m)]) + for app in models.get_apps() + ] + emit_post_sync_signal(all_models, verbosity, interactive, db) # Reinstall the initial_data fixture. kwargs = options.copy() From 1c237cb77c9912972dc877a39f432e01a4115648 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Wed, 4 Aug 2010 19:08:58 +0000 Subject: [PATCH 025/902] [1.2.X] ixed #13746: made the dumdata help message a bit clearer. Thanks, PaulM. Backport of [13469] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13470 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/commands/dumpdata.py | 5 +++-- docs/topics/db/managers.txt | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/django/core/management/commands/dumpdata.py b/django/core/management/commands/dumpdata.py index aaaa5845a572..23c03e7b17fa 100644 --- a/django/core/management/commands/dumpdata.py +++ b/django/core/management/commands/dumpdata.py @@ -20,7 +20,8 @@ class Command(BaseCommand): make_option('-n', '--natural', action='store_true', dest='use_natural_keys', default=False, help='Use natural keys if they are available.'), ) - help = 'Output the contents of the database as a fixture of the given format.' + help = ("Output the contents of the database as a fixture of the given " + "format (using each model's default manager).") args = '[appname appname.ModelName ...]' def handle(self, *app_labels, **options): @@ -163,4 +164,4 @@ def sort_dependencies(app_list): ) model_dependencies = skipped - return model_list \ No newline at end of file + return model_list diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt index 123408b2bb29..aa47e5dd1522 100644 --- a/docs/topics/db/managers.txt +++ b/docs/topics/db/managers.txt @@ -170,7 +170,8 @@ and ``Person.people.all()``, yielding predictable results. If you use custom ``Manager`` objects, take note that the first ``Manager`` Django encounters (in the order in which they're defined in the model) has a special status. Django interprets the first ``Manager`` defined in a class as -the "default" ``Manager``, and several parts of Django will use that ``Manager`` +the "default" ``Manager``, and several parts of Django +(including :djadmin:`dumpdata`) will use that ``Manager`` exclusively for that model. As a result, it's a good idea to be careful in your choice of default manager in order to avoid a situation where overriding ``get_query_set()`` results in an inability to retrieve objects you'd like to From 9383a37485f4c6a8b132447c7c0b66d598b0651a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 5 Aug 2010 01:30:54 +0000 Subject: [PATCH 026/902] [1.2.X] Corrected fix committed in r13466. Refs #14025. Backport of r13471 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13472 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/commands/flush.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 98e3f3151b24..f7e769bdac57 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -66,12 +66,12 @@ def handle_noargs(self, **options): # Emit the post sync signal. This allows individual # applications to respond as if the database had been # sync'd from scratch. - all_models = [ - (app.__name__.split('.')[-2], - [m for m in models.get_models(app, include_auto_created=True) - if router.allow_syncdb(db, m)]) - for app in models.get_apps() - ] + all_models = [] + for app in models.get_apps(): + all_models.extend([ + m for m in models.get_models(app, include_auto_created=True) + if router.allow_syncdb(db, m) + ]) emit_post_sync_signal(all_models, verbosity, interactive, db) # Reinstall the initial_data fixture. From 6c2e31e9b7004d5722b5e9b2c60a5fed7c017689 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 5 Aug 2010 02:14:31 +0000 Subject: [PATCH 027/902] [1.2.X] Fixed #13946 -- Modified the database cache backend to use the database router to determine availability of the cache table. Thanks to tiemonster for the report. Backport of r13473 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13474 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/cache/backends/db.py | 99 ++++++++++++++++++++++---------- django/db/backends/creation.py | 8 ++- docs/topics/cache.txt | 43 ++++++++++++++ 3 files changed, 116 insertions(+), 34 deletions(-) diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index 3398e6a85b70..1300dd4b665f 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -1,7 +1,7 @@ "Database cache backend." from django.core.cache.backends.base import BaseCache -from django.db import connection, transaction, DatabaseError +from django.db import connections, router, transaction, DatabaseError import base64, time from datetime import datetime try: @@ -9,10 +9,31 @@ except ImportError: import pickle +class Options(object): + """A class that will quack like a Django model _meta class. + + This allows cache operations to be controlled by the router + """ + def __init__(self, table): + self.db_table = table + self.app_label = 'django_cache' + self.module_name = 'cacheentry' + self.verbose_name = 'cache entry' + self.verbose_name_plural = 'cache entries' + self.object_name = 'CacheEntry' + self.abstract = False + self.managed = True + self.proxy = False + class CacheClass(BaseCache): def __init__(self, table, params): BaseCache.__init__(self, params) - self._table = connection.ops.quote_name(table) + self._table = table + + class CacheEntry(object): + _meta = Options(table) + self.cache_model_class = CacheEntry + max_entries = params.get('max_entries', 300) try: self._max_entries = int(max_entries) @@ -25,17 +46,22 @@ def __init__(self, table, params): self._cull_frequency = 3 def get(self, key, default=None): - cursor = connection.cursor() - cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % self._table, [key]) + db = router.db_for_read(self.cache_model_class) + table = connections[db].ops.quote_name(self._table) + cursor = connections[db].cursor() + + cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % table, [key]) row = cursor.fetchone() if row is None: return default now = datetime.now() if row[2] < now: - cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key]) - transaction.commit_unless_managed() + db = router.db_for_write(self.cache_model_class) + cursor = connections[db].cursor() + cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key]) + transaction.commit_unless_managed(using=db) return default - value = connection.ops.process_clob(row[1]) + value = connections[db].ops.process_clob(row[1]) return pickle.loads(base64.decodestring(value)) def set(self, key, value, timeout=None): @@ -47,56 +73,67 @@ def add(self, key, value, timeout=None): def _base_set(self, mode, key, value, timeout=None): if timeout is None: timeout = self.default_timeout - cursor = connection.cursor() - cursor.execute("SELECT COUNT(*) FROM %s" % self._table) + db = router.db_for_write(self.cache_model_class) + table = connections[db].ops.quote_name(self._table) + cursor = connections[db].cursor() + + cursor.execute("SELECT COUNT(*) FROM %s" % table) num = cursor.fetchone()[0] now = datetime.now().replace(microsecond=0) exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0) if num > self._max_entries: - self._cull(cursor, now) + self._cull(db, cursor, now) encoded = base64.encodestring(pickle.dumps(value, 2)).strip() - cursor.execute("SELECT cache_key, expires FROM %s WHERE cache_key = %%s" % self._table, [key]) + cursor.execute("SELECT cache_key, expires FROM %s WHERE cache_key = %%s" % table, [key]) try: result = cursor.fetchone() if result and (mode == 'set' or (mode == 'add' and result[1] < now)): - cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, - [encoded, connection.ops.value_to_db_datetime(exp), key]) + cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % table, + [encoded, connections[db].ops.value_to_db_datetime(exp), key]) else: - cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, - [key, encoded, connection.ops.value_to_db_datetime(exp)]) + cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % table, + [key, encoded, connections[db].ops.value_to_db_datetime(exp)]) except DatabaseError: # To be threadsafe, updates/inserts are allowed to fail silently - transaction.rollback_unless_managed() + transaction.rollback_unless_managed(using=db) return False else: - transaction.commit_unless_managed() + transaction.commit_unless_managed(using=db) return True def delete(self, key): - cursor = connection.cursor() - cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key]) - transaction.commit_unless_managed() + db = router.db_for_write(self.cache_model_class) + table = connections[db].ops.quote_name(self._table) + cursor = connections[db].cursor() + + cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key]) + transaction.commit_unless_managed(using=db) def has_key(self, key): + db = router.db_for_read(self.cache_model_class) + table = connections[db].ops.quote_name(self._table) + cursor = connections[db].cursor() + now = datetime.now().replace(microsecond=0) - cursor = connection.cursor() - cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s and expires > %%s" % self._table, - [key, connection.ops.value_to_db_datetime(now)]) + cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s and expires > %%s" % table, + [key, connections[db].ops.value_to_db_datetime(now)]) return cursor.fetchone() is not None - def _cull(self, cursor, now): + def _cull(self, db, cursor, now): if self._cull_frequency == 0: self.clear() else: - cursor.execute("DELETE FROM %s WHERE expires < %%s" % self._table, - [connection.ops.value_to_db_datetime(now)]) - cursor.execute("SELECT COUNT(*) FROM %s" % self._table) + cursor.execute("DELETE FROM %s WHERE expires < %%s" % table, + [connections[db].ops.value_to_db_datetime(now)]) + cursor.execute("SELECT COUNT(*) FROM %s" % table) num = cursor.fetchone()[0] if num > self._max_entries: - cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % self._table, [num / self._cull_frequency]) - cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % self._table, [cursor.fetchone()[0]]) + cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % table, [num / self._cull_frequency]) + cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % table, [cursor.fetchone()[0]]) def clear(self): - cursor = connection.cursor() - cursor.execute('DELETE FROM %s' % self._table) + db = router.db_for_write(self.cache_model_class) + table = connections[db].ops.quote_name(self._table) + cursor = connections[db].cursor() + cursor.execute('DELETE FROM %s' % table) diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py index 492ac1e3e93c..be9b6fc91df5 100644 --- a/django/db/backends/creation.py +++ b/django/db/backends/creation.py @@ -353,9 +353,11 @@ def create_test_db(self, verbosity=1, autoclobber=False): call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias) if settings.CACHE_BACKEND.startswith('db://'): - from django.core.cache import parse_backend_uri - _, cache_name, _ = parse_backend_uri(settings.CACHE_BACKEND) - call_command('createcachetable', cache_name) + from django.core.cache import parse_backend_uri, cache + from django.db import router + if router.allow_syncdb(self.connection.alias, cache.cache_model_class): + _, cache_name, _ = parse_backend_uri(settings.CACHE_BACKEND) + call_command('createcachetable', cache_name, database=self.connection.alias) # Get a cursor (even though we don't need one yet). This has # the side effect of initializing the test database. diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 9dedbcf3b924..5e263aa543bc 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -136,6 +136,49 @@ settings file. You can't use a different database backend for your cache table. Database caching works best if you've got a fast, well-indexed database server. +Database caching and multiple databases +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you use database caching with multiple databases, you'll also need +to set up routing instructions for your database cache table. For the +purposes of routing, the database cache table appears as a model named +``CacheEntry``, in an application named ``django_cache``. This model +won't appear in the models cache, but the model details can be used +for routing purposes. + +For example, the following router would direct all cache read +operations to ``cache_slave``, and all write operations to +``cache_master``. The cache table will only be synchronized onto +``cache_master``:: + + class CacheRouter(object): + """A router to control all database cache operations""" + + def db_for_read(self, model, **hints): + "All cache read operations go to the slave" + if model._meta.app_label in ('django_cache',): + return 'cache_slave' + return None + + def db_for_write(self, model, **hints): + "All cache write operations go to master" + if model._meta.app_label in ('django_cache',): + return 'cache_master' + return None + + def allow_syncdb(self, db, model): + "Only synchronize the cache model on master" + if model._meta.app_label in ('django_cache',): + return db == 'cache_master' + return None + +If you don't specify routing directions for the database cache model, +the cache backend will use the ``default`` database. + +Of course, if you don't use the database cache backend, you don't need +to worry about providing routing instructions for the database cache +model. + Filesystem caching ------------------ From d302a60c8db81087c8c425cb6583c88055ad921b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 5 Aug 2010 04:00:56 +0000 Subject: [PATCH 028/902] [1.2.X] Fixed #13613 -- Ensure that forms.URLField and forms.EmailField are used on a modelform. This ensures that fields are URL and Email fields are cleaned consistently when included on model forms. Thanks to amadison for the report. Backport of r13475 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13476 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/__init__.py | 16 ++++++++ .../model_forms_regress/models.py | 3 ++ .../model_forms_regress/tests.py | 38 ++++++++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 65b60a01739e..4640eb323899 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -795,6 +795,14 @@ def __init__(self, *args, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 75) CharField.__init__(self, *args, **kwargs) + def formfield(self, **kwargs): + # As with CharField, this will cause email validation to be performed twice + defaults = { + 'form_class': forms.EmailField, + } + defaults.update(kwargs) + return super(EmailField, self).formfield(**defaults) + class FilePathField(Field): description = _("File path") @@ -1105,6 +1113,14 @@ def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): CharField.__init__(self, verbose_name, name, **kwargs) self.validators.append(validators.URLValidator(verify_exists=verify_exists)) + def formfield(self, **kwargs): + # As with CharField, this will cause URL validation to be performed twice + defaults = { + 'form_class': forms.URLField, + } + defaults.update(kwargs) + return super(URLField, self).formfield(**defaults) + class XMLField(TextField): description = _("XML text") diff --git a/tests/regressiontests/model_forms_regress/models.py b/tests/regressiontests/model_forms_regress/models.py index 871bb6f815bf..4f9811a963ab 100644 --- a/tests/regressiontests/model_forms_regress/models.py +++ b/tests/regressiontests/model_forms_regress/models.py @@ -54,3 +54,6 @@ class Author(models.Model): class Author1(models.Model): publication = models.OneToOneField(Publication, null=False) full_name = models.CharField(max_length=255) + +class Homepage(models.Model): + url = models.URLField(verify_exists=False) diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py index 5a7a83bc0e3d..2e77a8897763 100644 --- a/tests/regressiontests/model_forms_regress/tests.py +++ b/tests/regressiontests/model_forms_regress/tests.py @@ -6,7 +6,8 @@ from django.conf import settings from django.test import TestCase -from models import Person, RealPerson, Triple, FilePathModel, Article, Publication, CustomFF, Author, Author1 +from models import Person, RealPerson, Triple, FilePathModel, Article, \ + Publication, CustomFF, Author, Author1, Homepage class ModelMultipleChoiceFieldTests(TestCase): @@ -212,7 +213,40 @@ class TestTicket11183(TestCase): def test_11183(self): form1 = ModelChoiceForm() field1 = form1.fields['person'] - # To allow the widget to change the queryset of field1.widget.choices correctly, + # To allow the widget to change the queryset of field1.widget.choices correctly, # without affecting other forms, the following must hold: self.assert_(field1 is not ModelChoiceForm.base_fields['person']) self.assert_(field1.widget.choices.field is field1) + +class HomepageForm(forms.ModelForm): + class Meta: + model = Homepage + +class URLFieldTests(TestCase): + def test_url_on_modelform(self): + "Check basic URL field validation on model forms" + self.assertFalse(HomepageForm({'url': 'foo'}).is_valid()) + self.assertFalse(HomepageForm({'url': 'http://'}).is_valid()) + self.assertFalse(HomepageForm({'url': 'http://example'}).is_valid()) + self.assertFalse(HomepageForm({'url': 'http://example.'}).is_valid()) + self.assertFalse(HomepageForm({'url': 'http://com.'}).is_valid()) + + self.assertTrue(HomepageForm({'url': 'http://localhost'}).is_valid()) + self.assertTrue(HomepageForm({'url': 'http://example.com'}).is_valid()) + self.assertTrue(HomepageForm({'url': 'http://www.example.com'}).is_valid()) + self.assertTrue(HomepageForm({'url': 'http://www.example.com:8000'}).is_valid()) + self.assertTrue(HomepageForm({'url': 'http://www.example.com/test'}).is_valid()) + self.assertTrue(HomepageForm({'url': 'http://www.example.com:8000/test'}).is_valid()) + self.assertTrue(HomepageForm({'url': 'http://example.com/foo/bar'}).is_valid()) + + def test_http_prefixing(self): + "If the http:// prefix is omitted on form input, the field adds it again. (Refs #13613)" + form = HomepageForm({'url': 'example.com'}) + form.is_valid() + # self.assertTrue(form.is_valid()) + # self.assertEquals(form.cleaned_data['url'], 'http://example.com/') + + form = HomepageForm({'url': 'example.com/test'}) + form.is_valid() + # self.assertTrue(form.is_valid()) + # self.assertEquals(form.cleaned_data['url'], 'http://example.com/test') From 91d64a5a09f112ea4155bdb1d254124666025477 Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Thu, 5 Aug 2010 11:29:18 +0000 Subject: [PATCH 029/902] [1.2.X] Fixed #14062: Corrected a stray reference to 2.6 as the high level for supported Pythons. Thanks aesmail. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13481 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/faq/install.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq/install.txt b/docs/faq/install.txt index 0139a82a6723..f5feb98aff4a 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -19,7 +19,7 @@ What are Django's prerequisites? -------------------------------- Django requires Python_, specifically any version of Python from 2.4 -through 2.6. No other Python libraries are required for basic Django +through 2.7. No other Python libraries are required for basic Django usage. For a development environment -- if you just want to experiment with Django -- From aa59bc74044b1e04b2cdfe6e27657f4d95db82c1 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 5 Aug 2010 12:52:59 +0000 Subject: [PATCH 030/902] [1.2.X] Fixed #13732 -- Fixed minor typo in docstring. Thanks to schinckel for the report. Backport of r13483 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13485 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/forms/localflavor/au.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regressiontests/forms/localflavor/au.py b/tests/regressiontests/forms/localflavor/au.py index fd4c0d69804f..cda782094aec 100644 --- a/tests/regressiontests/forms/localflavor/au.py +++ b/tests/regressiontests/forms/localflavor/au.py @@ -50,7 +50,7 @@ ## AUPhoneNumberField ######################################################## A field that accepts a 10 digit Australian phone number. -llows spaces and parentheses around area code. +Allows spaces and parentheses around area code. >>> from django.contrib.localflavor.au.forms import AUPhoneNumberField >>> f = AUPhoneNumberField() From 908b91b69ebdc5947bdb8944b143835e1d7d2126 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 5 Aug 2010 12:53:27 +0000 Subject: [PATCH 031/902] [1.2.X] Fixed #13621 -- Corrected the handling of input formats on date/time form fields. Thanks to bufke for the report, zerok and jacmkno for their work on the patch, and Karen, Jannis and Alex for feedback. Backport of r13484 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13486 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/forms/widgets.py | 26 +- tests/regressiontests/forms/input_formats.py | 894 +++++++++++++++++++ tests/regressiontests/forms/tests.py | 2 + 3 files changed, 913 insertions(+), 9 deletions(-) create mode 100644 tests/regressiontests/forms/input_formats.py diff --git a/django/forms/widgets.py b/django/forms/widgets.py index e3799c69ad98..1480527d9d2a 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -308,9 +308,13 @@ def __init__(self, attrs=None, format=None): super(DateInput, self).__init__(attrs) if format: self.format = format + self.manual_format = True + else: + self.format = formats.get_format('DATE_INPUT_FORMATS')[0] + self.manual_format = False def _format_value(self, value): - if self.is_localized: + if self.is_localized and not self.manual_format: return formats.localize_input(value) elif hasattr(value, 'strftime'): value = datetime_safe.new_date(value) @@ -336,9 +340,13 @@ def __init__(self, attrs=None, format=None): super(DateTimeInput, self).__init__(attrs) if format: self.format = format + self.manual_format = True + else: + self.format = formats.get_format('DATETIME_INPUT_FORMATS')[0] + self.manual_format = False def _format_value(self, value): - if self.is_localized: + if self.is_localized and not self.manual_format: return formats.localize_input(value) elif hasattr(value, 'strftime'): value = datetime_safe.new_datetime(value) @@ -364,9 +372,13 @@ def __init__(self, attrs=None, format=None): super(TimeInput, self).__init__(attrs) if format: self.format = format + self.manual_format = True + else: + self.format = formats.get_format('TIME_INPUT_FORMATS')[0] + self.manual_format = False def _format_value(self, value): - if self.is_localized: + if self.is_localized and not self.manual_format: return formats.localize_input(value) elif hasattr(value, 'strftime'): return value.strftime(self.format) @@ -751,12 +763,8 @@ class SplitDateTimeWidget(MultiWidget): time_format = TimeInput.format def __init__(self, attrs=None, date_format=None, time_format=None): - if date_format: - self.date_format = date_format - if time_format: - self.time_format = time_format - widgets = (DateInput(attrs=attrs, format=self.date_format), - TimeInput(attrs=attrs, format=self.time_format)) + widgets = (DateInput(attrs=attrs, format=date_format), + TimeInput(attrs=attrs, format=time_format)) super(SplitDateTimeWidget, self).__init__(widgets, attrs) def decompress(self, value): diff --git a/tests/regressiontests/forms/input_formats.py b/tests/regressiontests/forms/input_formats.py new file mode 100644 index 000000000000..498c6de9fb85 --- /dev/null +++ b/tests/regressiontests/forms/input_formats.py @@ -0,0 +1,894 @@ +from datetime import time, date, datetime +from unittest import TestCase + +from django import forms +from django.conf import settings +from django.utils.translation import activate, deactivate + + +class LocalizedTimeTests(TestCase): + def setUp(self): + self.old_TIME_INPUT_FORMATS = settings.TIME_INPUT_FORMATS + self.old_USE_L10N = settings.USE_L10N + + settings.TIME_INPUT_FORMATS = ["%I:%M:%S %p", "%I:%M %p"] + settings.USE_L10N = True + + activate('de') + + def tearDown(self): + settings.TIME_INPUT_FORMATS = self.old_TIME_INPUT_FORMATS + settings.USE_L10N = self.old_USE_L10N + + deactivate() + + def test_timeField(self): + "TimeFields can parse dates in the default format" + f = forms.TimeField() + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM') + + # Parse a time in a valid format, get a parsed result + result = f.clean('13:30:05') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip + text = f.widget._format_value(result) + self.assertEqual(text, '13:30:05') + + # Parse a time in a valid, but non-default format, get a parsed result + result = f.clean('13:30') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:00") + + def test_localized_timeField(self): + "Localized TimeFields act as unlocalized widgets" + f = forms.TimeField(localize=True) + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM') + + # Parse a time in a valid format, get a parsed result + result = f.clean('13:30:05') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, '13:30:05') + + # Parse a time in a valid format, get a parsed result + result = f.clean('13:30') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:00") + + def test_timeField_with_inputformat(self): + "TimeFields with manually specified input formats can accept those formats" + f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"]) + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM') + self.assertRaises(forms.ValidationError, f.clean, '13:30:05') + + # Parse a time in a valid format, get a parsed result + result = f.clean('13.30.05') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:05") + + # Parse a time in a valid format, get a parsed result + result = f.clean('13.30') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:00") + + def test_localized_timeField_with_inputformat(self): + "Localized TimeFields with manually specified input formats can accept those formats" + f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"], localize=True) + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM') + self.assertRaises(forms.ValidationError, f.clean, '13:30:05') + + # Parse a time in a valid format, get a parsed result + result = f.clean('13.30.05') + self.assertEqual(result, time(13,30,5)) + + # # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:05") + + # Parse a time in a valid format, get a parsed result + result = f.clean('13.30') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:00") + + +class CustomTimeInputFormatsTests(TestCase): + def setUp(self): + self.old_TIME_INPUT_FORMATS = settings.TIME_INPUT_FORMATS + settings.TIME_INPUT_FORMATS = ["%I:%M:%S %p", "%I:%M %p"] + + def tearDown(self): + settings.TIME_INPUT_FORMATS = self.old_TIME_INPUT_FORMATS + + def test_timeField(self): + "TimeFields can parse dates in the default format" + f = forms.TimeField() + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '13:30:05') + + # Parse a time in a valid format, get a parsed result + result = f.clean('1:30:05 PM') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip + text = f.widget._format_value(result) + self.assertEqual(text, '01:30:05 PM') + + # Parse a time in a valid, but non-default format, get a parsed result + result = f.clean('1:30 PM') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:00 PM") + + def test_localized_timeField(self): + "Localized TimeFields act as unlocalized widgets" + f = forms.TimeField(localize=True) + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '13:30:05') + + # Parse a time in a valid format, get a parsed result + result = f.clean('1:30:05 PM') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, '01:30:05 PM') + + # Parse a time in a valid format, get a parsed result + result = f.clean('01:30 PM') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:00 PM") + + def test_timeField_with_inputformat(self): + "TimeFields with manually specified input formats can accept those formats" + f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"]) + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM') + self.assertRaises(forms.ValidationError, f.clean, '13:30:05') + + # Parse a time in a valid format, get a parsed result + result = f.clean('13.30.05') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:05 PM") + + # Parse a time in a valid format, get a parsed result + result = f.clean('13.30') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:00 PM") + + def test_localized_timeField_with_inputformat(self): + "Localized TimeFields with manually specified input formats can accept those formats" + f = forms.TimeField(input_formats=["%H.%M.%S", "%H.%M"], localize=True) + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM') + self.assertRaises(forms.ValidationError, f.clean, '13:30:05') + + # Parse a time in a valid format, get a parsed result + result = f.clean('13.30.05') + self.assertEqual(result, time(13,30,5)) + + # # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:05 PM") + + # Parse a time in a valid format, get a parsed result + result = f.clean('13.30') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:00 PM") + + +class SimpleTimeFormatTests(TestCase): + def test_timeField(self): + "TimeFields can parse dates in the default format" + f = forms.TimeField() + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM') + + # Parse a time in a valid format, get a parsed result + result = f.clean('13:30:05') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:05") + + # Parse a time in a valid, but non-default format, get a parsed result + result = f.clean('13:30') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:00") + + def test_localized_timeField(self): + "Localized TimeFields in a non-localized environment act as unlocalized widgets" + f = forms.TimeField() + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM') + + # Parse a time in a valid format, get a parsed result + result = f.clean('13:30:05') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:05") + + # Parse a time in a valid format, get a parsed result + result = f.clean('13:30') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:00") + + def test_timeField_with_inputformat(self): + "TimeFields with manually specified input formats can accept those formats" + f = forms.TimeField(input_formats=["%I:%M:%S %p", "%I:%M %p"]) + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '13:30:05') + + # Parse a time in a valid format, get a parsed result + result = f.clean('1:30:05 PM') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:05") + + # Parse a time in a valid format, get a parsed result + result = f.clean('1:30 PM') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:00") + + def test_localized_timeField_with_inputformat(self): + "Localized TimeFields with manually specified input formats can accept those formats" + f = forms.TimeField(input_formats=["%I:%M:%S %p", "%I:%M %p"], localize=True) + # Parse a time in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '13:30:05') + + # Parse a time in a valid format, get a parsed result + result = f.clean('1:30:05 PM') + self.assertEqual(result, time(13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:05") + + # Parse a time in a valid format, get a parsed result + result = f.clean('1:30 PM') + self.assertEqual(result, time(13,30,0)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "13:30:00") + + +class LocalizedDateTests(TestCase): + def setUp(self): + self.old_DATE_INPUT_FORMATS = settings.DATE_INPUT_FORMATS + self.old_USE_L10N = settings.USE_L10N + + settings.DATE_INPUT_FORMATS = ["%d/%m/%Y", "%d-%m-%Y"] + settings.USE_L10N = True + + activate('de') + + def tearDown(self): + settings.DATE_INPUT_FORMATS = self.old_DATE_INPUT_FORMATS + settings.USE_L10N = self.old_USE_L10N + + deactivate() + + def test_dateField(self): + "DateFields can parse dates in the default format" + f = forms.DateField() + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '21/12/2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip + text = f.widget._format_value(result) + self.assertEqual(text, '21.12.2010') + + # Parse a date in a valid, but non-default format, get a parsed result + result = f.clean('21.12.10') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + def test_localized_dateField(self): + "Localized DateFields act as unlocalized widgets" + f = forms.DateField(localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '21/12/2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, '21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.10') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + def test_dateField_with_inputformat(self): + "DateFields with manually specified input formats can accept those formats" + f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"]) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21') + self.assertRaises(forms.ValidationError, f.clean, '21/12/2010') + self.assertRaises(forms.ValidationError, f.clean, '21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('12.21.2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + # Parse a date in a valid format, get a parsed result + result = f.clean('12-21-2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + def test_localized_dateField_with_inputformat(self): + "Localized DateFields with manually specified input formats can accept those formats" + f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"], localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21') + self.assertRaises(forms.ValidationError, f.clean, '21/12/2010') + self.assertRaises(forms.ValidationError, f.clean, '21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('12.21.2010') + self.assertEqual(result, date(2010,12,21)) + + # # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + # Parse a date in a valid format, get a parsed result + result = f.clean('12-21-2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + +class CustomDateInputFormatsTests(TestCase): + def setUp(self): + self.old_DATE_INPUT_FORMATS = settings.DATE_INPUT_FORMATS + settings.DATE_INPUT_FORMATS = ["%d.%m.%Y", "%d-%m-%Y"] + + def tearDown(self): + settings.DATE_INPUT_FORMATS = self.old_DATE_INPUT_FORMATS + + def test_dateField(self): + "DateFields can parse dates in the default format" + f = forms.DateField() + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip + text = f.widget._format_value(result) + self.assertEqual(text, '21.12.2010') + + # Parse a date in a valid, but non-default format, get a parsed result + result = f.clean('21-12-2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + def test_localized_dateField(self): + "Localized DateFields act as unlocalized widgets" + f = forms.DateField(localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, '21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21-12-2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + def test_dateField_with_inputformat(self): + "DateFields with manually specified input formats can accept those formats" + f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"]) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '21.12.2010') + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21') + + # Parse a date in a valid format, get a parsed result + result = f.clean('12.21.2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + # Parse a date in a valid format, get a parsed result + result = f.clean('12-21-2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + def test_localized_dateField_with_inputformat(self): + "Localized DateFields with manually specified input formats can accept those formats" + f = forms.DateField(input_formats=["%m.%d.%Y", "%m-%d-%Y"], localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '21.12.2010') + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21') + + # Parse a date in a valid format, get a parsed result + result = f.clean('12.21.2010') + self.assertEqual(result, date(2010,12,21)) + + # # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + + # Parse a date in a valid format, get a parsed result + result = f.clean('12-21-2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010") + +class SimpleDateFormatTests(TestCase): + def test_dateField(self): + "DateFields can parse dates in the default format" + f = forms.DateField() + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('2010-12-21') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21") + + # Parse a date in a valid, but non-default format, get a parsed result + result = f.clean('12/21/2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21") + + def test_localized_dateField(self): + "Localized DateFields in a non-localized environment act as unlocalized widgets" + f = forms.DateField() + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('2010-12-21') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21") + + # Parse a date in a valid format, get a parsed result + result = f.clean('12/21/2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21") + + def test_dateField_with_inputformat(self): + "DateFields with manually specified input formats can accept those formats" + f = forms.DateField(input_formats=["%d.%m.%Y", "%d-%m-%Y"]) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21") + + # Parse a date in a valid format, get a parsed result + result = f.clean('21-12-2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21") + + def test_localized_dateField_with_inputformat(self): + "Localized DateFields with manually specified input formats can accept those formats" + f = forms.DateField(input_formats=["%d.%m.%Y", "%d-%m-%Y"], localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21") + + # Parse a date in a valid format, get a parsed result + result = f.clean('21-12-2010') + self.assertEqual(result, date(2010,12,21)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21") + +class LocalizedDateTimeTests(TestCase): + def setUp(self): + self.old_DATETIME_INPUT_FORMATS = settings.DATETIME_INPUT_FORMATS + self.old_USE_L10N = settings.USE_L10N + + settings.DATETIME_INPUT_FORMATS = ["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"] + settings.USE_L10N = True + + activate('de') + + def tearDown(self): + settings.DATETIME_INPUT_FORMATS = self.old_DATETIME_INPUT_FORMATS + settings.USE_L10N = self.old_USE_L10N + + deactivate() + + def test_dateTimeField(self): + "DateTimeFields can parse dates in the default format" + f = forms.DateTimeField() + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.2010 13:30:05') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip + text = f.widget._format_value(result) + self.assertEqual(text, '21.12.2010 13:30:05') + + # Parse a date in a valid, but non-default format, get a parsed result + result = f.clean('21.12.2010 13:30') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010 13:30:00") + + def test_localized_dateTimeField(self): + "Localized DateTimeFields act as unlocalized widgets" + f = forms.DateTimeField(localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.2010 13:30:05') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, '21.12.2010 13:30:05') + + # Parse a date in a valid format, get a parsed result + result = f.clean('21.12.2010 13:30') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010 13:30:00") + + def test_dateTimeField_with_inputformat(self): + "DateTimeFields with manually specified input formats can accept those formats" + f = forms.DateTimeField(input_formats=["%H.%M.%S %m.%d.%Y", "%H.%M %m-%d-%Y"]) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05 13:30:05') + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010') + self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('13.30.05 12.21.2010') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010 13:30:05") + + # Parse a date in a valid format, get a parsed result + result = f.clean('13.30 12-21-2010') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010 13:30:00") + + def test_localized_dateTimeField_with_inputformat(self): + "Localized DateTimeFields with manually specified input formats can accept those formats" + f = forms.DateTimeField(input_formats=["%H.%M.%S %m.%d.%Y", "%H.%M %m-%d-%Y"], localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05') + self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010') + self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('13.30.05 12.21.2010') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010 13:30:05") + + # Parse a date in a valid format, get a parsed result + result = f.clean('13.30 12-21-2010') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "21.12.2010 13:30:00") + + +class CustomDateTimeInputFormatsTests(TestCase): + def setUp(self): + self.old_DATETIME_INPUT_FORMATS = settings.DATETIME_INPUT_FORMATS + settings.DATETIME_INPUT_FORMATS = ["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"] + + def tearDown(self): + settings.DATETIME_INPUT_FORMATS = self.old_DATETIME_INPUT_FORMATS + + def test_dateTimeField(self): + "DateTimeFields can parse dates in the default format" + f = forms.DateTimeField() + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05') + + # Parse a date in a valid format, get a parsed result + result = f.clean('1:30:05 PM 21/12/2010') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip + text = f.widget._format_value(result) + self.assertEqual(text, '01:30:05 PM 21/12/2010') + + # Parse a date in a valid, but non-default format, get a parsed result + result = f.clean('1:30 PM 21-12-2010') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:00 PM 21/12/2010") + + def test_localized_dateTimeField(self): + "Localized DateTimeFields act as unlocalized widgets" + f = forms.DateTimeField(localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05') + + # Parse a date in a valid format, get a parsed result + result = f.clean('1:30:05 PM 21/12/2010') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, '01:30:05 PM 21/12/2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('1:30 PM 21-12-2010') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:00 PM 21/12/2010") + + def test_dateTimeField_with_inputformat(self): + "DateTimeFields with manually specified input formats can accept those formats" + f = forms.DateTimeField(input_formats=["%m.%d.%Y %H:%M:%S", "%m-%d-%Y %H:%M"]) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010') + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05') + + # Parse a date in a valid format, get a parsed result + result = f.clean('12.21.2010 13:30:05') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:05 PM 21/12/2010") + + # Parse a date in a valid format, get a parsed result + result = f.clean('12-21-2010 13:30') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:00 PM 21/12/2010") + + def test_localized_dateTimeField_with_inputformat(self): + "Localized DateTimeFields with manually specified input formats can accept those formats" + f = forms.DateTimeField(input_formats=["%m.%d.%Y %H:%M:%S", "%m-%d-%Y %H:%M"], localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010') + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05') + + # Parse a date in a valid format, get a parsed result + result = f.clean('12.21.2010 13:30:05') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:05 PM 21/12/2010") + + # Parse a date in a valid format, get a parsed result + result = f.clean('12-21-2010 13:30') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "01:30:00 PM 21/12/2010") + +class SimpleDateTimeFormatTests(TestCase): + def test_dateTimeField(self): + "DateTimeFields can parse dates in the default format" + f = forms.DateTimeField() + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('2010-12-21 13:30:05') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21 13:30:05") + + # Parse a date in a valid, but non-default format, get a parsed result + result = f.clean('12/21/2010 13:30:05') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21 13:30:05") + + def test_localized_dateTimeField(self): + "Localized DateTimeFields in a non-localized environment act as unlocalized widgets" + f = forms.DateTimeField() + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '13:30:05 21.12.2010') + + # Parse a date in a valid format, get a parsed result + result = f.clean('2010-12-21 13:30:05') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21 13:30:05") + + # Parse a date in a valid format, get a parsed result + result = f.clean('12/21/2010 13:30:05') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21 13:30:05") + + def test_dateTimeField_with_inputformat(self): + "DateTimeFields with manually specified input formats can accept those formats" + f = forms.DateTimeField(input_formats=["%I:%M:%S %p %d.%m.%Y", "%I:%M %p %d-%m-%Y"]) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05') + + # Parse a date in a valid format, get a parsed result + result = f.clean('1:30:05 PM 21.12.2010') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21 13:30:05") + + # Parse a date in a valid format, get a parsed result + result = f.clean('1:30 PM 21-12-2010') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21 13:30:00") + + def test_localized_dateTimeField_with_inputformat(self): + "Localized DateTimeFields with manually specified input formats can accept those formats" + f = forms.DateTimeField(input_formats=["%I:%M:%S %p %d.%m.%Y", "%I:%M %p %d-%m-%Y"], localize=True) + # Parse a date in an unaccepted format; get an error + self.assertRaises(forms.ValidationError, f.clean, '2010-12-21 13:30:05') + + # Parse a date in a valid format, get a parsed result + result = f.clean('1:30:05 PM 21.12.2010') + self.assertEqual(result, datetime(2010,12,21,13,30,5)) + + # Check that the parsed result does a round trip to the same format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21 13:30:05") + + # Parse a date in a valid format, get a parsed result + result = f.clean('1:30 PM 21-12-2010') + self.assertEqual(result, datetime(2010,12,21,13,30)) + + # Check that the parsed result does a round trip to default format + text = f.widget._format_value(result) + self.assertEqual(text, "2010-12-21 13:30:00") diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 8757e799a9bb..7a91cb701e2c 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -41,6 +41,8 @@ from validators import TestFieldWithValidators from widgets import WidgetTests +from input_formats import * + __test__ = { 'extra_tests': extra_tests, 'form_tests': form_tests, From e10e3007d19ac1feba4d8c124f60446ecb1ace2c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 5 Aug 2010 13:01:10 +0000 Subject: [PATCH 032/902] [1.2.X] Fixed #13153 -- Removed a stale reference to the examples directory in setup.cfg. Thanks to cesar@mifprojects.com for the report. Backport of r13487 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13488 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 99bc60c9a33d..37cc9a592f20 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ [bdist_rpm] -doc_files = docs examples extras AUTHORS INSTALL LICENSE README +doc_files = docs extras AUTHORS INSTALL LICENSE README install-script = scripts/rpm-install.sh From 9ff58e30c1aa0ba1888888fcc1c4fd8b7e4254ad Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 5 Aug 2010 13:19:54 +0000 Subject: [PATCH 033/902] =?UTF-8?q?[1.2.X]=20Fixed=20#13610=20--=20Improve?= =?UTF-8?q?d=20error=20reporting=20when=20the=20ENGINE=20setting=20is=20om?= =?UTF-8?q?mitted=20from=20a=20database=20configuration.=20Thanks=20to=20G?= =?UTF-8?q?regor=20M=C3=BCllegger=20for=20the=20report=20and=20patch.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport of r13489 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13490 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/core/management/__init__.py | 10 +++++----- django/db/__init__.py | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 4921f7c3ad85..0a7f9db204a0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -336,6 +336,7 @@ answer newbie questions, and generally made Django that much better: Aljosa Mohorovic Ramiro Morales Eric Moritz + Gregor Müllegger Robin Munn James Murty msundstr diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 32e744374a7f..85bf324fdcf8 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -250,15 +250,15 @@ def fetch_command(self, subcommand): """ try: app_name = get_commands()[subcommand] - if isinstance(app_name, BaseCommand): - # If the command is already loaded, use it directly. - klass = app_name - else: - klass = load_command_class(app_name, subcommand) except KeyError: sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \ (subcommand, self.prog_name)) sys.exit(1) + if isinstance(app_name, BaseCommand): + # If the command is already loaded, use it directly. + klass = app_name + else: + klass = load_command_class(app_name, subcommand) return klass def autocomplete(self): diff --git a/django/db/__init__.py b/django/db/__init__.py index 4bae04ab9a2f..4c4faef694be 100644 --- a/django/db/__init__.py +++ b/django/db/__init__.py @@ -35,6 +35,8 @@ raise ImproperlyConfigured("You must default a '%s' database" % DEFAULT_DB_ALIAS) for alias, database in settings.DATABASES.items(): + if 'ENGINE' not in database: + raise ImproperlyConfigured("You must specify a 'ENGINE' for database '%s'" % alias) if database['ENGINE'] in ("postgresql", "postgresql_psycopg2", "sqlite3", "mysql", "oracle"): import warnings if 'django.contrib.gis' in settings.INSTALLED_APPS: From 6d76cae2bc7640ded4e9431af269eec6d97d84a5 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 02:13:20 +0000 Subject: [PATCH 034/902] [1.2.X] Fixed #13651 -- Added Malayalam (ml) translation. Thanks to rajeesh. Backport of r13491 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13493 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/global_settings.py | 1 + django/conf/locale/ml/LC_MESSAGES/django.mo | Bin 0 -> 64084 bytes django/conf/locale/ml/LC_MESSAGES/django.po | 5044 +++++++++++++++++ django/conf/locale/ml/LC_MESSAGES/djangojs.mo | Bin 0 -> 4350 bytes django/conf/locale/ml/LC_MESSAGES/djangojs.po | 156 + django/conf/locale/ml/__init__.py | 0 django/conf/locale/ml/formats.py | 38 + 7 files changed, 5239 insertions(+) create mode 100644 django/conf/locale/ml/LC_MESSAGES/django.mo create mode 100644 django/conf/locale/ml/LC_MESSAGES/django.po create mode 100644 django/conf/locale/ml/LC_MESSAGES/djangojs.mo create mode 100644 django/conf/locale/ml/LC_MESSAGES/djangojs.po create mode 100644 django/conf/locale/ml/__init__.py create mode 100644 django/conf/locale/ml/formats.py diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index cdff2ff13f02..4dc65b8cf4fc 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -78,6 +78,7 @@ ('lt', gettext_noop('Lithuanian')), ('lv', gettext_noop('Latvian')), ('mk', gettext_noop('Macedonian')), + ('ml', gettext_noop('Malayalam')), ('mn', gettext_noop('Mongolian')), ('nl', gettext_noop('Dutch')), ('no', gettext_noop('Norwegian')), diff --git a/django/conf/locale/ml/LC_MESSAGES/django.mo b/django/conf/locale/ml/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..654b4fc4ee2bb44b4997dc086ba6dc2ce507db78 GIT binary patch literal 64084 zcmeIb3!Ge4mG6JV2O2;SK^{6-WQa*5-5o#_nh*$tKp=sb1P~BIb$4}lL3dYERh^`r zQ9}@nJOZK-0U<_tjS!=d7Vw4d*Xa0uf*PH1)ERX~o$HKVb?*1K*52ows;+(n@qh3C zb8mfe>UYjQd#}CrdhNCMIbHew{!iPI@cG$OlH>(o&%Q}={T@kj|6wXkl3jC>*JIJh6z zzXP8N&S+1PXMxWKp9vle>i*%N=p7A;zfSNFa2fb?uoqOn1K?}G8h9=ED{vq1H62Ov zT<}`(7;r22H1KPn+I>6V4?y+v&)@;zFTuURr_N6jsbD|w2(SZGz185p;44A#JrK&* zf};D{P<|D-Ki4+|d?%=OW1#r@6exP%0Z~ozLs0VC=cpv9gNJ~szZv9TatB`rgI@;K z?q}e0z+Z*yrylL{+Y6L@4gkf^q2RN@!@>Q)`Jnn=1d9K2LDgRaNVS^z&?( zJPSMs90a>T>F+C`?tc>$-5-F`^M8Pn&!50^!DldN)qf@UJg^8#FY7_^aSb>Gz5|pV z_C3-4^?9JK4+TZp}7LVNiU3 z9Gn4uAzXh8Je}(wf@gqpQJVBp1z!Vh1XVvd$?5I|%I=>FijN~g`HR8xxjq_Hf7gWa z8$gZc=1~3)PE}bB^!P>aQ1E-;Vc>s(vd4o?^>%Hb{Bi;Kd~h`=J0Au$uB$@% zR#0;JASii!D&SW@@%KGY~bgs99d+I(YIerk7zkL*hMDkT|Pw?O~d^`nE<2e>A zfaic3?=UF&T?b14w}F!1J3+;n2SL&M0;u?~|5ESgRiNZH3_cay1U?hI4irCIK*{k| zQ2p)zCAZIk>gOBav%tqe*~veH>gV^MdzXJ~k=bYo$XM!VK zkAg1%{|%f4?v0UL4IT~Z`Vmla{sDL{_&ZSZ(K&o^nrs6luU~`W>)9(^e{;bZTrUD& z0WJe&k9UDfz(0aVfQwgo`!Xm$x(XD(cY<@kuYs_h`~j4H&RXsAJQtKcE&wIZE>L>x z3pfZ$?ln;QSQoBe2Vx4zm7)BL;J#dc1H1tIF(`gdVz8q7Hck9)!QX@8<24Lk?XLhu?^;m&z6I3%Tfu$6J3!IDAN(uu+o1IG zTkuryxiF`G&IeBb$3Q}0@+J>q9{C{UT8JUlQ)081A1I?ym$T&r3k@Qv#n0UJk0= zRiOCT42teepz3V})&G5<c>XSw zkAZ6cXuwZ^;{P+C;DO-# zzz*;;py>W5sDAgvm^JRbK=JupQ1lC+`ke<#AIE|k-wEORWYG8q#mAYT+Ajz911|(s z|1wZ?hC%T+3ab7a!1>@cpy+=v;Ku@f22{H*g6ij+py+)U6#XB7lE=S;2Y~+*6yN*3 z%G4_#fa^V7(t-0)G!m{kTHC^{biRsV6Y4jww>`N9q0-dx`cz7#o)fnDIwD?U%1bGgqeBj66|eF#*4169Y@ zfd_MaQ@Fkld>Pjt0XxCpf~SE;)g0G>hjIO3Q1u=Mj{pyVspo^ogR<*$LD9VooCm%h zJOg|mcs%%fZ~^#|5zkXE2T$U9Q@H*tIFE7s06dfOy;0u7;Q1gflI*wM`x^vL;rfl? z`U{}=`6(#5>@(`)JQO^R>o!pR_k!)HRMc`|L1G*ZSdbQx;-g>c!T%%(JS08z6ZXP`-g1w zb;JsA71y5xWse76>3PN+P;xv4Yy&GGrjXnYqJm_v*Zcj~f#Umq@QvW7z=yzdF!pP} zJ+H=(z?;CYgRgl*lKcW(xyjenC(}s%zZqN({sferA4_9QI=Ky0|9f7WB>$~)P-`pRM0x|&uKi}Oe|o?RLHWZVsQ#`4=YaQuhl5`M)$TU|_kNq(=PXeD zo*D3B@IbDIK}3;U9m)^9#p%xhb^mnm2=HR?ad0E}YjDl2ZqF-kBQC@572u00zv}J2 zAMsvrF4x}%WmkLL?)>c!ev<2#fHT0~f#U0FcldY?0{7MXuit@tfX`x5nFa0* z$}W!vWzXk=$AUM4F9$yjUI-ricfLNWfh)NFAlM7;{cc~c)WCDO{uy`)c+!3BDS__* zMgM8{J0G(_@xK~89ISz-f?L3c!0&*VQu3zl%;GNy#C)#r1rMg)!Jo$G!J|O=%bNn; z2_DAvL!jt>GvISRIn~_4(klU=P=~qr4gPx5ww*j`sO`bWi!^ z;1cj@U+_467I-4p*Mcj-&w;xCf-fQy+INE|asAzYfF^k8m&lnZe;B-6*I)5<>~$a} zl{^MMgZtNgl|2fs-~2V77he5!$DQB;%6|#&2Of!09zgvC;K#UrIl*Z&_}p*#JoV0i zUj`+QKY<&;*r2 z!H+YDPf-4SlyC?5&Y$=^z6fRpx&8*Y0X+3*&gUn=nOy$@lzjI47w^9SK7;F*f~SHf zg0kN$K*{-5@JMhwcnJ6%@cH0xLixV`>Uui?d@GeB6jpHQn zB5)80^4qV1;$y#GyZs*v%I=nd zYBvH(e;Yxye`6@$3W~q`!Nb6Z!2Q8*f*SwNK+)OvH}0p$f#QEDD7qJbF9&-;>EU5e z?H>b0@0XzZ-3Ox=zXyOX0AC8K{wi=jxE7RM-2--m{|cT3p7TFkZ?}P>y8~3akAt$a zAA^$5e}nRm=l|C0%>vc`aiGf22gOGb)VTV=!8Q`+tIh`%w zQCzoyB9tA< z`BIKIb7;QT=M4^)2mOSyFLGSL@wXgzap-f4L$WAbEB43FIiai+uoCcmDxA!5IoFC+ z&7Tt}Kab`_%aE z4fp>EyoBp@cE|UwhB@D#@{fc%<$#^w$GA6>kGi`pgu3*d^Y%a zsH3tUa6XIU^&Dr0^5ek&b@eUdi>B!M_9J=RQs@(1Zi%3sg<9=Y~Q!ud17XL9dfLfz}YmxOc8)z9WQiaHBJ{r7?Y zNZBViuI4z9<1UV$QGO$M0!NkORUBt=#LqDI=Wv|F@mY?iaqP*>nP4$gdImTrobLg4 zaZjI*bF8&9=CDxK4c-vSe#H6X;rxx9S2_NfL!Vp0cXE_C-vRCeuHg6?$G>rWn6kMX z@$zn{G(id2)u^lB#w<-zYBZ}{0;akj%RWH5XYUI ze~Du?$2DAU;CPht_&Jp8uW@{tqdgS9HsISSTfuPz$G#l5ajfLtVc<@VFLJ(#qsF;D zFXT9#_#S2=k;$9WvD$(5fU&W`~P;@+LPdtc}J@44;(zsJ$d z@j9*#=NRN%pX)i^W@q?3`08-}o`CNFZ{>J4ZDw;U=E#5c<>CsClPUix$BV-KZ&LRD zaIX4a;r#Ex2UIp=!Em8_pjfM&IJ3VrFg$bN>Pn$JRI3*U1`1u3%4LJa>SY6^dTm~z zr&{bQ)cZ??f%4F0g?gnhS{bRPb!s!}mC8U@u{y6%sTQi`zW#b!_dvOunz2c7sHafu z=_!=!<6E~E&aSKz#X@&+Xhy9xQ0k@@Ept2+daISeLUCxcFkI{_!KGEGmFuPU0?q3s zu8Z|Ttuk0DRC;x@R+^D(Un^8gmyeXICF_R)bd?Hg%cXVYAs?40g+2=1mBHbWdZ}9I zDb>n-L&bWjXGXDBm{}g8aHu#ivoHq{Wn!p}*6Jmg>lrK$m235Cv5pkVz1CB)S{j-S zUnBHf?ww&`OX-*MMCZ0I=onr&Bbhm?HnYH;p-R0_EUYaKlza3&0(+1x7KW>3^fFqw ztTfu5%$n0(85ye2trcd?87dBzI9Ug)-TlR(zETg}c6XO*wce3|fe9*?;A^7Vz2(wC z&zeAXaHLk3&_;&Jmj_;E&FMilYpj}aFo4aB4A13#&slTS=bCD%HZo9O6M3vxkQ?^~ zD?O!wNO)bj-jBW*F^#Opfnrx_0D{GVYN^;WS}3hYszx;MQt7%>CSomf;(!1`nTy^k zC6}MI6gN_D>=5w}mWJvzijAR^J-574PqGO6pYZ0aIi`)d?ML+%y2=9s<;oD0CNCRo zQLj~5z1pH;R)|P48?%0E*;tFi!=++X-i4Nmg^}vOaIr3fEx5Rn`Em>l#>^!h>rpay z$-@jMZ!pv?cGu~~6&jTo#aCD-+nKPzQ8=8Gk8G@EsftFusFp0~KW5=6qlHahQ??xe`n}tI?j} zy;SQi4wrh?q+>X#ARG2TNZc40s92<{mIf5fh{B~oA_c}KV{zI&2yq&{rvqqa!~3pH z&~vy;1n*IBE;CI`pO=&1tPc|{50#2Tg;m2v!fb!C2zTg0%JT|cBXv|&8mfu3`$#%X z6bnP8bqsZ=r?L*sj`Z0V!7MqYSR;}pr<8{J2(rm3<-Wq4;|g7)_0nACmwKrW8#*O7 z3+iG(49q!=kcFYHt`Z1|$xDg@wbI;{x99my)jGQ}WR+?|<>F9sO0`^P5QWo*`UW(Z zQ$_~*idEe?wMdW@S_}^$+xCJ2k&^jkv@=&Us9CXATUV*}#99+w=bCkfx^e?}&?KT6 zW&)q^-sNQNv@6-%PR#DAtk+a0xyLv{X?cifRIOmym|J^cQDNq41^8H_pDC)VR2sqt z22o(o%)%hEl;-u+B+Iqof#Rr4m3)L*fKHKpHTaKW->H^hpa-V@N+>rzQd8@$R%)6> z2Fw@`Ac3yfRx1q`nWBxfDcNseUQ`(BEmsFqVIrXlhM+suO0jO*d3C9~KUwS+xENJ+ z6>FtJsama6DaAwQc=#$a7v~BpSZF;h>(Hs)?MxPzy4&?t8tlT}IAL}lC=88+OY{0c z;%Iqz0AsU>uSPX1&-r3Lysa4?7^xPSK|{Th0!|q3j9kown*DnA`0z5ju&y#P&=bPB zk}(S*q7c10aO%EkSx>Rs<^h|qloIt936R63>R`E6L!V7SE{j?9(P3tDLRlawkumy- z*!|zkSx6WaiI~7Ft+7c>CU2IQ4p{NG-Y>%_tslmg%k_cL0$xnU8OAm!!$=9P>n~Z< zk*gU=lI6lcrBAV{;yqb*P_U%9bxVBEn%3wS{So`?q*(F=vB!+M7H>Uw$nDB$2LF=+ zQY8mf5(ySxN}^e5PD?_m<xC>o3Ci`$YiLbKUq9d$M;Ta z8!R$mI?!_uNky&cvNJiYM=K2l9XFf8obHh-xwx`MO$BpX)W{|Z?y8fFdWh1e4cWvT zbgR%qe$V`07_2A^5wVGn$jP0U2uAkZo=meXO zkVS8KpkxV^nPVNcN$SbRi|gn1XcQqYZI;!o8=|LAZ(*;X^L zr7PCHB)UxVy>nOc*MX52?^`Lf=8g;av@KuWws^4yHdr)Mcc!z7ts2`j+C<@uGdhQ5-L2`<6Z@JrI zHocK{TIzb*`r;tTNM~Vo2bD+a9avADz|k?gy|BD^nbw2~U=^R?2@XB7D@**V1XW$s z*mPot2BImgUREZG^0i2Ajjr1J>w^Q(U8)olZz|W6I%ttqX8ktL#1gh&EOo>M3bQj( zJ*D0vxl^E$EGc!h>#I7#21>F-n+@2r#q5|_Eg?c84_+N!QXaBp#*%WiR-ad3>M#e5 zPG$p&f@?3V#v$fu$7i;$sfu0DK?WtwLpD3u#2Xfra4}Hqv#%B_Lf!M>#?|AJ0i~Y^ zZdg-6%np}Ss(lq)bx8eIQ!SoUa6hxcMq_4nVI?@vcwbi%tKq(8`Ra%*seKhtBL*z}t?H`mot z=`y}6Bg3`i43tq?mz=?zT;|(WHpv+kaueq0p~6h%-_^>%OxsxGiUfX$&2-K;$6uRF=Ciph^9jV#K1hqQ|Avo`!rTFX|- zQLEZHG}|*96&-{fO=M^kQ$<(iwb-m}YPpKy0w^wXa@kjHxHyss?CRxl*>K)s`NtMClf7p_cP0( zk3xT`1HaVnV|l2Z-g^+GX?Q7>wH{tNl!atlN-o6&`je$(8dC96+gFHNh3WDHNw74< zedrA)6vr&vWY&=?8P=fofWx+gEevdVNJgS<%=YBWBJA*GetTw-4FI-xlQTyKM)?{X zW`b1)+_3al0JC_)4Knd+X_(0iC&qAUXeV7vLDwiqfsK32fo4yOjMVpzoiZB}C9Nkz z%I_9&=EzVfIjcA{RO~4xXEARgoMoz1w-^i`9(eK_ld7;*Qe4KmV1yN>PA$I=P$>2l z%U(v#EISV6uF);9G#THlnXWaV_(r#Lwkg-B|0p0q1m+3-MLa*1$Og#W>$2=g6Is70Z(qs3x}o zxgu@@#goDuQ`cN7`UX+h?Qg!$ZR9rJYJQB6QOXN8EtQ+qGa7a&qbFrx%e1jtqi&k+ z*~Hx3Alq+lzB9twEI#c>?{(^|UU(VtbhMVN$O53wEwTwKJk-G`dn-wB1~T1hdH~OJ+A4#EsupeH(uYg{)4((-^s1 z`m^LzD^YTVmQ`)L%kQ@us@)FlC%iuu`R4v({uNa-K^~Sp3hd%3Ky+iR(z}UrZ2qy( zv2BjXnA7!HjpszAf!?{b!qUYA#??sCM1k}Sd9q|Pqg74?GYtY-fkp~qrR=k46A6pl z`E0&WUsvuXov!exMUO^3As@9|L*c}prlQ;+5Mo*j zA5x7awK5`YKd^VkLtP^A3T4K8`%JNdjSVhveH%^8qg8^~SPA)R8C8=TxO2>{&6uHQ zRrWAU7yb46aA!vccPnfL@f3_URqd*-sFsufMr`b2Wyvx!)*y{J{7Ff;GpHq!O{*~* z93JT+!lV^^Q@o*y?sa2m>6Ew9HqI&4{y8Fy0}KUe<`v3o)U>DHJ?({K=I3{Mh-SSd zp5(O`mc>d$zJrZ?8^~mPOweMUDC8eqtQ_%mZpyuO zB$@VC73(@is>%&kv2AQ$Mw#pw^yhuL`D&%J-pB5- z!3v=Hp|)jcBLnGE(N$!I;(M4pUOC6AQk6}B!fATEQCLtKTsY_0`Sa&4=-@2g^G6?{ zqE8G~DOtrz$Y}U61v|}^RcQt%@VmpJZ8O6J~ zb=V#DRKa`ELvI!V+72M>n5ATo#%!gAz{3;GRcQjF{TqA6qh~h$+*Xg{SB(tm zkVC94t*<8-1@Gk$>ny@I#5}v@i+UT@T6sjSFocMvQpK;nrZNjmO=UG6yIOv0Z{BEY zl$pgIb-Bzn;PXpe_D})`gW>jk=k?-c5Hzva#u_eI9<*oZRu<{$;X}FBJ>qen(69Xt zJzmJu8qvBgy>qy3@oc4GGq3UC6Ei9kV}+dyO$xSX4o^0CWXUuwamEMnvxzO$QXe}M z?E3ed1aYNU;e8Z;dw^F5YV*sFd9cWvFWNogpXKl|W156kSTIk& z^JrwPxXnLOWTLe9irnH`(r-~cP>;iJQDbCS8y&5r6>}D(u6e@VI}-~bcbdnd5Nqhd zQDSZoo>|1VhRj7#DpOt|

@ZOiS>_l*4;)Cex*zJt|RrEe#IWg97X8*uIF%sIWln zDfehcaW2b0Rml0UIXt_TWtN-l)UDQ{ogH_@F{V4)9)H9Pbd za~`P@4Tfw?)JF&v%-dRNMQ3F6?1cDaQL+beu&Hp`rd_%}m!drgYeCTW>li+Hg(T3$ zZebKx^SvQP9r*BDMlYmVF2_M;xqd$llb~KtOP_#~yV)*Re6K^+q7CjYri@WJooaeW zkzrWkn19Y|y3TB*Sx;l)y~Si#&>iktKRT3 z&-jXyj=$jn886G)!wtDb`kceGG>UMM>xhnO&E{^s2eO*4&SbUb#$+`wByreH95^|b z9ruQJZHOCT0g=6MQ|j&2Ui;dTvW9Zao!&Py@u~1Wz_~-04Y8#TMZ=hUCFc&?K3_~y zdHaN?E(OgodRT08AR8cT1>%)*+8|x|2DWC^JpgDGIzr;es?=yC=jvI^Ma6->%Ed=q zR4WeDf&S8ZmKVg}i)X8qmULoqL9x(ZE%lx_GltiWnG1tk#1bVog9|!}3)@AwYG1PA z^rSOAW82Q1H!4kC%s!dNobd^Fdvcz=Q9H|4^fs38rfo>|lk*kOJ!2eXhqeE36N%oi z(i48X+#2bUh}k<1Y9|EaEGT5j^tc(-Nl#Z5^x zW7&iqMjI26QRrtv=8f%`Gx*jhG^z#V#BrBC&IiG95urT-30v`DXPz|XRNt~UOA>u; zd|1AZ`6yRMM>aU#QE1G1gSo<&xtzUIi;uIHKYLbfwyD-$u`|x{8%FN H-KW-Hsa zMU{LsY&69THT0g2R|agU5E&d<801av>Co0qVV{TC)ZALpCM2}s7U9Bpp9e3t zpC`+drDFX4B(yW}J%fVQFY#O}3p;1$K+nNg^s{11d^Lob)G;b&WF%51|%HPuF{i9KQT?^?q9XI1;EB^ak~fa%PZnDokP zWyz%@0~|vM?+TFr6CuN?sbZk`>e2YtXd)XTeW~(1D?J}g_$vtx(BCah21(UTF8rAW zX)M=*T84U9)tPv{X5Y2M`odOtC9jkWRoILgs;o^sZ_&;Vrj+e$gtKx_EYm~Bq|#ka z^rVLuM3I(F-fmyDBsxmML(%XWWbU+yNIJh6E_yQ){xbL!gym1Tr>M`MJb z6ouixm*k(f)!Lih^SmU!UrBv?DK|ylAW6cOYed;lF}>iAF5-EO6Eexf*5>-6pu9d| zGnQpRZ*PI``UqtsjM!`@6x54-oe7&xST~x9CvKBu&pvHUFbwgsMteweYjF@X;4Oos z&O4QSTV_?S@PYu3MH5zWiMqD0{4;SYV4CFax)e_6~^rh31N3R^ildTa2(e3rEjCYJS`NW802?S>dRoI_DpC zQ{`lTs^r@Yp`|#-ohEncrrgiG-z90Dh_rQmM>bi=mL)Ag$2u(ojg!(Cm=7Jv3%+B z)3U)G)jog5sU8*ER->-YLY=Lv4pd$qI>B-?ril~JUA?63xU8NS>n&B=P7BXbI}68k zmFqKJ(YB(Z3A!!v0Smk}Kguzf-8_{)%HWB0XJP5ti*Vq-ic5c}< zc76PQ&#s*x*|qb%l>BHzTIdxX*fnx! zidnr|jm!?KvBU0#X7eEOz^Vv!AdWS!{WN-{1lSE<0uAO6%A6RA}w^~JL_{bT-wG*=J9lXbgLRy#fkJL8hB$&$g zL8GkBsg#$`onAC;HA#2K^ie0nfW|8_W4@^`#-n@f6q_oI3X05e90Vni->_>Z%w56R zyKRtfk5UXnb#+OzW)@K1ID^WYI3^p7tAI)n$JpMxYv&zCf^ROP?A=^7*o|V>74yIp zq#Nzo#SpEMrontoJ)FPGOd<@1u6IxcgG8eYh#~Whz_2EG;(V*wx!X}|3A_OUSoikO z6iVWM3>w_nY(fg6;`;qoi_zb0C-k-{c=$cZ<& zI#tCcGV#%+{F(LU{)#kKK#XvFmz1ZTw$M4QwgF&V_&7I11ah{xfRx;O*UqR|le!6GH^HvFM0+`QRc z8+p*oe9BCT_PJN6Bulpv+z7d$O1eaJ*dnaSP$WA48`!){(atSpVncYN&kB6gXR0|m zaf3t1@#8P<@=*bl@B&I0sw~kZQ4wc2GZEKkCbxN?jmN0V?J1)pe>fh4v-^uw zs(917)a;6M)Ok|H=xTJ1L2RL2gRbF$J0XB`F=^Z8KT4k zi4%KCr@LU^v?A)?9PCO;XceJtf{CF?KDQBqo81e{96kaaI@(~(Q4{8hq585#b#I74 zIltKP2`vQdtZ6c<3N@%l78mjUu+fWiUxV|QrK5$Lt$TM5>t2JAg6MuS=i=TJt*3Tp z*qf+}W|#1)lk^x&4T+`mCc}~N-FE4Sy z+6k|udo4mE?NYm;4RU1>LIVr|m0Ww0jb3)v5SWZf*CFXEl)0Vxx%=`?>(2*@vokPX zm_^lFY|7FSQ>dv`l;E_%CMO#zb%`5ru(30Vwm4aSL0D~+ZIiu2XtM5}Jjp%@a?+?_ z7Q@*c9YrZUaS?Br!fa3kJf)V!{Nrk8%|KI+?^u}%m4n6n)YEXk{SfycFd5K5&k)ctEBSaV6Zab{4TL7hry&Ub^lNR!Jqy z4n4$iD>=&U`GDg+)4wOMjWdU)~SuV8iLe1JN$x4Gl`v#lA&vL&?G#HC!U`} zWQ0D>>3eIqxzB@5$x4VqVOI}(bJqyN-)g>~A~Oy3bC;%v9NKJDQLNS`<- zPGDn0q>EjtC{M~l+Dr;l-QUXvPVM4h{|pnwqp)T|3^cKK_tCr;i>k#z^Fc1$f|<%= zd~#Q&<@o(bcV0xK6WZbszHN86jU(TWg*rIRW)SOGJf<6{ocoL~rih{L!Ww5z49XgO z2AG7g*t4W5ad-phL)G10PU5xjQHpd<@16I8frw5 zTdC9#YfZ>u9LXfs$h~Pe6-y)J?)Kx=eO!E7ev|F+*j`1vX#v+S2=CXJ=n;^jpbayK z4IW%3s>;PS(`>_pOFW@rjB64(5tx$T3kU?8a?6HMZYy<{8=}As)|X_aZ55>CGL}8r zeWZ@C;VvB?8y%CibkkAyVSzBNdPlAwO*#!+C?RBs7=$z?CbKJ>Yr|B?4Qw3mVmLS$ zCk-0rc5X!{a@f5KNrpGIZF{dY+K@?hoPokQ^1+b8FmUt9+>~IMe-k(TwICRpR(Cmn zm}dQKe!cpn38VS6XfagV<7>>rTl_@P?hS^}j-6p!F2@ijb~8DiT%lK(flN3^bT-D= zWNgsEf88Z!Z!D|*)XS=#(Z9h3Y1pe(1J;M@*rzq0+qdS#^k!wsxgx`>DBTzQc8+ZCkr)6 z6(b@Fh9yq(%|`th{T7OL0b?GI812-!7(3R4-NSTDQd9!lS&Fy2&1jE*$zht;B}QP5e(l^RyEpV< zOh^JdChl|hgcl7Rrm?sUp2Pd%7ky+|=iSW-t0K!?X3gXo%}EB_(KH6@adDz?)e%CK zLT9s(l|wi6>`zzS9$7EaB9*;-o=8YhI_qCwm{l`IvXSIM&hGFn;=9E+Jh1eLHp7f$ zI|>a#eS)kgi0u|DGWz?>&3wJu;LYyl3JKIAha}dy6Cl@na5pl4|Ea%Rw0_8{<+YB` zLqccsHty+84K>U)#(0aO?ZI`@fS1dk*^&vr9SAe2c*b`Zl5M1rO&Rx9xU(^Hgl8wqTTA`hP)`5Ba)iNApgsn`O!hHaa=MIMCxHa^PFbx zrqyQyt44S_%{t7q;0hi#=J{^%Gn@Zk4MohvSq8gz=5U%Y@#Lg4*)XS{!!>GoGMXtj z8(aPi>0eMlxt^!3=%#omAIizj=YS+1WXL86GO33#NlYi~5ZgRgP&8mVaF4gB7Jf06 zjyJ1s#iDz;*j*<`RnnZk?`BxlQfIPIHjXu1Von^8n;|2O(}%_7rdaNCphqDs(G;~j zK+*LyxJ6|i67(e5CLiBZR176PwA#_hsnq2pmZy{2Djd?7Z^dp)lF*VzZN^BC7ywKV z8M09Aw|yiu=C&SKFgSW-mGP05KTd12zCW0PSmw#-f?Na}b561w-U~9|2K1vy;&UYL zIJHxjGj3a?+V;pG<3)cmr@x(R{o}SVsnG-r%{bMELA(4>p#&f2J)(6ex2bQUpS*=& zgT*sUYo-Cor??N3h>=K#pxL(JHjYDY^pIxtbc-ew_Y}+TGd|dmpcaF-7s;B-Oro+s zUw@i-Ri8=Hq!@Z=FfV70Z`$RPCi$~+_zdxk=;;{IG4)AcSIYyp(wo#seJ#=o_5ya&~3`OIq)s-PW`~5 zoDJEzf5dj=JZR#_dSi=?R~U5*#GWM;W@(?aUGAK3Ow!-LE;I#mn9!f_pbYp-dCqGr za-)8>T5l=i;*VVpe!bVkirfeuXvEEeh(wbZq4YD{{=?h0zJV$u^%McgN~{cpA_Rf3 zeG5D%Q+|4ToL#BWjZ(8@>edz{y=&*Tlv64}kP&HS%m%;(h6S@kahiMVi_DIXy;SF> zO>bjL3?Ui%Y9_>W^Td$TwU|KCh$__%LeTz;VjEI{aCkM)BRZ1-m9xGm^`{l;L^G+{ zQJ6V~nRQO`IgQijc=ez~#ZHt@-{>MLTvg*l6GQ}6bOJw_pe74CPdBeUA%==8SBEF(;o*1>$Za3FR{sC+8X;v+5Erl>kKG&cnzt z3w?6~>f)jVYf`XlxCN2ruN} zr0&q_&6&wJ^N5^tcC!W_0b9L+Z)js{h&tM*N0$csV3f}8R%#=NEqP0D=CCsq9QS!M z8&3tsNbU8Bo4jZH4q|r4O<8wu;5*t%jCZI|p2;jAt>jLFX;_%%F1kqvL(L5y%dDl$ z3Mbpd1&o9)3)bE}2RXx)E1R~mywok+LfkX&F}euC%{mTKjU`yVy(OX}?odiq#S*iW zeC-fmsExHXKe?uMj!cjPBvS&q&BVlrJ*0%bgLrN*yOBdN2IC}((h2)evNj&E;3JMy z13^fVf-RVIz1_s)I+JCN*>F8Jr*g~MVcyetirHdlVml5^0~DLEotQvTqb$;|5p5j9 z&TZUBIymCExvuel8Z|r6zz|YudW518Gn;0)u}iw!yo7nYnE=(g)9jshLlBrR4leke0*%uQj63|#U}G>aC+P)t1JC|ObdT<=#*$WCf4m_Qo( zqp&q}9|GYRhjO3t5_p$2K-<$~YG^vwoC{5lD$*51HyF$C7oHL{sY10}9wCOC=tM02 zJAOK*v)FNJ9d7-IFj!0yS@abE-O(v=U5vU!+#h`$>2TxZJ|^E)R%QhCXT%nOLzp7}c7&&s0(xt2dT8J->R_q1E%H(}eX(}CJ@?v=E!?rv#?Df96a|BoAlx<+4EMKL_imO_FuL$ihfj0+9=jb?qw!g|M+W&AmUe<@ zA4n`C%HNipZKN_qOrGkQiU?hVG+QZ!Q?vA7VouEaCzM|+$D{MCps6zKv-(oBDx!@11BqQettg_ICI)=Gdu zEu2I%w3xE{6rtdB+=N`dd}B75JxGPqAU(|hF}Wfbp~nzR%O~`^%q-|1UL$`E5$DMy z=-uO7X~5LtWqRY^4Zi7S8q$idGbs>EL;jQWpION?bvX&GW*xv>-X{Md!eaCoC=TRy zKb=0%8PUKucCz>FZ%~m<1$Uc4!MPUURAv_PIfl^UQz^}}6aj(cr_mOR{4r)$^ClON z({RQi3Ga$=E-AhlPCoCoH(gT5;jw7t)z$Hjz`zW@0qjVX>K?4^y>4 zR<5n|Dw@?+lSP~{DMmPS4XKO=ACKlyn}QiC)O~jOi}D>!SKW!@-DR!er-n?9_GgLl zKVfpMLl^a-;4E___iXwRBAEvj{|{G&Subh}e{h1@uZcSZsX3rnkNQ%%8^T1E!)Rq# z%;4@yZWGa%f&omVm5dGifaK8^6JcV3Dh$zYOvg;U6Pj7_By}Q_O&!~K(8SWhuBYlS z4kz2iX)zr4e>z|zWc(CsnOSRt4x*c~ljh+(u?~6gp!8-;*`j*7(84-=Wgarld0t zq9$=qAC@te7>bjYM*qK`0|tgo=O-}K9X4(}b#OnHA!Rng1*VgqM#HuRx{iU@LM)9@ z^t-5^lc2U6Y#X_G_-2D@PkngUq%JiTixG;LKnOC(*ru#FjpPi%YBi-}jLyxASb>GZ z2H)yK`4jfeaA7*NK&PH>q4?_jPa-JA)M<(+CkSOW3oJWU$rt~GlmeLQM z&XI_NFePQGkC(5c-lQZ%hZ0g&)vih6$Tu%PRb^(<>Z*kg!Xot>{-zQYAeU@zfjcZM zJoqDw`5kDbnLR8bzL;OwSe}6NJm_N#6F&ek+4*nTi0%9aROU7q6%z7Y4=gq83c2~Q z6sH|7p&*6mC~Zh=8M?M2$HuQtDJ7N=U2aZo#pnCH*05FcEo9wdqbl9RYS`LBiir2E zIbVZ&ypI7XzS?(4tG8R%vKDh1MnZ?0J*GoLw$65;vbec5*CLsje&*SJ!*UuVq+~i2 zSDn-SuLLbPBX5@-;>P`^8V%vRxdmfvl(Sg^V%AOIjV)KDU$k@=tOfelF) zD&OOTuV8iUNpv+Ksi}DIOv2}OLR6_p9Q#~(N3Twa4~lvUaV&db&@mEj0g zFmnB(ZfZ|hy%|>C^rUmDX3MuJdyATxOe<3pk&R3e5Q1mG{Y2g5r__*{Pu;8eh}kH^ zPQx31OH)Kc&|u!=|CgNXZ!w@jf{rYeE%EFPN{?7;EWeqlhCeH9ovLXnVvMP|G?VbD zO1(Ibd|#dYx0a3h8kKM?kMA-Qw?vmEaW`&6{$D%xG?hu8B*;hPxCjf45~ow62niR? zgThg*yZ2q@Rh~{nl}-0oE5>cvr)H~26$6ws?Xy@)KUggR0zUMXRdxnfFb(U?`9+wi zs61hnI~jDtb2FDfkPRUwFOJBJ&40T^)r~MNT`e|aq{x|J1vfXLDk8YFLq@FI(hoz? zR6gN2vY~n)hNwx*HU+sL;!mD*sOKgw=@v(WOkA_>7BM`~5(&kWbVA@n!(}AX;YlON z|9`D!6ivT84n=c!H<`FULADs8d1h4EtQUhwYy({->Q5fW`rA14mhT7YW7N@t+@B;G ztoi>q{@usMM{-65yT01oV&lp5iM#&gG~=%+YAe>HCAlguZTgjHu6h2O{YSX4iydeV zn69xU=3)4X2O=VpO&54wT83KLjbOG6K{9QU22FSD5Z9=Vq)m=+RL7tK3bDxZTW@2WdbOM0~ z-gu!zx`7U7!6~A;7$cR?F}G})DB4oFFX+-NfblDb^Vys^l?X+zGEx)W^~&DTtQ>c@ z(c3Ku`}&VaOA}-eLM|iX&oMJRshq_TaKWQSLVj4bM!lfqWZKgZqyCKt(X(qo)kqfEg zJL34V7}BChh8qplT`;PD?0Seyv?gdW=w$^8uPA+ZiBe!MKaAx8qN{9eM_GYD99puq6=k>z8!PE33toqN+%?{~y!qA3UGiK~XGB6G!X}PIE>~WkEU*25)?&5lhoQm@;E`@F$yr)xWO(0SRUqR_GkUOS;moMoHdR~_tiB` zh%K;#BqQUx=jVLe!i2Wl^Bzg7`%Da(ZL_(7_xfmDKCq-Xr5R-!YHOhdhO11^W@lq5 zm-rFI(jbVHDDWYRIBjVJ8EZ@+@Z$HJwQ!qC;%;-~8$~Hcg%DkP%7LdrKJCiVRHlcm z-oz&;Mn`36j(eK!hNf@&4f`san%vie4)~j!GrQts1)ysw$J^XNCVvS9$ClGI^Rq5B4b864G#d)I{8Ucgz#T@^{%3rL^O>= z?pe`*@eYQ0jfqh@fG$Ne*Pg$J^;E!gg{@s%nB8Bm4|jHS%%)0eVlb-q5JNUwvz$bi zVG!JgBb+Iv=;rq>Zj6%+{menO;r}J#HnP$d+f)xgvbccz)7Q;0f4vgs=%^t9aWjV< z#yrimJE$z|$-8l&eTp-`oYaubJc0?xyM1VKqfGOO!bZ0bh}IW30B2g zfDngp)#C!<@UW^^Z<;thdP3KY56%KQVlu&+xGVm-Q}EfR7LVzQ!pe;*<9WQu4n zF>r=UGJ>!sZ5*!iM;(`Dej`4JfEykOuu?HYU1SVTnWN`C+32PGx)8aIB`Vcy;u6g` z=?6ma4OiyxO+h1Y;|2sPDA^V3)A;O*iUWO>i;uXdRvf4U{iXFiWq!7*ez8(sL@^cV z1;s*twbXm!%;93Kwysj`>6p1N`j|WxDr1dvXppCK5u;NupUfpqToV7r1s%nOT#et* zi&{@@r8(hEByWXe%rE#fgis8IM)x*5=E*w2XbdL#tn%EcsI|DC>&&To5*;mI_Q{Rw z3Yc?EKZ4+_t^6#;s|I7X{~Zza;Bv@J0pH(9Gz-h@LmuRBBWK@-`;QrKw#Mp)KAN0Q zS|et;(GV%+wP|OR5*_Gx!xQO@X5;&lv!}8C3HR;lBW5#xJT97tevvH+Uj8ePNm(c1`W?};@XFqNIN%{9BbAK0*F(!YNdwD zXtDrLrN*H*Sf^^LARiWua~riA^rXQ7HmMI0pY-oCS<)`AR^ulpWA8GZyQlv3D=ZpW zraVGhK{|`No7!Ybl7Y17q0l4q_|Z)e^rH`9POwWDN~PIA4^HSJ>RNqec9zqI?Oah; zMBubF7tthS7*ry@ZOtnVPPRA5QAQc{4Q27^Q-jUmXyKl;dD61TRpjQ*nnN@6JS9_O zCb|&oEM-&b|<$#k)p(;+V0k^%cTiL=;gsw%}MI_BOr*_fjCxS%SkW9` z%v`22ziBZgL1IMCONcGn;zIozI%lwjVA$=Q^tbJtfbF18A+o6i)f8Xz7TA~r_!}$Z zdkYo{9h@w6A%vgiV$hVZv1CmL<0)h=+-zoEJ=od2f>zrQcB>&oW&YtmHtE|3lHG(A zi3Mv--@bG`LZjOdQJfh#Q92MXXOb!sIzS}zEiV<>5Kg}|AjT>@KpGk$L zjLHI)j6;@45uCa-$#pzBjGqq8FsX|~FdCVDn+Z5b%z4^w7M%tg?uFsQZ;N{c8_3pZ zq{uXkep+?Ygez=yPQpTqssxR=X`^X4h6m#S*&!KCL%66#nn`vF=ypO{P;gq;WIc>2 zVGnd^OGg__0Ez}&H{}As%B@LbMK`ZBX>g$v88TC8Zq#9#&TR5-tIUi8DG5G*-9lp9 zth(wdKBY>gRmueOidKR6U1k=T3ZtKRyBb;(PZ=<0#_K`YxWsMJq*lh;+O%R!xFx?F zq$=_x20+#a8`Yj!w|a7iq|!ZfuOC%uP|{pFB?w&Y34jN1Nd)TH3hV||D1>l|LrFe` zwu1SdFDGydY@Due#bEQk_LG-8EsT;{RLR^TUljE%K9C24k~5oSh1czNqjo-~#fc1- zBmdesgQ=-&!NrzT;Ix=H05|{K9a_yyo#}Q{Zbn6<1og0YkZKIcrOOR!NU<EpIdr2Oqyw-h1?GccKP6nyNY!ePC%M zk{|h;Y4_xXk{=g{q6I_dye0wjdU!xy?or~$I|ybL8C4jX=ti@~VQ+lv7L{_nn$;+X z_|QU-PA^(S=uAhn!eSIzZc0@v9omp6WyT5;;sUe4M2PD8Z_->g*sT0*bM!Gi2BW4~ zl1CZ5ZRui)pzjMH4393|ZY3F--?(e%HW%JxweWT7;HQL0;o@{hz56Q zNO~Co9HqN|PB;Xt{FD7OqSC;LZ7p1q|0%^l-#ce{;vt=x2H`3StUSxn>xp(O;)=7z`z zlA0P7h|nnAlFFefmmgk+sl@J$v8gJBI0y-vDab%63)lg6lNMK5+0EOwy4cU9400gHrjE^$T^Ms5pK(b2}q35 zy^_b>Zet8#3Sq%guqX(R6onYn6mZfW5HXdc61{bo?>l#AHxuwy#GAC3R>{oDr3kxE z{ApP<6HM0)Km12Qm^vM%dps`e<{%=$O93u^F0eGYVSXZPE^3mhIey3yaQYB%!w}7a hT0~f8+RR|)p*qYf%vrk1-F6evw, 2010. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Django SVN\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-05-27 11:01+0530\n" +"PO-Revision-Date: 2010-05-28 15:09+0530\n" +"Last-Translator: Rajeesh Nair \n" +"Language-Team: MALAYALAM \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Malayalam\n" +"X-Poedit-Country: INDIA\n" + +#: conf/global_settings.py:44 +msgid "Arabic" +msgstr "അറബി" + +#: conf/global_settings.py:45 +msgid "Bulgarian" +msgstr "ബള്‍ഗേറിയന്‍" + +#: conf/global_settings.py:46 +msgid "Bengali" +msgstr "ബംഗാളി" + +#: conf/global_settings.py:47 +msgid "Bosnian" +msgstr "ബോസ്നിയന്‍" + +#: conf/global_settings.py:48 +msgid "Catalan" +msgstr "കാറ്റലന്‍" + +#: conf/global_settings.py:49 +msgid "Czech" +msgstr "ചെക്" + +#: conf/global_settings.py:50 +msgid "Welsh" +msgstr "വെല്‍ഷ്" + +#: conf/global_settings.py:51 +msgid "Danish" +msgstr "ഡാനിഷ്" + +#: conf/global_settings.py:52 +msgid "German" +msgstr "ജര്‍മന്‍" + +#: conf/global_settings.py:53 +msgid "Greek" +msgstr "ഗ്രീക്ക്" + +#: conf/global_settings.py:54 +msgid "English" +msgstr "ഇംഗ്ളീഷ്" + +#: conf/global_settings.py:55 +msgid "British English" +msgstr "ബ്രിട്ടീഷ് ഇംഗ്ളീഷ്" + +#: conf/global_settings.py:56 +msgid "Spanish" +msgstr "സ്പാനിഷ്" + +#: conf/global_settings.py:57 +msgid "Argentinean Spanish" +msgstr "അര്‍ജന്റീനിയന്‍ സ്പാനിഷ്" + +#: conf/global_settings.py:58 +msgid "Estonian" +msgstr "എസ്ടോണിയന്‍ സ്പാനിഷ്" + +#: conf/global_settings.py:59 +msgid "Basque" +msgstr "ബാസ്ക്യു" + +#: conf/global_settings.py:60 +msgid "Persian" +msgstr "പേര്‍ഷ്യന്‍" + +#: conf/global_settings.py:61 +msgid "Finnish" +msgstr "ഫിന്നിഷ്" + +#: conf/global_settings.py:62 +msgid "French" +msgstr "ഫ്രെഞ്ച്" + +#: conf/global_settings.py:63 +msgid "Frisian" +msgstr "ഫ്രിസിയന്‍" + +#: conf/global_settings.py:64 +msgid "Irish" +msgstr "ഐറിഷ്" + +#: conf/global_settings.py:65 +msgid "Galician" +msgstr "ഗലിഷ്യന്‍" + +#: conf/global_settings.py:66 +msgid "Hebrew" +msgstr "ഹീബ്റു" + +#: conf/global_settings.py:67 +msgid "Hindi" +msgstr "ഹിന്ദി" + +#: conf/global_settings.py:68 +msgid "Croatian" +msgstr "ക്രൊയേഷ്യന്‍" + +#: conf/global_settings.py:69 +msgid "Hungarian" +msgstr "ഹംഗേറിയന്‍" + +#: conf/global_settings.py:70 +msgid "Indonesian" +msgstr "ഇന്‍ദൊനേഷ്യന്‍" + +#: conf/global_settings.py:71 +msgid "Icelandic" +msgstr "ഐസ്ലാന്‍ഡിക്" + +#: conf/global_settings.py:72 +msgid "Italian" +msgstr "ഇറ്റാലിയന്‍" + +#: conf/global_settings.py:73 +msgid "Japanese" +msgstr "ജാപ്പനീസ്" + +#: conf/global_settings.py:74 +msgid "Georgian" +msgstr "ജോര്‍ജിയന്‍" + +#: conf/global_settings.py:75 +msgid "Khmer" +msgstr "" + +#: conf/global_settings.py:76 +msgid "Kannada" +msgstr "കന്നഡ" + +#: conf/global_settings.py:77 +msgid "Korean" +msgstr "കൊറിയന്‍" + +#: conf/global_settings.py:78 +msgid "Lithuanian" +msgstr "ലിത്വാനിയന്‍" + +#: conf/global_settings.py:79 +msgid "Latvian" +msgstr "ലാറ്റ്വിയന്‍" + +#: conf/global_settings.py:80 +msgid "Macedonian" +msgstr "മാസിഡോണിയന്‍" + +#: conf/global_settings.py:81 +msgid "Mongolian" +msgstr "മംഗോളിയന്‍" + +#: conf/global_settings.py:82 +msgid "Dutch" +msgstr "ഡച്ച്" + +#: conf/global_settings.py:83 +msgid "Norwegian" +msgstr "" + +#: conf/global_settings.py:84 +msgid "Norwegian Bokmal" +msgstr "" + +#: conf/global_settings.py:85 +msgid "Norwegian Nynorsk" +msgstr "" + +#: conf/global_settings.py:86 +msgid "Polish" +msgstr "" + +#: conf/global_settings.py:87 +msgid "Portuguese" +msgstr "" + +#: conf/global_settings.py:88 +msgid "Brazilian Portuguese" +msgstr "" + +#: conf/global_settings.py:89 +msgid "Romanian" +msgstr "" + +#: conf/global_settings.py:90 +msgid "Russian" +msgstr "" + +#: conf/global_settings.py:91 +msgid "Slovak" +msgstr "" + +#: conf/global_settings.py:92 +msgid "Slovenian" +msgstr "" + +#: conf/global_settings.py:93 +msgid "Albanian" +msgstr "" + +#: conf/global_settings.py:94 +msgid "Serbian" +msgstr "" + +#: conf/global_settings.py:95 +msgid "Serbian Latin" +msgstr "" + +#: conf/global_settings.py:96 +msgid "Swedish" +msgstr "" + +#: conf/global_settings.py:97 +msgid "Tamil" +msgstr "" + +#: conf/global_settings.py:98 +msgid "Telugu" +msgstr "" + +#: conf/global_settings.py:99 +msgid "Thai" +msgstr "" + +#: conf/global_settings.py:100 +msgid "Turkish" +msgstr "" + +#: conf/global_settings.py:101 +msgid "Ukrainian" +msgstr "" + +#: conf/global_settings.py:102 +msgid "Vietnamese" +msgstr "" + +#: conf/global_settings.py:103 +msgid "Simplified Chinese" +msgstr "" + +#: conf/global_settings.py:104 +msgid "Traditional Chinese" +msgstr "" + +#: contrib/admin/actions.py:48 +#, python-format +msgid "Successfully deleted %(count)d %(items)s." +msgstr "%(count)d %(items)s വിജയകരമായി ഡിലീറ്റ് ചെയ്തു." + +#: contrib/admin/actions.py:55 contrib/admin/options.py:1125 +msgid "Are you sure?" +msgstr "തീര്‍ച്ചയാണോ?" + +#: contrib/admin/actions.py:73 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "തെരഞ്ഞെടുത്ത %(verbose_name_plural)s ഡിലീറ്റ് ചെയ്യുക." + +#: contrib/admin/filterspecs.py:44 +#, python-format +msgid "" +"

By %s:

\n" +"
    \n" +msgstr "" +"

    By %s:

    \n" +"
      \n" + +#: contrib/admin/filterspecs.py:75 contrib/admin/filterspecs.py:92 +#: contrib/admin/filterspecs.py:147 contrib/admin/filterspecs.py:173 +msgid "All" +msgstr "എല്ലാം" + +#: contrib/admin/filterspecs.py:113 +msgid "Any date" +msgstr "ഏതെങ്കിലും തീയതി" + +#: contrib/admin/filterspecs.py:114 +msgid "Today" +msgstr "ഇന്ന്" + +#: contrib/admin/filterspecs.py:117 +msgid "Past 7 days" +msgstr "കഴിഞ്ഞ ഏഴു ദിവസം" + +#: contrib/admin/filterspecs.py:119 +msgid "This month" +msgstr "ഈ മാസം" + +#: contrib/admin/filterspecs.py:121 +msgid "This year" +msgstr "ഈ വര്‍ഷം" + +#: contrib/admin/filterspecs.py:147 forms/widgets.py:466 +msgid "Yes" +msgstr "അതെ" + +#: contrib/admin/filterspecs.py:147 forms/widgets.py:466 +msgid "No" +msgstr "അല്ല" + +#: contrib/admin/filterspecs.py:154 forms/widgets.py:466 +msgid "Unknown" +msgstr "അജ്ഞാതം" + +#: contrib/admin/helpers.py:20 +msgid "Action:" +msgstr "ആക്ഷന്‍" + +#: contrib/admin/models.py:19 +msgid "action time" +msgstr "ആക്ഷന്‍ സമയം" + +#: contrib/admin/models.py:22 +msgid "object id" +msgstr "ഒബ്ജെക്ട് ഐഡി" + +#: contrib/admin/models.py:23 +msgid "object repr" +msgstr "ഒബ്ജെക്ട് സൂചന" + +#: contrib/admin/models.py:24 +msgid "action flag" +msgstr "ആക്ഷന്‍ ഫ്ളാഗ്" + +#: contrib/admin/models.py:25 +msgid "change message" +msgstr "സന്ദേശം മാറ്റുക" + +#: contrib/admin/models.py:28 +msgid "log entry" +msgstr "ലോഗ് എന്ട്രി" + +#: contrib/admin/models.py:29 +msgid "log entries" +msgstr "ലോഗ് എന്ട്രികള്‍" + +#: contrib/admin/options.py:138 contrib/admin/options.py:153 +msgid "None" +msgstr "ഒന്നുമില്ല" + +#: contrib/admin/options.py:559 +#, python-format +msgid "Changed %s." +msgstr "%s മാറ്റി." + +#: contrib/admin/options.py:559 contrib/admin/options.py:569 +#: contrib/comments/templates/comments/preview.html:16 db/models/base.py:845 +#: forms/models.py:568 +msgid "and" +msgstr "ഉം" + +#: contrib/admin/options.py:564 +#, python-format +msgid "Added %(name)s \"%(object)s\"." +msgstr "%(name)s \"%(object)s\" ചേര്‍ത്തു." + +#: contrib/admin/options.py:568 +#, python-format +msgid "Changed %(list)s for %(name)s \"%(object)s\"." +msgstr "%(name)s \"%(object)s\" ന്റെ %(list)s മാറ്റി." + +#: contrib/admin/options.py:573 +#, python-format +msgid "Deleted %(name)s \"%(object)s\"." +msgstr "%(name)s \"%(object)s\" ഡിലീറ്റ് ചെയ്തു." + +#: contrib/admin/options.py:577 +msgid "No fields changed." +msgstr "ഒരു മാറ്റവുമില്ല." + +#: contrib/admin/options.py:643 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" വിജയകരമായി കൂട്ടിച്ചേര്ത്തു." + +#: contrib/admin/options.py:647 contrib/admin/options.py:680 +msgid "You may edit it again below." +msgstr "താഴെ നിന്ന് വീണ്ടും മാറ്റം വരുത്താം" + +#: contrib/admin/options.py:657 contrib/admin/options.py:690 +#, python-format +msgid "You may add another %s below." +msgstr "%s ഒന്നു കൂടി ചേര്‍ക്കാം" + +#: contrib/admin/options.py:678 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." +msgstr "%(name)s \"%(obj)s\" ല്‍ മാറ്റം വരുത്തി." + +#: contrib/admin/options.py:686 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" കൂട്ടി ചേര്‍ത്തു. താഴെ നിന്നും മാറ്റം വരുത്താം." + +#: contrib/admin/options.py:740 contrib/admin/options.py:997 +msgid "" +"Items must be selected in order to perform actions on them. No items have " +"been changed." +msgstr "ആക്ഷന്‍ നടപ്പിലാക്കേണ്ട വകകള്‍ തെരഞ്ഞെടുക്കണം. ഒന്നും മാറ്റിയിട്ടില്ല." + +#: contrib/admin/options.py:759 +msgid "No action selected." +msgstr "ആക്ഷനൊന്നും തെരഞ്ഞെടുത്തില്ല." + +#: contrib/admin/options.py:840 +#, python-format +msgid "Add %s" +msgstr "%s ചേര്‍ക്കുക" + +#: contrib/admin/options.py:866 contrib/admin/options.py:1105 +#, python-format +msgid "%(name)s object with primary key %(key)r does not exist." +msgstr "%(key)r എന്ന പ്രാഥമിക കീ ഉള്ള %(name)s വസ്തു ഒന്നും നിലവിലില്ല." + +#: contrib/admin/options.py:931 +#, python-format +msgid "Change %s" +msgstr "%s മാറ്റാം" + +#: contrib/admin/options.py:977 +msgid "Database error" +msgstr "ഡേറ്റാബേസ് തകരാറാണ്." + +#: contrib/admin/options.py:1039 +#, python-format +msgid "%(count)s %(name)s was changed successfully." +msgid_plural "%(count)s %(name)s were changed successfully." +msgstr[0] "%(count)s %(name)s ല്‍ മാറ്റം വരുത്തി." +msgstr[1] "%(count)s %(name)s ല്‍ മാറ്റം വരുത്തി." + +#: contrib/admin/options.py:1066 +#, python-format +msgid "%(total_count)s selected" +msgid_plural "All %(total_count)s selected" +msgstr[0] "%(total_count)s തെരഞ്ഞെടുത്തു." +msgstr[1] "%(total_count)sഉം തെരഞ്ഞെടുത്തു." + +#: contrib/admin/options.py:1071 +#, python-format +msgid "0 of %(cnt)s selected" +msgstr "%(cnt)s ല്‍ ഒന്നും തെരഞ്ഞെടുത്തില്ല." + +#: contrib/admin/options.py:1118 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" ഡിലീറ്റ് ചെയ്തു." + +#: contrib/admin/options.py:1155 +#, python-format +msgid "Change history: %s" +msgstr "%s ലെ മാറ്റങ്ങള്‍." + +#: contrib/admin/sites.py:18 contrib/admin/views/decorators.py:14 +#: contrib/auth/forms.py:81 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "ദയവായി ശരിയായ യൂസര്‍നാമവും പാസ്വേര്ഡും നല്കുക. ഇംഗ്ളീഷ് അക്ഷരങ്ങള്‍ വല്യക്ഷരമാണോ അല്ലയോ എന്നത് " +"ശ്രദ്ധിക്കണം" + +#: contrib/admin/sites.py:307 contrib/admin/views/decorators.py:40 +msgid "Please log in again, because your session has expired." +msgstr "താങ്കളുടെ സെഷന്റെ കാലാവധി കഴിഞ്ഞു. വീണ്ടും ലോഗിന്‍ ചെയ്യണം." + +#: contrib/admin/sites.py:314 contrib/admin/views/decorators.py:47 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "നിങ്ങളുടെ ബ്രൗസര്‍ കുക്കീസ് സ്വീകരിക്കാന്‍ തയ്യാറല്ലെന്നു തോന്നുന്നു. കുക്കീസ് പ്രവര്‍ത്തനക്ഷമമാക്കിയ ശേഷം " +"ഈ പേജ് രീലോഡ് ചെയ്ത് വീണ്ടും ശ്രമിക്കുക." + +#: contrib/admin/sites.py:330 contrib/admin/sites.py:336 +#: contrib/admin/views/decorators.py:66 +msgid "Usernames cannot contain the '@' character." +msgstr "യൂസര്‍നാമത്തില്‍ '@' എന്ന ചിഹ്നം പാടില്ല." + +#: contrib/admin/sites.py:333 contrib/admin/views/decorators.py:62 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "നിങ്ങളുടെ ഇ-മെയില്‍ അഡ്രസ്സ് അല്ല യൂസര്‍നാമം. പകരം '%s' ഉപയോഗിച്ച് നോക്കുക." + +#: contrib/admin/sites.py:389 +msgid "Site administration" +msgstr "സൈറ്റ് ഭരണം" + +#: contrib/admin/sites.py:403 contrib/admin/templates/admin/login.html:26 +#: contrib/admin/templates/registration/password_reset_complete.html:14 +#: contrib/admin/views/decorators.py:20 +msgid "Log in" +msgstr "ലോഗ്-ഇന്‍" + +#: contrib/admin/sites.py:448 +#, python-format +msgid "%s administration" +msgstr "%s ഭരണം" + +#: contrib/admin/widgets.py:75 +msgid "Date:" +msgstr "തീയതി:" + +#: contrib/admin/widgets.py:75 +msgid "Time:" +msgstr "സമയം:" + +#: contrib/admin/widgets.py:99 +msgid "Currently:" +msgstr "" + +#: contrib/admin/widgets.py:99 +msgid "Change:" +msgstr "മാറ്റുക:" + +#: contrib/admin/widgets.py:129 +msgid "Lookup" +msgstr "തിരയുക" + +#: contrib/admin/widgets.py:244 +msgid "Add Another" +msgstr "ഒന്നു കൂടി ചേര്‍ക്കുക" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "പേജ് കണ്ടില്ല" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "ക്ഷമിക്കണം, ആവശ്യപ്പെട്ട പേജ് കണ്ടെത്താന്‍ കഴിഞ്ഞില്ല." + +#: contrib/admin/templates/admin/500.html:4 +#: contrib/admin/templates/admin/app_index.html:8 +#: contrib/admin/templates/admin/base.html:55 +#: contrib/admin/templates/admin/change_form.html:18 +#: contrib/admin/templates/admin/change_list.html:42 +#: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:6 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/object_history.html:6 +#: contrib/admin/templates/admin/auth/user/change_password.html:11 +#: contrib/admin/templates/registration/logged_out.html:4 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:5 +#: contrib/admin/templates/registration/password_reset_complete.html:4 +#: contrib/admin/templates/registration/password_reset_confirm.html:4 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:3 +msgid "Home" +msgstr "പൂമുഖം" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "സെര്‍വര്‍ തകരാറാണ്" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "സെര്‍വര്‍ തകരാറാണ് (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "സെര്‍വര്‍ തകരാറാണ് (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "എന്തോ തകരാറുണ്ട്. ഉടന്‍ പരിഹരിക്കാനായി സൈറ്റ് നിയന്ത്രകര്‍ക്കു ഇ-മെയില്‍ വഴി രിപ്പോര്‍ട്ട് ചെയ്തിട്ടുണ്ട്." +"ദയവായി കാത്തിരിക്കുക." + +#: contrib/admin/templates/admin/actions.html:4 +msgid "Run the selected action" +msgstr "തെരഞ്ഞെടുത്ത ആക്ഷന്‍ നടപ്പിലാക്കുക" + +#: contrib/admin/templates/admin/actions.html:4 +msgid "Go" +msgstr "" + +#: contrib/admin/templates/admin/actions.html:11 +msgid "Click here to select the objects across all pages" +msgstr "എല്ലാ പേജിലേയും വസ്തുക്കള്‍ തെരഞ്ഞെടുക്കാന്‍ ഇവിടെ ക്ലിക് ചെയ്യുക." + +#: contrib/admin/templates/admin/actions.html:11 +#, python-format +msgid "Select all %(total_count)s %(module_name)s" +msgstr "മുഴുവന്‍ %(total_count)s %(module_name)s ഉം തെരഞ്ഞെടുക്കുക" + +#: contrib/admin/templates/admin/actions.html:13 +msgid "Clear selection" +msgstr "തെരഞ്ഞെടുത്തത് റദ്ദാക്കുക." + +#: contrib/admin/templates/admin/app_index.html:10 +#: contrib/admin/templates/admin/index.html:19 +#, python-format +msgid "%(name)s" +msgstr "" + +#: contrib/admin/templates/admin/base.html:28 +msgid "Welcome," +msgstr "സ്വാഗതം, " + +#: contrib/admin/templates/admin/base.html:33 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:3 +msgid "Documentation" +msgstr "സഹായക്കുറിപ്പുകള്‍" + +#: contrib/admin/templates/admin/base.html:41 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:48 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:4 +msgid "Change password" +msgstr "പാസ് വേര്‍ഡ് മാറ്റുക." + +#: contrib/admin/templates/admin/base.html:48 +#: contrib/admin/templates/registration/password_change_done.html:3 +#: contrib/admin/templates/registration/password_change_form.html:4 +msgid "Log out" +msgstr "പുറത്ത് കടക്കുക." + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "ജാംഗോ സൈറ്റ് അഡ്മിന്‍" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "ജാംഗോ ഭരണം" + +#: contrib/admin/templates/admin/change_form.html:21 +#: contrib/admin/templates/admin/index.html:29 +msgid "Add" +msgstr "" + +#: contrib/admin/templates/admin/change_form.html:28 +#: contrib/admin/templates/admin/object_history.html:10 +msgid "History" +msgstr "ചരിത്രം" + +#: contrib/admin/templates/admin/change_form.html:29 +#: contrib/admin/templates/admin/edit_inline/stacked.html:9 +#: contrib/admin/templates/admin/edit_inline/tabular.html:28 +msgid "View on site" +msgstr "" + +#: contrib/admin/templates/admin/change_form.html:39 +#: contrib/admin/templates/admin/change_list.html:71 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +#: contrib/admin/templates/registration/password_change_form.html:15 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "ദയവായി താഴെയുള്ള തെറ്റ് പരിഹരിക്കുക." +msgstr[1] "ദയവായി താഴെയുള്ള തെറ്റുകള്‍ പരിഹരിക്കുക." + +#: contrib/admin/templates/admin/change_list.html:63 +#, python-format +msgid "Add %(name)s" +msgstr "" + +#: contrib/admin/templates/admin/change_list.html:82 +msgid "Filter" +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:10 +#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:302 +msgid "Delete" +msgstr "" + +#: contrib/admin/templates/admin/delete_confirmation.html:16 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "%(object_name)s '%(escaped_object)s ഡിലീറ്റ് ചെയ്യുമ്പോള്‍ അതുമായി ബന്ധമുള്ള വസ്തുക്കളും" +"ഡിലീറ്റ് ആവും. പക്ഷേ നിങ്ങള്‍ക്ക് താഴെ പറഞ്ഞ തരം വസ്തുക്കള്‍ ഡിലീറ്റ് ചെയ്യാനുള്ള അനുമതി ഇല്ല:" + +#: contrib/admin/templates/admin/delete_confirmation.html:23 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "%(object_name)s \"%(escaped_object)s\" ഡിലീറ്റ് ചെയ്യണമെന്ന് തീര്‍ച്ചയാണോ?" +"അതുമായി ബന്ധമുള്ള താഴെപ്പറയുന്ന വസ്തുക്കളെല്ലാം ഡിലീറ്റ് ആവും:" + +#: contrib/admin/templates/admin/delete_confirmation.html:28 +#: contrib/admin/templates/admin/delete_selected_confirmation.html:33 +msgid "Yes, I'm sure" +msgstr "അതെ, തീര്‍ച്ചയാണ്" + +#: contrib/admin/templates/admin/delete_selected_confirmation.html:9 +msgid "Delete multiple objects" +msgstr "ഒന്നിലേറെ വസ്തുക്കള്‍ ഡിലീറ്റ് ചെയ്തോളൂ" + +#: contrib/admin/templates/admin/delete_selected_confirmation.html:15 +#, python-format +msgid "" +"Deleting the %(object_name)s would result in deleting related objects, but " +"your account doesn't have permission to delete the following types of " +"objects:" +msgstr "%(object_name)s ഡിലീറ്റ് ചെയ്യുമ്പോള്‍ അതുമായി ബന്ധമുള്ള വസ്തുക്കളും" +"ഡിലീറ്റ് ആവും. പക്ഷേ നിങ്ങള്‍ക്ക് താഴെ പറഞ്ഞ തരം വസ്തുക്കള്‍ ഡിലീറ്റ് ചെയ്യാനുള്ള അനുമതി ഇല്ല:" + +#: contrib/admin/templates/admin/delete_selected_confirmation.html:22 +#, python-format +msgid "" +"Are you sure you want to delete the selected %(object_name)s objects? All of " +"the following objects and their related items will be deleted:" +msgstr "തെരഞ്ഞെടുത്ത %(object_name)s എല്ലാം ഡിലീറ്റ് ചെയ്യണമെന്ന് തീര്‍ച്ചയാണോ?" +"താഴെപ്പറയുന്ന വസ്തുക്കളും അതുമായി ബന്ധമുള്ളതെല്ലാം ഡിലീറ്റ് ആവും:" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr "" + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "" + +#: contrib/admin/templates/admin/index.html:35 +msgid "Change" +msgstr "മാറ്റുക" + +#: contrib/admin/templates/admin/index.html:45 +msgid "You don't have permission to edit anything." +msgstr "ഒന്നിലും മാറ്റം വരുത്താനുള്ള അനുമതി ഇല്ല." + +#: contrib/admin/templates/admin/index.html:53 +msgid "Recent Actions" +msgstr "സമീപകാല പ്രവ്രുത്തികള്‍" + +#: contrib/admin/templates/admin/index.html:54 +msgid "My Actions" +msgstr "എന്റെ പ്രവ്രുത്തികള്‍" + +#: contrib/admin/templates/admin/index.html:58 +msgid "None available" +msgstr "ഒന്നും ലഭ്യമല്ല" + +#: contrib/admin/templates/admin/index.html:72 +msgid "Unknown content" +msgstr "ഉള്ളടക്കം അറിയില്ല." + +#: contrib/admin/templates/admin/invalid_setup.html:7 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "നിങ്ങളുടെ ഡേറ്റാബേസ് ഇന്‍സ്ടാലേഷനില്‍ എന്തോ പിശകുണ്ട്. ശരിയായ ടേബിളുകള്‍ ഉണ്ടെന്നും ഡേറ്റാബേസ് " +"വായനായോഗ്യമാണെന്നും ഉറപ്പു വരുത്തുക." + +#: contrib/admin/templates/admin/login.html:19 +msgid "Username:" +msgstr "യൂസര്‍ നാമം" + +#: contrib/admin/templates/admin/login.html:22 +msgid "Password:" +msgstr "പാസ് വേര്‍ഡ്" + +#: contrib/admin/templates/admin/object_history.html:22 +msgid "Date/time" +msgstr "തീയതി/സമയം" + +#: contrib/admin/templates/admin/object_history.html:23 +msgid "User" +msgstr "യൂസര്‍" + +#: contrib/admin/templates/admin/object_history.html:24 +msgid "Action" +msgstr "ആക്ഷന്‍" + +#: contrib/admin/templates/admin/object_history.html:38 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "ഈ വസ്തുവിന്റെ മാറ്റങ്ങളുടെ ചരിത്രം ലഭ്യമല്ല. ഒരുപക്ഷെ ഇത് അഡ്മിന്‍ സൈറ്റ് വഴി ചേര്‍ത്തതായിരിക്കില്ല." + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "എല്ലാം കാണട്ടെ" + +#: contrib/admin/templates/admin/pagination.html:11 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Save" +msgstr "സേവ് ചെയ്യണം" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Search" +msgstr "പരതുക" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 ഫലം" +msgstr[1] "%(counter)s ഫലങ്ങള്‍" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "ആകെ %(full_result_count)s" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save as new" +msgstr "പുതിയതായി സേവ് ചെയ്യണം" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and add another" +msgstr "സേവ് ചെയ്ത ശേഷം വേറെ ചേര്‍ക്കണം" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save and continue editing" +msgstr "സേവ് ചെയ്ത ശേഷം മാറ്റം വരുത്താം" + +#: contrib/admin/templates/admin/auth/user/add_form.html:5 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "ആദ്യം, യൂസര്‍ നാമവും പാസ് വേര്‍ഡും നല്കണം. പിന്നെ, കൂടുതല്‍ കാര്യങ്ങള്‍ മാറ്റാവുന്നതാണ്." + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "%(username)s ന് പുതിയ പാസ് വേര്‍ഡ് നല്കുക." + +#: contrib/admin/templates/admin/auth/user/change_password.html:35 +#: contrib/auth/forms.py:17 contrib/auth/forms.py:61 contrib/auth/forms.py:186 +msgid "Password" +msgstr "പാസ് വേര്‍ഡ്" + +#: contrib/admin/templates/admin/auth/user/change_password.html:41 +#: contrib/admin/templates/registration/password_change_form.html:37 +#: contrib/auth/forms.py:187 +msgid "Password (again)" +msgstr "പാസ് വേര്‍ഡ് (വീണ്ടും)" + +#: contrib/admin/templates/admin/auth/user/change_password.html:42 +#: contrib/auth/forms.py:19 +msgid "Enter the same password as above, for verification." +msgstr "പാസ് വേര്‍ഡ് മുകളിലെ പോലെ തന്നെ നല്കുക. (ഉറപ്പു വരുത്താനാണ്.)" + +#: contrib/admin/templates/admin/edit_inline/stacked.html:64 +#: contrib/admin/templates/admin/edit_inline/tabular.html:110 +#, python-format +msgid "Add another %(verbose_name)s" +msgstr "%(verbose_name)s ഒന്നു കൂടി ചേര്‍ക്കുക" + +#: contrib/admin/templates/admin/edit_inline/stacked.html:67 +#: contrib/admin/templates/admin/edit_inline/tabular.html:113 +#: contrib/comments/templates/comments/delete.html:12 +msgid "Remove" +msgstr "നീക്കം ചെയ്യുക" + +#: contrib/admin/templates/admin/edit_inline/tabular.html:15 +msgid "Delete?" +msgstr "ഡിലീറ്റ് ചെയ്യട്ടെ?" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "ഈ വെബ് സൈറ്റില്‍ കുറെ നല്ല സമയം ചെലവഴിച്ചതിനു നന്ദി." + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "വീണ്ടും ലോഗ്-ഇന്‍ ചെയ്യുക." + +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_change_form.html:5 +#: contrib/admin/templates/registration/password_change_form.html:7 +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "Password change" +msgstr "പാസ് വേര്‍ഡ് മാറ്റം" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "പാസ് വേര്‍ഡ് മാറ്റം വിജയിച്ചു" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "നിങ്ങളുടെ പാസ് വേര്‍ഡ് മാറ്റിക്കഴിഞ്ഞു." + +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "സുരക്ഷയ്ക്കായി നിങ്ങളുടെ പഴയ പാസ് വേര്‍ഡ് നല്കുക. പിന്നെ, പുതിയ പാസ് വേര്‍ഡ് രണ്ട് തവണ നല്കുക. " +"(ടയ്പ് ചെയ്തതു ശരിയാണെന്ന് ഉറപ്പാക്കാന്‍)" + +#: contrib/admin/templates/registration/password_change_form.html:27 +#: contrib/auth/forms.py:170 +msgid "Old password" +msgstr "പഴയ പാസ് വേര്‍ഡ്" + +#: contrib/admin/templates/registration/password_change_form.html:32 +#: contrib/auth/forms.py:144 +msgid "New password" +msgstr "പുതിയ പാസ് വേര്‍ഡ്" + +#: contrib/admin/templates/registration/password_change_form.html:43 +#: contrib/admin/templates/registration/password_reset_confirm.html:21 +msgid "Change my password" +msgstr "എന്റെ പാസ് വേര്‍ഡ് മാറ്റണം" + +#: contrib/admin/templates/registration/password_reset_complete.html:4 +#: contrib/admin/templates/registration/password_reset_confirm.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +msgid "Password reset" +msgstr "പാസ് വേര്‍ഡ് പുനസ്ഥാപിക്കല്‍" + +#: contrib/admin/templates/registration/password_reset_complete.html:6 +#: contrib/admin/templates/registration/password_reset_complete.html:10 +msgid "Password reset complete" +msgstr "പാസ് വേര്‍ഡ് പുനസ്ഥാപിക്കല്‍ പൂര്‍ണം" + +#: contrib/admin/templates/registration/password_reset_complete.html:12 +msgid "Your password has been set. You may go ahead and log in now." +msgstr "നിങ്ങളുടെ പാസ് വേര്‍ഡ് തയ്യാര്‍. ഇനി ലോഗ്-ഇന്‍ ചെയ്യാം." + +#: contrib/admin/templates/registration/password_reset_confirm.html:4 +msgid "Password reset confirmation" +msgstr "പാസ് വേര്‍ഡ് പുനസ്ഥാപിക്കല്‍ ഉറപ്പാക്കല്‍" + +#: contrib/admin/templates/registration/password_reset_confirm.html:12 +msgid "Enter new password" +msgstr "പുതിയ പാസ് വേര്‍ഡ് നല്കൂ" + +#: contrib/admin/templates/registration/password_reset_confirm.html:14 +msgid "" +"Please enter your new password twice so we can verify you typed it in " +"correctly." +msgstr "ദയവായി നിങ്ങളുടെ പുതിയ പാസ് വേര്‍ഡ് രണ്ടു തവണ നല്കണം. ശരിയായാണ് ടൈപ്പു ചെയ്തത് എന്നു ഉറപ്പിക്കാനാണ്." + +#: contrib/admin/templates/registration/password_reset_confirm.html:18 +msgid "New password:" +msgstr "പുതിയ പാസ് വേര്‍ഡ്:" + +#: contrib/admin/templates/registration/password_reset_confirm.html:20 +msgid "Confirm password:" +msgstr "പാസ് വേര്‍ഡ് ഉറപ്പാക്കൂ:" + +#: contrib/admin/templates/registration/password_reset_confirm.html:26 +msgid "Password reset unsuccessful" +msgstr "പാസ് വേര്‍ഡ് പുനസ്ഥാപിക്കല്‍ പരാജയം" + +#: contrib/admin/templates/registration/password_reset_confirm.html:28 +msgid "" +"The password reset link was invalid, possibly because it has already been " +"used. Please request a new password reset." +msgstr "പാസ് വേര്‍ഡ് പുനസ്ഥാപിക്കാന്‍ നല്കിയ ലിങ്ക് യോഗ്യമല്ല. ഒരു പക്ഷേ, അതു മുന്പ് തന്നെ ഉപയോഗിച്ചു " +"കഴിഞ്ഞതാവാം. പുതിയ ഒരു ലിങ്കിന് അപേക്ഷിക്കൂ." + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "പാസ് വേര്‍ഡ് പുനസ്ഥാപിക്കല്‍ വിജയം" + +#: contrib/admin/templates/registration/password_reset_done.html:12 +msgid "" +"We've e-mailed you instructions for setting your password to the e-mail " +"address you submitted. You should be receiving it shortly." +msgstr "നിങ്ങളുടെ പാസ് വേര്‍ഡ് പുനസ്ഥാപിക്കാനായി നിര്‍ദ്ദേശങ്ങള്‍ അടങ്ങിയ ഒരു ഈ-മെയില്‍ നിങ്ങള്‍ നല്കിയ" +"വിലാസത്തില്‍ അയച്ചിട്ടുണ്ട്. അത് നിങ്ങള്‍ക്ക് ഉടന്‍ ലഭിക്കേണ്ടതാണ്." + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "നിങ്ങള്‍ പാസ് വേര്‍ഡ് പുനസ്ഥാപിക്കാന്‍ അപേക്ഷിച്ചതു കൊണ്ടാണ് ഈ ഇ-മെയില്‍ നിങ്ങള്‍ക്ക് ലഭിക്കുന്നത്." + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "നിങ്ങളുടെ %(site_name)s എന്ന സൈറ്റിലെ അക്കൗണ്ടിനു വേണ്ടി." + +#: contrib/admin/templates/registration/password_reset_email.html:5 +msgid "Please go to the following page and choose a new password:" +msgstr "ദയവായി താഴെ പറയുന്ന പേജ് സന്ദര്‍ശിച്ച് പുതിയ പാസ് വേര്‍ഡ് തെരഞ്ഞെടുക്കുക:" + +#: contrib/admin/templates/registration/password_reset_email.html:9 +msgid "Your username, in case you've forgotten:" +msgstr "നിങ്ങള്‍ മറന്നെങ്കില്‍, നിങ്ങളുടെ യൂസര്‍ നാമം, :" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Thanks for using our site!" +msgstr "ഞങ്ങളുടെ സൈറ്റ് ഉപയോഗിച്ചതിന് നന്ദി!" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +#, python-format +msgid "The %(site_name)s team" +msgstr "" + +#: contrib/admin/templates/registration/password_reset_form.html:12 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll e-mail " +"instructions for setting a new one." +msgstr "പാസ് വേര്‍ഡ് മറന്നോ? നിങ്ങളുടെ ഇ-മെയില്‍ വിലാസം നല്കൂ, പുതിയ പാസ് വേര്‍ഡ് സ്ഥാപിക്കാനായി " +"ഞങ്ങള്‍ നിര്‍ദ്ദേശങ്ങള്‍ ആ വിലാസത്തില്‍ അയക്കാം." + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "ഇ-മെയില്‍ വിലാസം:" + +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "എന്റെ പാസ് വേര്‍ഡ് പുനസ്ഥാപിക്കൂ" + +#: contrib/admin/templatetags/admin_list.py:257 +msgid "All dates" +msgstr "എല്ലാ തീയതികളും" + +#: contrib/admin/views/main.py:65 +#, python-format +msgid "Select %s" +msgstr "%s തെരഞ്ഞെടുക്കൂ" + +#: contrib/admin/views/main.py:65 +#, python-format +msgid "Select %s to change" +msgstr "മാറ്റാനുള്ള %s തെരഞ്ഞെടുക്കൂ" + +#: contrib/admin/views/template.py:38 contrib/sites/models.py:38 +msgid "site" +msgstr "സൈറ്റ്" + +#: contrib/admin/views/template.py:40 +msgid "template" +msgstr "ടെമ്പ്ലേറ്റ്" + +#: contrib/admindocs/views.py:61 contrib/admindocs/views.py:63 +#: contrib/admindocs/views.py:65 +msgid "tag:" +msgstr "ടാഗ്:" + +#: contrib/admindocs/views.py:94 contrib/admindocs/views.py:96 +#: contrib/admindocs/views.py:98 +msgid "filter:" +msgstr "അരിപ്പ:" + +#: contrib/admindocs/views.py:158 contrib/admindocs/views.py:160 +#: contrib/admindocs/views.py:162 +msgid "view:" +msgstr "വ്യൂ" + +#: contrib/admindocs/views.py:190 +#, python-format +msgid "App %r not found" +msgstr "%r എന്ന App കണ്ടില്ല." + +#: contrib/admindocs/views.py:197 +#, python-format +msgid "Model %(model_name)r not found in app %(app_label)r" +msgstr "%(app_label)r എന്ന Appല്‍ %(model_name)r എന്ന മാത്രുക കണ്ടില്ല." + +#: contrib/admindocs/views.py:209 +#, python-format +msgid "the related `%(app_label)s.%(data_type)s` object" +msgstr "ബന്ധപ്പെട്ട `%(app_label)s.%(data_type)s` വസ്തു" + +#: contrib/admindocs/views.py:209 contrib/admindocs/views.py:228 +#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:247 +#: contrib/admindocs/views.py:261 contrib/admindocs/views.py:266 +msgid "model:" +msgstr "മാത്രുക:" + +#: contrib/admindocs/views.py:224 contrib/admindocs/views.py:256 +#, python-format +msgid "related `%(app_label)s.%(object_name)s` objects" +msgstr "ബന്ധപ്പെട്ട `%(app_label)s.%(object_name)s` വസ്തുക്കള്‍" + +#: contrib/admindocs/views.py:228 contrib/admindocs/views.py:261 +#, python-format +msgid "all %s" +msgstr "%s എല്ലാം" + +#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:266 +#, python-format +msgid "number of %s" +msgstr "%sന്റെ എണ്ണം" + +#: contrib/admindocs/views.py:271 +#, python-format +msgid "Fields on %s objects" +msgstr "%s വസ്തുക്കളിലെ വിവരങ്ങള്‍" + +#: contrib/admindocs/views.py:361 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s വിലാസ മാത്രുക (urlpattern object) ആണെന്ന് തോന്നുന്നില്ല." + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:3 +msgid "Bookmarklets" +msgstr "ബുക്ക് മാര്‍ക്കുകള്‍" + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:4 +msgid "Documentation bookmarklets" +msgstr "സഹായക്കുറിപ്പുകളുടെ ബുക്ക്മാര്‍ക്കുകള്‍" + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:8 +msgid "" +"\n" +"

      To install bookmarklets, drag the link to your bookmarks\n" +"toolbar, or right-click the link and add it to your bookmarks. Now you can\n" +"select the bookmarklet from any page in the site. Note that some of these\n" +"bookmarklets require you to be viewing the site from a computer designated\n" +"as \"internal\" (talk to your system administrator if you aren't sure if\n" +"your computer is \"internal\").

      \n" +msgstr "" +"\n" +"

      ബുക്ക്മാര്‍ക്ക്ലെറ്റുകള്‍ ഇന്‍സ്റ്റാള്‍ ചെയ്യാന്‍, ലിങ്കിനെ നിങ്ങളുടെ ബുക്ക്മാര്‍ക് ടൂള്‍ബാറിലേക്ക് \n" +"വലിച്ചിടുകയോ, ലിങ്കിന്‍മേല്‍ റൈറ്റ്ക്ളിക് ചെയ്ത് ബുക്ക്മാര്‍ക്കായി ചേര്‍ക്കുകയോ ചെയ്യുക. ഇനി സൈറ്റിലെ ഏതു പേജില്‍ നിന്നും\n" +" ഈ ബുക്ക്മാര്ക് തെരഞ്ഞെടുക്കാം. ചില ബുക്ക്മാര്‍ക്കുകള്‍ ഇന്റേണല്‍ ആയ കമ്പ്യൂട്ടറില്‍ നിന്നേ ലഭ്യമാവൂ എന്നു ശ്രദ്ധിക്കണം.\n" +"നിങ്ങളുടെ കംപ്യൂട്ടര്‍ അത്തരത്തില്‍ പെട്ടതാണോ എന്നറിയാന്‍ സിസ്റ്റം അഡ്മിനിസ്ട്രേട്ടറെ ബന്ധപ്പെടുക.

      \n" + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:18 +msgid "Documentation for this page" +msgstr "ഈ പേജിന്റെ സഹായക്കുറിപ്പുകള്‍" + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:19 +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "ഏതു പേജില്‍ നിന്നും അതിന്റെ ഉദ്ഭവമായ വ്യൂവിന്റെ സഹായക്കുറിപ്പിലേക്കു ചാടാന്‍" + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:21 +msgid "Show object ID" +msgstr "വസ്തുവിന്റെ ഐഡി കാണിക്കുക." + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:22 +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "ഒറ്റ വസ്തുവിനെ പ്രതിനിധീകരിക്കുന്ന പേജുകളുടെ ഉള്ളടക്കത്തിന്റെ തരവും തനതായ IDയും കാണിക്കുന്നു." + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:24 +msgid "Edit this object (current window)" +msgstr "ഈ വസ്തുവില് മാറ്റം വരുത്തുക (ഇതേ വിന്‍ഡോ)" + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:25 +msgid "Jumps to the admin page for pages that represent a single object." +msgstr "ഒറ്റ വസ്തുവിനെ പ്രതിനിധീകരിക്കുന്ന പേജുകള്‍ക്കുള്ള അഡ്മിന്‍ പേജിലേക്ക് ചാടുന്നു." + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:27 +msgid "Edit this object (new window)" +msgstr "ഈ വസ്തുവില് മാറ്റം വരുത്തുക (പുതിയ വിന്‍ഡോ)" + +#: contrib/admindocs/templates/admin_doc/bookmarklets.html:28 +msgid "As above, but opens the admin page in a new window." +msgstr "മുകളിലേതു പോലെ, പക്ഷെ, അഡ്മിന്‍ പേജ് പുതിയ വിന്ഡോവിലാണ് തുറക്കുക." + +#: contrib/auth/admin.py:29 +msgid "Personal info" +msgstr "വ്യക്തിപരമായ വിവരങ്ങള്‍" + +#: contrib/auth/admin.py:30 +msgid "Permissions" +msgstr "അനുമതികള്‍" + +#: contrib/auth/admin.py:31 +msgid "Important dates" +msgstr "പ്രധാന തീയതികള്‍" + +#: contrib/auth/admin.py:32 +msgid "Groups" +msgstr "ഗ്രൂപ്പുകള്‍" + +#: contrib/auth/admin.py:114 +msgid "Password changed successfully." +msgstr "പാസ് വേര്‍ഡ് മാറ്റിയിരിക്കുന്നു." + +#: contrib/auth/admin.py:124 +#, python-format +msgid "Change password: %s" +msgstr "പാസ് വേര്‍ഡ് മാറ്റുക: %s" + +#: contrib/auth/forms.py:14 contrib/auth/forms.py:48 contrib/auth/forms.py:60 +msgid "Username" +msgstr "യൂസര്‍ നാമം (ഉപയോക്ത്രു നാമം)" + +#: contrib/auth/forms.py:15 contrib/auth/forms.py:49 +msgid "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only." +msgstr "നിര്‍ബന്ധം. 30 ഓ അതില്‍ കുറവോ ചിഹ്നങ്ങള്‍. അക്ഷരങ്ങള്‍, അക്കങ്ങള്‍, " +"പിന്നെ @/./+/-/_എന്നിവയും മാത്രം." + +#: contrib/auth/forms.py:16 contrib/auth/forms.py:50 +msgid "This value may contain only letters, numbers and @/./+/-/_ characters." +msgstr "അക്ഷരങ്ങള്‍, അക്കങ്ങള്‍, പിന്നെ @/./+/-/_എന്നിവയും മാത്രം." + +#: contrib/auth/forms.py:18 +msgid "Password confirmation" +msgstr "പാസ് വേര്‍ഡ് ഉറപ്പാക്കല്‍" + +#: contrib/auth/forms.py:31 +msgid "A user with that username already exists." +msgstr "ആ പേരുള്ള ഒരു ഉപയോക്താവ് നിലവിലുണ്ട്." + +#: contrib/auth/forms.py:37 contrib/auth/forms.py:156 +#: contrib/auth/forms.py:198 +msgid "The two password fields didn't match." +msgstr "പാസ് വേര്‍ഡ് നല്കിയ കള്ളികള്‍ രണ്ടും തമ്മില്‍ സാമ്യമില്ല." + +#: contrib/auth/forms.py:83 +msgid "This account is inactive." +msgstr "ഈ അക്കൗണ്ട് മരവിപ്പിച്ചതാണ്." + +#: contrib/auth/forms.py:88 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "നിങ്ങളുടെ വെബ്-ബ്രൗസറിലെ കുക്കീസൊന്നും പ്രവര്‍ത്തിക്കുന്നില്ല. ഇതിലേക്ക് പ്രവേശിക്കാന്‍ അവ ആവശ്യമാണ്." + +#: contrib/auth/forms.py:101 +msgid "E-mail" +msgstr "ഇ-മെയില്‍" + +#: contrib/auth/forms.py:110 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "ആ ഇ-മെയിലുമായി ബന്ധപ്പെട്ട യൂസര് അക്കൗണ്ടൊന്നും നിലവിലില്ല. രജിസ്റ്റര്‍ ചെയ്തെന്നു തീര്‍ച്ചയാണോ?" + +#: contrib/auth/forms.py:136 +#, python-format +msgid "Password reset on %s" +msgstr "%s ലെ പാസ് വേര്‍ഡ് പുനസ്ഥാപിച്ചു." + +#: contrib/auth/forms.py:145 +msgid "New password confirmation" +msgstr "പുതിയ പാസ് വേര്‍ഡ് ഉറപ്പാക്കല്‍" + +#: contrib/auth/forms.py:178 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "നിങ്ങളുടെ പഴയ പാസ് വേര്‍ഡ് തെറ്റായാണ് നല്കിയത്. തിരുത്തുക." + +#: contrib/auth/models.py:66 contrib/auth/models.py:94 +msgid "name" +msgstr "പേര്" + +#: contrib/auth/models.py:68 +msgid "codename" +msgstr "കോഡ്-നാമം" + +#: contrib/auth/models.py:72 +msgid "permission" +msgstr "അനുമതി" + +#: contrib/auth/models.py:73 contrib/auth/models.py:95 +msgid "permissions" +msgstr "അനുമതികള്‍" + +#: contrib/auth/models.py:98 +msgid "group" +msgstr "ഗ്രൂപ്പ്" + +#: contrib/auth/models.py:99 contrib/auth/models.py:206 +msgid "groups" +msgstr "ഗ്രൂപ്പുകള്‍" + +#: contrib/auth/models.py:196 +msgid "username" +msgstr "യൂസര്‍ നാമം (ഉപയോക്ത്രു നാമം)" + +#: contrib/auth/models.py:196 +msgid "" +"Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters" +msgstr "നിര്‍ബന്ധം. 30 ഓ അതില്‍ കുറവോ ചിഹ്നങ്ങള്‍. അക്ഷരങ്ങള്‍, അക്കങ്ങള്‍, " +"പിന്നെ @/./+/-/_എന്നിവയും മാത്രം." + +#: contrib/auth/models.py:197 +msgid "first name" +msgstr "പേര് - ആദ്യഭാഗം" + +#: contrib/auth/models.py:198 +msgid "last name" +msgstr "പേര് - അന്ത്യഭാഗം" + +#: contrib/auth/models.py:199 +msgid "e-mail address" +msgstr "ഇ-മെയില്‍ വിലാസം" + +#: contrib/auth/models.py:200 +msgid "password" +msgstr "പാസ് വേര്‍ഡ്" + +#: contrib/auth/models.py:200 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "'[algo]$[salt]$[hexdigest]' അല്ലെങ്കില്‍ പാസ് വേര്‍ഡ് " +"മാറ്റാനുള്ള ഫോം ഉപയോഗിക്കുക." + +#: contrib/auth/models.py:201 +msgid "staff status" +msgstr "സ്റ്റാഫ് പദവി" + +#: contrib/auth/models.py:201 +msgid "Designates whether the user can log into this admin site." +msgstr "ഈ യൂസര്‍ക്ക് ഈ അഡ്മിന് സൈറ്റിലേക്ക് പ്രവേശിക്കാമോ എന്നു വ്യക്തമാക്കാന്‍" + +#: contrib/auth/models.py:202 +msgid "active" +msgstr "സജീവം" + +#: contrib/auth/models.py:202 +msgid "" +"Designates whether this user should be treated as active. Unselect this " +"instead of deleting accounts." +msgstr "ഈ യൂസര്‍ സജീവമാണോയെന്ന് വ്യക്തമാക്കുന്നു. അക്കൗണ്ട് ഡിലീറ്റ് ചെയ്യുന്നതിനു പകരം ഇത് ഒഴിവാക്കുക." + +#: contrib/auth/models.py:203 +msgid "superuser status" +msgstr "സൂപ്പര്‍-യൂസര്‍ പദവി" + +#: contrib/auth/models.py:203 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "ഈ ഉപയോക്താവിന് എടുത്തു പറയാതെ തന്നെ എല്ലാ അനുമതികളും ലഭിക്കുന്നതാണെന്ന് വ്യക്തമാക്കുന്നു" + +#: contrib/auth/models.py:204 +msgid "last login" +msgstr "അവസാനമായി ലോഗിന്‍ ചെയ്തതു" + +#: contrib/auth/models.py:205 +msgid "date joined" +msgstr "ചേര്‍ന്ന തീയതി" + +#: contrib/auth/models.py:207 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "ഈ ഉപയോക്താവിന് നേരിട്ട് ലഭിച്ചതു കൂടാതെ അവര്‍ അംഗമായ ഗ്രൂപ്പിന് ലഭിച്ച അനുമതികളും അനുഭവിക്കാം" + +#: contrib/auth/models.py:208 +msgid "user permissions" +msgstr "യൂസര്‍ (ഉപയോക്താവ്)നുള്ള അനുമതികള്‍" + +#: contrib/auth/models.py:212 contrib/comments/models.py:50 +#: contrib/comments/models.py:168 +msgid "user" +msgstr "യൂസര്‍ (ഉപയോക്താവ്)" + +#: contrib/auth/models.py:213 +msgid "users" +msgstr "യൂസേര്‍സ് (ഉപയോക്താക്കള്‍)" + +#: contrib/auth/models.py:394 +msgid "message" +msgstr "സന്ദേശം" + +#: contrib/auth/views.py:79 +msgid "Logged out" +msgstr "ലോഗ്-ഔട്ട് ചെയ്തു (പുറത്തിറങ്ങി)" + +#: contrib/auth/management/commands/createsuperuser.py:24 +#: core/validators.py:120 forms/fields.py:427 +msgid "Enter a valid e-mail address." +msgstr "ശരിയായ ഇ-മെയില്‍ വിലാസം നല്കുക." + +#: contrib/comments/admin.py:12 +msgid "Content" +msgstr "ഉള്ളടക്കം" + +#: contrib/comments/admin.py:15 +msgid "Metadata" +msgstr "" + +#: contrib/comments/admin.py:40 +msgid "flagged" +msgid_plural "flagged" +msgstr[0] "അടയാളപ്പെടുത്തി" +msgstr[1] "അടയാളപ്പെടുത്തി" + +#: contrib/comments/admin.py:41 +msgid "Flag selected comments" +msgstr "തെരഞ്ഞെടുത്ത അഭിപ്രായങ്ങള്‍ അടയാളപ്പെടുത്തുക" + +#: contrib/comments/admin.py:45 +msgid "approved" +msgid_plural "approved" +msgstr[0] "അംഗീകരിച്ചു" +msgstr[1] "അംഗീകരിച്ചു" + +#: contrib/comments/admin.py:46 +msgid "Approve selected comments" +msgstr "തെരഞ്ഞെടുത്ത അഭിപ്രായങ്ങള്‍ അംഗീകരിക്കുക" + +#: contrib/comments/admin.py:50 +msgid "removed" +msgid_plural "removed" +msgstr[0] "നീക്കം ചെയ്തു" +msgstr[1] "നീക്കം ചെയ്തു" + +#: contrib/comments/admin.py:51 +msgid "Remove selected comments" +msgstr "തെരഞ്ഞെടുത്ത അഭിപ്രായങ്ങള്‍ നീക്കം ചെയ്യുക" + +#: contrib/comments/admin.py:63 +#, python-format +msgid "1 comment was successfully %(action)s." +msgid_plural "%(count)s comments were successfully %(action)s." +msgstr[0] "1 അഭിപ്രായം വിജയകരമായി %(action)s." +msgstr[1] "%(count)s അഭിപ്രായങ്ങള്‍ വിജയകരമായി %(action)s." + +#: contrib/comments/feeds.py:13 +#, python-format +msgid "%(site_name)s comments" +msgstr "%(site_name)s അഭിപ്രായങ്ങള്‍" + +#: contrib/comments/feeds.py:23 +#, python-format +msgid "Latest comments on %(site_name)s" +msgstr "%(site_name)s ലെ ഏറ്റവും പുതിയ അഭിപ്രായങ്ങള്‍" + +#: contrib/comments/forms.py:93 +msgid "Name" +msgstr "പേര്" + +#: contrib/comments/forms.py:94 +msgid "Email address" +msgstr "ഇ-മെയില്‍ വിലാസം" + +#: contrib/comments/forms.py:95 contrib/flatpages/admin.py:8 +#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1101 +msgid "URL" +msgstr "URL(വെബ്-വിലാസം)" + +#: contrib/comments/forms.py:96 +msgid "Comment" +msgstr "അഭിപ്രായം" + +#: contrib/comments/forms.py:175 +#, python-format +msgid "Watch your mouth! The word %s is not allowed here." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "ശ്ശ്ശ്! %s എന്ന വാക്ക് ഇവിടെ അനുവദനീയമല്ല." +msgstr[1] "ശ്ശ്ശ്! %s എന്നീ വാക്കുകള്‍ ഇവിടെ അനുവദനീയമല്ല." + +#: contrib/comments/forms.py:182 +msgid "" +"If you enter anything in this field your comment will be treated as spam" +msgstr "ഈ കള്ളിയില്‍ എന്തെങ്കിലും രേഖപ്പെടുത്തിയാല്‍ നിങ്ങളുടെ അഭിപ്രായം സ്പാം ആയി കണക്കാക്കും" + +#: contrib/comments/models.py:22 contrib/contenttypes/models.py:81 +msgid "content type" +msgstr "ഏതു തരം ഉള്ളടക്കം" + +#: contrib/comments/models.py:24 +msgid "object ID" +msgstr "വസ്തു ID" + +#: contrib/comments/models.py:52 +msgid "user's name" +msgstr "യൂസറുടെ പേര്" + +#: contrib/comments/models.py:53 +msgid "user's email address" +msgstr "യൂസറുടെ ഇ-മെയില്‍ വിലാസം" + +#: contrib/comments/models.py:54 +msgid "user's URL" +msgstr "യൂസറുടെ URL" + +#: contrib/comments/models.py:56 contrib/comments/models.py:76 +#: contrib/comments/models.py:169 +msgid "comment" +msgstr "അഭിപ്രായം" + +#: contrib/comments/models.py:59 +msgid "date/time submitted" +msgstr "സമര്‍പ്പിച്ച തീയതി/സമയം" + +#: contrib/comments/models.py:60 db/models/fields/__init__.py:896 +msgid "IP address" +msgstr "IP വിലാസം" + +#: contrib/comments/models.py:61 +msgid "is public" +msgstr "പരസ്യമാണ്" + +#: contrib/comments/models.py:62 +msgid "" +"Uncheck this box to make the comment effectively disappear from the site." +msgstr "അഭിപ്രായം സൈറ്റില്‍ നിന്നും ഫലപ്രദമായി നീക്കം ചെയ്യാന്‍ ഈ ബോക്സിലെ ടിക് ഒഴിവാക്കുക." + +#: contrib/comments/models.py:64 +msgid "is removed" +msgstr "നീക്കം ചെയ്തു." + +#: contrib/comments/models.py:65 +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "അഭിപ്രായം അനുചിതമെങ്കില്‍ ഈ ബോക്സ് ടിക് ചെയ്യുക. \"ഈ അഭിപ്രായം നീക്കം ചെയ്തു \" എന്ന സന്ദേശം" +"ആയിരിക്കും പകരം കാണുക." + +#: contrib/comments/models.py:77 +msgid "comments" +msgstr "അഭിപ്രായങ്ങള്‍" + +#: contrib/comments/models.py:119 +msgid "" +"This comment was posted by an authenticated user and thus the name is read-" +"only." +msgstr "ഈ അഭിപ്രായം ഒരു അംഗീകൃത യൂസര്‍ രേഖപ്പെടുത്തിയതാണ്. അതിനാല്‍ പേര് വായിക്കാന്‍ മാത്രം." + +#: contrib/comments/models.py:128 +msgid "" +"This comment was posted by an authenticated user and thus the email is read-" +"only." +msgstr "ഈ അഭിപ്രായം ഒരു അംഗീകൃത യൂസര്‍ രേഖപ്പെടുത്തിയതാണ്. അതിനാല്‍ ഇ-മെയില്‍ വിലാസം വായിക്കാന്‍ മാത്രം." + +#: contrib/comments/models.py:153 +#, python-format +msgid "" +"Posted by %(user)s at %(date)s\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" +msgstr "" +"%(date)sന് %(user)s രേഖപ്പെടുത്തിയത്:\n" +"\n" +"%(comment)s\n" +"\n" +"http://%(domain)s%(url)s" + +#: contrib/comments/models.py:170 +msgid "flag" +msgstr "അടയാളം" + +#: contrib/comments/models.py:171 +msgid "date" +msgstr "തീയതി" + +#: contrib/comments/models.py:181 +msgid "comment flag" +msgstr "അഭിപ്രായ അടയാളം" + +#: contrib/comments/models.py:182 +msgid "comment flags" +msgstr "അഭിപ്രായ അടയാളങ്ങള്‍" + +#: contrib/comments/templates/comments/approve.html:4 +msgid "Approve a comment" +msgstr "അഭിപ്രായം അംഗീകരിക്കൂ" + +#: contrib/comments/templates/comments/approve.html:7 +msgid "Really make this comment public?" +msgstr "ശരിക്കും ഈ അഭിപ്രായം പരസ്യമാക്കണോ?" + +#: contrib/comments/templates/comments/approve.html:12 +msgid "Approve" +msgstr "അംഗീകരിക്കൂ" + +#: contrib/comments/templates/comments/approved.html:4 +msgid "Thanks for approving" +msgstr "അംഗീകരിച്ചതിനു നന്ദി" + +#: contrib/comments/templates/comments/approved.html:7 +#: contrib/comments/templates/comments/deleted.html:7 +#: contrib/comments/templates/comments/flagged.html:7 +msgid "" +"Thanks for taking the time to improve the quality of discussion on our site" +msgstr "നമ്മുടെ സൈറ്റിലെ ചര്‍ച്ചകളുടെ നിലവാരം ഉയര്‍ത്താന്‍ സമയം ചെലവഴിച്ചതിനു നന്ദി." + +#: contrib/comments/templates/comments/delete.html:4 +msgid "Remove a comment" +msgstr "അഭിപ്രായം നീക്കം ചെയ്യൂ" + +#: contrib/comments/templates/comments/delete.html:7 +msgid "Really remove this comment?" +msgstr "ഈ അഭിപ്രായം ശരിക്കും നീക്കം ചെയ്യണോ?" + +#: contrib/comments/templates/comments/deleted.html:4 +msgid "Thanks for removing" +msgstr "നീക്കം ചെയ്തതിനു നന്ദി" + +#: contrib/comments/templates/comments/flag.html:4 +msgid "Flag this comment" +msgstr "ഈ അഭിപ്രായം അടയാളപ്പെടുത്തൂ" + +#: contrib/comments/templates/comments/flag.html:7 +msgid "Really flag this comment?" +msgstr "ഈ അഭിപ്രായം ശരിക്കും അടയാളപ്പെടുത്തണോ?" + +#: contrib/comments/templates/comments/flag.html:12 +msgid "Flag" +msgstr "അടയാളം" + +#: contrib/comments/templates/comments/flagged.html:4 +msgid "Thanks for flagging" +msgstr "അടയാളപ്പെടുത്തിയതിനു നന്ദി" + +#: contrib/comments/templates/comments/form.html:17 +#: contrib/comments/templates/comments/preview.html:32 +msgid "Post" +msgstr "രേഖപ്പെടുത്തൂ" + +#: contrib/comments/templates/comments/form.html:18 +#: contrib/comments/templates/comments/preview.html:33 +msgid "Preview" +msgstr "അവലോകനം" + +#: contrib/comments/templates/comments/posted.html:4 +msgid "Thanks for commenting" +msgstr "അഭിപ്രായം രേഖപ്പെടുത്തിയതിനു നന്ദി" + +#: contrib/comments/templates/comments/posted.html:7 +msgid "Thank you for your comment" +msgstr "അഭിപ്രായത്തിനു നന്ദി" + +#: contrib/comments/templates/comments/preview.html:4 +#: contrib/comments/templates/comments/preview.html:13 +msgid "Preview your comment" +msgstr "അഭിപ്രായം അവലോകനം ചെയ്യുക" + +#: contrib/comments/templates/comments/preview.html:11 +msgid "Please correct the error below" +msgid_plural "Please correct the errors below" +msgstr[0] "ദയവായി താഴെ പറയുന്ന തെറ്റ് തിരുത്തുക" +msgstr[1] "ദയവായി താഴെ പറയുന്ന തെറ്റുകള്‍ തിരുത്തുക" + +#: contrib/comments/templates/comments/preview.html:16 +msgid "Post your comment" +msgstr "അഭിപ്രായം രേഖപ്പെടുത്തുക" + +#: contrib/comments/templates/comments/preview.html:16 +msgid "or make changes" +msgstr "അല്ലെങ്കില്‍ മാറ്റം വരുത്തുക." + +#: contrib/contenttypes/models.py:77 +msgid "python model class name" +msgstr "" + +#: contrib/contenttypes/models.py:82 +msgid "content types" +msgstr "ഉള്ളടക്കം ഏതൊക്കെ തരം" + +#: contrib/flatpages/admin.py:9 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "ഉദാ: '/about/contact/'. ആദ്യവും അവസാനവും സ്ളാഷുകള്‍ നിര്‍ബന്ധം." + +#: contrib/flatpages/admin.py:11 +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "ഇതില്‍ അക്ഷരങ്ങള്‍, അക്കങ്ങള്‍, അണ്ടര്‍സ്കോര്‍, വരകള്‍, സ്ളാഷുകള്‍ എന്നിവ മാത്രമേ പാടുള്ളൂ. " + +#: contrib/flatpages/admin.py:22 +msgid "Advanced options" +msgstr "ഉയര്‍ന്ന സൗകര്യങ്ങള്‍" + +#: contrib/flatpages/models.py:8 +msgid "title" +msgstr "ശീര്‍ഷകം" + +#: contrib/flatpages/models.py:9 +msgid "content" +msgstr "ഉള്ളടക്കം" + +#: contrib/flatpages/models.py:10 +msgid "enable comments" +msgstr "അഭിപ്രായങ്ങള്‍ അനുവദിക്കുക" + +#: contrib/flatpages/models.py:11 +msgid "template name" +msgstr "ടെമ്പ്ലേറ്റിന്റെ പേര്" + +#: contrib/flatpages/models.py:12 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"ഉദാ: 'flatpages/contact_page.html'. ഇതു നല്കിയില്ലെങ്കില്‍, 'flatpages/default.html' എന്ന " +"വിലാസം ഉപയോഗിക്കപ്പെടും." + +#: contrib/flatpages/models.py:13 +msgid "registration required" +msgstr "രജിസ്ട്രേഷന്‍ ആവശ്യമാണ്" + +#: contrib/flatpages/models.py:13 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "ഇതു ടിക് ചെയ്താല്‍ പിന്നെ ലോഗ്-ഇന്‍ ചെയ്ത യൂസര്‍ക്കു മാത്രമേ ഈ പേജ് കാണാന്‍ കഴിയൂ." + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "ഫ്ളാറ്റ് പേജ്" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "ഫ്ളാറ്റ് പേജുകള്‍" + +#: contrib/formtools/wizard.py:140 +msgid "" +"We apologize, but your form has expired. Please continue filling out the " +"form from this page." +msgstr "ക്ഷമിക്കണം, താങ്കളുടെ ഫോം കാലഹരണപ്പെട്ടു കഴിഞ്ഞു. ദയവായി ഈ പേജിലെ ഫോം പൂരിപ്പിച്ച് തുടരുക." + +#: contrib/gis/db/models/fields.py:50 +msgid "The base GIS field -- maps to the OpenGIS Specification Geometry type." +msgstr "" + +#: contrib/gis/db/models/fields.py:270 +msgid "Point" +msgstr "ബിന്ദു" + +#: contrib/gis/db/models/fields.py:274 +msgid "Line string" +msgstr "" + +#: contrib/gis/db/models/fields.py:278 +msgid "Polygon" +msgstr "ബഹുഭുജം" + +#: contrib/gis/db/models/fields.py:282 +msgid "Multi-point" +msgstr "ബഹുബിന്ദു" + +#: contrib/gis/db/models/fields.py:286 +msgid "Multi-line string" +msgstr "" + +#: contrib/gis/db/models/fields.py:290 +msgid "Multi polygon" +msgstr "ബഹു ബഹുഭുജം" + +#: contrib/gis/db/models/fields.py:294 +msgid "Geometry collection" +msgstr "ജ്യാമിതി ശേഖരം" + +#: contrib/gis/forms/fields.py:17 +msgid "No geometry value provided." +msgstr "" + +#: contrib/gis/forms/fields.py:18 +msgid "Invalid geometry value." +msgstr "" + +#: contrib/gis/forms/fields.py:19 +msgid "Invalid geometry type." +msgstr "" + +#: contrib/gis/forms/fields.py:20 +msgid "" +"An error occurred when transforming the geometry to the SRID of the geometry " +"form field." +msgstr "" + +#: contrib/humanize/templatetags/humanize.py:19 +msgid "th" +msgstr "ആം" + +#: contrib/humanize/templatetags/humanize.py:19 +msgid "st" +msgstr "ആം" + +#: contrib/humanize/templatetags/humanize.py:19 +msgid "nd" +msgstr "ആം" + +#: contrib/humanize/templatetags/humanize.py:19 +msgid "rd" +msgstr "ആം" + +#: contrib/humanize/templatetags/humanize.py:51 +#, python-format +msgid "%(value).1f million" +msgid_plural "%(value).1f million" +msgstr[0] "%(value).1f മില്ല്യണ്‍ (ദശലക്ഷം)" +msgstr[1] "%(value).1f മില്ല്യണ്‍ (ദശലക്ഷം)" + +#: contrib/humanize/templatetags/humanize.py:54 +#, python-format +msgid "%(value).1f billion" +msgid_plural "%(value).1f billion" +msgstr[0] "%(value).1f ബില്ല്യണ്‍ (ശതകോടി)" +msgstr[1] "%(value).1f ബില്ല്യണ്‍ (ശതകോടി)" + +#: contrib/humanize/templatetags/humanize.py:57 +#, python-format +msgid "%(value).1f trillion" +msgid_plural "%(value).1f trillion" +msgstr[0] "%(value).1f ട്രില്ല്യണ്‍ (ലക്ഷം കോടി)" +msgstr[1] "%(value).1f ട്രില്ല്യണ്‍ (ലക്ഷം കോടി)" + +#: contrib/humanize/templatetags/humanize.py:73 +msgid "one" +msgstr "ഒന്ന്" + +#: contrib/humanize/templatetags/humanize.py:73 +msgid "two" +msgstr "രണ്ട്" + +#: contrib/humanize/templatetags/humanize.py:73 +msgid "three" +msgstr "മൂന്ന്" + +#: contrib/humanize/templatetags/humanize.py:73 +msgid "four" +msgstr "നാല്" + +#: contrib/humanize/templatetags/humanize.py:73 +msgid "five" +msgstr "അഞ്ച്" + +#: contrib/humanize/templatetags/humanize.py:73 +msgid "six" +msgstr "ആറ്" + +#: contrib/humanize/templatetags/humanize.py:73 +msgid "seven" +msgstr "ഏഴ്" + +#: contrib/humanize/templatetags/humanize.py:73 +msgid "eight" +msgstr "എട്ട്" + +#: contrib/humanize/templatetags/humanize.py:73 +msgid "nine" +msgstr "ഒന്‍പത്" + +#: contrib/humanize/templatetags/humanize.py:93 +msgid "today" +msgstr "ഇന്ന്" + +#: contrib/humanize/templatetags/humanize.py:95 +msgid "tomorrow" +msgstr "നാളെ" + +#: contrib/humanize/templatetags/humanize.py:97 +msgid "yesterday" +msgstr "ഇന്നലെ" + +#: contrib/localflavor/ar/forms.py:28 +msgid "Enter a postal code in the format NNNN or ANNNNAAA." +msgstr "" + +#: contrib/localflavor/ar/forms.py:50 contrib/localflavor/br/forms.py:92 +#: contrib/localflavor/br/forms.py:131 contrib/localflavor/pe/forms.py:24 +#: contrib/localflavor/pe/forms.py:52 +msgid "This field requires only numbers." +msgstr "" + +#: contrib/localflavor/ar/forms.py:51 +msgid "This field requires 7 or 8 digits." +msgstr "" + +#: contrib/localflavor/ar/forms.py:80 +msgid "Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format." +msgstr "" + +#: contrib/localflavor/ar/forms.py:81 +msgid "Invalid CUIT." +msgstr "" + +#: contrib/localflavor/at/at_states.py:5 +msgid "Burgenland" +msgstr "" + +#: contrib/localflavor/at/at_states.py:6 +msgid "Carinthia" +msgstr "" + +#: contrib/localflavor/at/at_states.py:7 +msgid "Lower Austria" +msgstr "" + +#: contrib/localflavor/at/at_states.py:8 +msgid "Upper Austria" +msgstr "" + +#: contrib/localflavor/at/at_states.py:9 +msgid "Salzburg" +msgstr "" + +#: contrib/localflavor/at/at_states.py:10 +msgid "Styria" +msgstr "" + +#: contrib/localflavor/at/at_states.py:11 +msgid "Tyrol" +msgstr "" + +#: contrib/localflavor/at/at_states.py:12 +msgid "Vorarlberg" +msgstr "" + +#: contrib/localflavor/at/at_states.py:13 +msgid "Vienna" +msgstr "" + +#: contrib/localflavor/at/forms.py:20 contrib/localflavor/ch/forms.py:17 +#: contrib/localflavor/no/forms.py:13 +msgid "Enter a zip code in the format XXXX." +msgstr "" + +#: contrib/localflavor/at/forms.py:48 +msgid "Enter a valid Austrian Social Security Number in XXXX XXXXXX format." +msgstr "" + +#: contrib/localflavor/au/forms.py:17 +msgid "Enter a 4 digit post code." +msgstr "" + +#: contrib/localflavor/br/forms.py:17 +msgid "Enter a zip code in the format XXXXX-XXX." +msgstr "" + +#: contrib/localflavor/br/forms.py:26 +msgid "Phone numbers must be in XX-XXXX-XXXX format." +msgstr "" + +#: contrib/localflavor/br/forms.py:54 +msgid "" +"Select a valid brazilian state. That state is not one of the available " +"states." +msgstr "" + +#: contrib/localflavor/br/forms.py:90 +msgid "Invalid CPF number." +msgstr "" + +#: contrib/localflavor/br/forms.py:91 +msgid "This field requires at most 11 digits or 14 characters." +msgstr "" + +#: contrib/localflavor/br/forms.py:130 +msgid "Invalid CNPJ number." +msgstr "" + +#: contrib/localflavor/br/forms.py:132 +msgid "This field requires at least 14 digits" +msgstr "" + +#: contrib/localflavor/ca/forms.py:25 +msgid "Enter a postal code in the format XXX XXX." +msgstr "" + +#: contrib/localflavor/ca/forms.py:96 +msgid "Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format." +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:5 +msgid "Aargau" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:6 +msgid "Appenzell Innerrhoden" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:7 +msgid "Appenzell Ausserrhoden" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:8 +msgid "Basel-Stadt" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:9 +msgid "Basel-Land" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:10 +msgid "Berne" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:11 +msgid "Fribourg" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:12 +msgid "Geneva" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:13 +msgid "Glarus" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:14 +msgid "Graubuenden" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:15 +msgid "Jura" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:16 +msgid "Lucerne" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:17 +msgid "Neuchatel" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:18 +msgid "Nidwalden" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:19 +msgid "Obwalden" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:20 +msgid "Schaffhausen" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:21 +msgid "Schwyz" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:22 +msgid "Solothurn" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:23 +msgid "St. Gallen" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:24 +msgid "Thurgau" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:25 +msgid "Ticino" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:26 +msgid "Uri" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:27 +msgid "Valais" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:28 +msgid "Vaud" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:29 +msgid "Zug" +msgstr "" + +#: contrib/localflavor/ch/ch_states.py:30 +msgid "Zurich" +msgstr "" + +#: contrib/localflavor/ch/forms.py:65 +msgid "" +"Enter a valid Swiss identity or passport card number in X1234567<0 or " +"1234567890 format." +msgstr "" + +#: contrib/localflavor/cl/forms.py:30 +msgid "Enter a valid Chilean RUT." +msgstr "" + +#: contrib/localflavor/cl/forms.py:31 +msgid "Enter a valid Chilean RUT. The format is XX.XXX.XXX-X." +msgstr "" + +#: contrib/localflavor/cl/forms.py:32 +msgid "The Chilean RUT is not valid." +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:8 +msgid "Prague" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:9 +msgid "Central Bohemian Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:10 +msgid "South Bohemian Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:11 +msgid "Pilsen Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:12 +msgid "Carlsbad Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:13 +msgid "Usti Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:14 +msgid "Liberec Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:15 +msgid "Hradec Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:16 +msgid "Pardubice Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:17 +msgid "Vysocina Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:18 +msgid "South Moravian Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:19 +msgid "Olomouc Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:20 +msgid "Zlin Region" +msgstr "" + +#: contrib/localflavor/cz/cz_regions.py:21 +msgid "Moravian-Silesian Region" +msgstr "" + +#: contrib/localflavor/cz/forms.py:28 contrib/localflavor/sk/forms.py:30 +msgid "Enter a postal code in the format XXXXX or XXX XX." +msgstr "" + +#: contrib/localflavor/cz/forms.py:48 +msgid "Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX." +msgstr "" + +#: contrib/localflavor/cz/forms.py:49 +msgid "Invalid optional parameter Gender, valid values are 'f' and 'm'" +msgstr "" + +#: contrib/localflavor/cz/forms.py:50 +msgid "Enter a valid birth number." +msgstr "" + +#: contrib/localflavor/cz/forms.py:107 +msgid "Enter a valid IC number." +msgstr "" + +#: contrib/localflavor/de/de_states.py:5 +msgid "Baden-Wuerttemberg" +msgstr "" + +#: contrib/localflavor/de/de_states.py:6 +msgid "Bavaria" +msgstr "" + +#: contrib/localflavor/de/de_states.py:7 +msgid "Berlin" +msgstr "" + +#: contrib/localflavor/de/de_states.py:8 +msgid "Brandenburg" +msgstr "" + +#: contrib/localflavor/de/de_states.py:9 +msgid "Bremen" +msgstr "" + +#: contrib/localflavor/de/de_states.py:10 +msgid "Hamburg" +msgstr "" + +#: contrib/localflavor/de/de_states.py:11 +msgid "Hessen" +msgstr "" + +#: contrib/localflavor/de/de_states.py:12 +msgid "Mecklenburg-Western Pomerania" +msgstr "" + +#: contrib/localflavor/de/de_states.py:13 +msgid "Lower Saxony" +msgstr "" + +#: contrib/localflavor/de/de_states.py:14 +msgid "North Rhine-Westphalia" +msgstr "" + +#: contrib/localflavor/de/de_states.py:15 +msgid "Rhineland-Palatinate" +msgstr "" + +#: contrib/localflavor/de/de_states.py:16 +msgid "Saarland" +msgstr "" + +#: contrib/localflavor/de/de_states.py:17 +msgid "Saxony" +msgstr "" + +#: contrib/localflavor/de/de_states.py:18 +msgid "Saxony-Anhalt" +msgstr "" + +#: contrib/localflavor/de/de_states.py:19 +msgid "Schleswig-Holstein" +msgstr "" + +#: contrib/localflavor/de/de_states.py:20 +msgid "Thuringia" +msgstr "" + +#: contrib/localflavor/de/forms.py:15 contrib/localflavor/fi/forms.py:13 +#: contrib/localflavor/fr/forms.py:16 +msgid "Enter a zip code in the format XXXXX." +msgstr "" + +#: contrib/localflavor/de/forms.py:42 +msgid "" +"Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X " +"format." +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:5 +msgid "Arava" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:6 +msgid "Albacete" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:7 +msgid "Alacant" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:8 +msgid "Almeria" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:9 +msgid "Avila" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:10 +msgid "Badajoz" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:11 +msgid "Illes Balears" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:12 +msgid "Barcelona" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:13 +msgid "Burgos" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:14 +msgid "Caceres" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:15 +msgid "Cadiz" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:16 +msgid "Castello" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:17 +msgid "Ciudad Real" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:18 +msgid "Cordoba" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:19 +msgid "A Coruna" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:20 +msgid "Cuenca" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:21 +msgid "Girona" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:22 +msgid "Granada" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:23 +msgid "Guadalajara" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:24 +msgid "Guipuzkoa" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:25 +msgid "Huelva" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:26 +msgid "Huesca" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:27 +msgid "Jaen" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:28 +msgid "Leon" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:29 +msgid "Lleida" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:30 +#: contrib/localflavor/es/es_regions.py:17 +msgid "La Rioja" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:31 +msgid "Lugo" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:32 +#: contrib/localflavor/es/es_regions.py:18 +msgid "Madrid" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:33 +msgid "Malaga" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:34 +msgid "Murcia" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:35 +msgid "Navarre" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:36 +msgid "Ourense" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:37 +msgid "Asturias" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:38 +msgid "Palencia" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:39 +msgid "Las Palmas" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:40 +msgid "Pontevedra" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:41 +msgid "Salamanca" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:42 +msgid "Santa Cruz de Tenerife" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:43 +#: contrib/localflavor/es/es_regions.py:11 +msgid "Cantabria" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:44 +msgid "Segovia" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:45 +msgid "Seville" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:46 +msgid "Soria" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:47 +msgid "Tarragona" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:48 +msgid "Teruel" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:49 +msgid "Toledo" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:50 +msgid "Valencia" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:51 +msgid "Valladolid" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:52 +msgid "Bizkaia" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:53 +msgid "Zamora" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:54 +msgid "Zaragoza" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:55 +msgid "Ceuta" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:56 +msgid "Melilla" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:5 +msgid "Andalusia" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:6 +msgid "Aragon" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:7 +msgid "Principality of Asturias" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:8 +msgid "Balearic Islands" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:9 +msgid "Basque Country" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:10 +msgid "Canary Islands" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:12 +msgid "Castile-La Mancha" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:13 +msgid "Castile and Leon" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:14 +msgid "Catalonia" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:15 +msgid "Extremadura" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:16 +msgid "Galicia" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:19 +msgid "Region of Murcia" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:20 +msgid "Foral Community of Navarre" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:21 +msgid "Valencian Community" +msgstr "" + +#: contrib/localflavor/es/forms.py:20 +msgid "Enter a valid postal code in the range and format 01XXX - 52XXX." +msgstr "" + +#: contrib/localflavor/es/forms.py:40 +msgid "" +"Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or " +"9XXXXXXXX." +msgstr "" + +#: contrib/localflavor/es/forms.py:67 +msgid "Please enter a valid NIF, NIE, or CIF." +msgstr "" + +#: contrib/localflavor/es/forms.py:68 +msgid "Please enter a valid NIF or NIE." +msgstr "" + +#: contrib/localflavor/es/forms.py:69 +msgid "Invalid checksum for NIF." +msgstr "" + +#: contrib/localflavor/es/forms.py:70 +msgid "Invalid checksum for NIE." +msgstr "" + +#: contrib/localflavor/es/forms.py:71 +msgid "Invalid checksum for CIF." +msgstr "" + +#: contrib/localflavor/es/forms.py:143 +msgid "" +"Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX." +msgstr "" + +#: contrib/localflavor/es/forms.py:144 +msgid "Invalid checksum for bank account number." +msgstr "" + +#: contrib/localflavor/fi/forms.py:29 +msgid "Enter a valid Finnish social security number." +msgstr "" + +#: contrib/localflavor/fr/forms.py:31 +msgid "Phone numbers must be in 0X XX XX XX XX format." +msgstr "" + +#: contrib/localflavor/id/forms.py:28 +msgid "Enter a valid post code" +msgstr "" + +#: contrib/localflavor/id/forms.py:68 contrib/localflavor/nl/forms.py:53 +msgid "Enter a valid phone number" +msgstr "" + +#: contrib/localflavor/id/forms.py:107 +msgid "Enter a valid vehicle license plate number" +msgstr "" + +#: contrib/localflavor/id/forms.py:170 +msgid "Enter a valid NIK/KTP number" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:9 +#: contrib/localflavor/id/id_choices.py:73 +msgid "Bali" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:10 +#: contrib/localflavor/id/id_choices.py:45 +msgid "Banten" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:11 +#: contrib/localflavor/id/id_choices.py:54 +msgid "Bengkulu" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:12 +#: contrib/localflavor/id/id_choices.py:47 +msgid "Yogyakarta" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:13 +#: contrib/localflavor/id/id_choices.py:51 +msgid "Jakarta" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:14 +#: contrib/localflavor/id/id_choices.py:75 +msgid "Gorontalo" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:15 +#: contrib/localflavor/id/id_choices.py:57 +msgid "Jambi" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:16 +msgid "Jawa Barat" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:17 +msgid "Jawa Tengah" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:18 +msgid "Jawa Timur" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:19 +#: contrib/localflavor/id/id_choices.py:88 +msgid "Kalimantan Barat" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:20 +#: contrib/localflavor/id/id_choices.py:66 +msgid "Kalimantan Selatan" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:21 +#: contrib/localflavor/id/id_choices.py:89 +msgid "Kalimantan Tengah" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:22 +#: contrib/localflavor/id/id_choices.py:90 +msgid "Kalimantan Timur" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:23 +msgid "Kepulauan Bangka-Belitung" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:24 +#: contrib/localflavor/id/id_choices.py:62 +msgid "Kepulauan Riau" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:25 +#: contrib/localflavor/id/id_choices.py:55 +msgid "Lampung" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:26 +#: contrib/localflavor/id/id_choices.py:70 +msgid "Maluku" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:27 +#: contrib/localflavor/id/id_choices.py:71 +msgid "Maluku Utara" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:28 +#: contrib/localflavor/id/id_choices.py:59 +msgid "Nanggroe Aceh Darussalam" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:29 +msgid "Nusa Tenggara Barat" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:30 +msgid "Nusa Tenggara Timur" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:31 +msgid "Papua" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:32 +msgid "Papua Barat" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:33 +#: contrib/localflavor/id/id_choices.py:60 +msgid "Riau" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:34 +#: contrib/localflavor/id/id_choices.py:68 +msgid "Sulawesi Barat" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:35 +#: contrib/localflavor/id/id_choices.py:69 +msgid "Sulawesi Selatan" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:36 +#: contrib/localflavor/id/id_choices.py:76 +msgid "Sulawesi Tengah" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:37 +#: contrib/localflavor/id/id_choices.py:79 +msgid "Sulawesi Tenggara" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:38 +msgid "Sulawesi Utara" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:39 +#: contrib/localflavor/id/id_choices.py:52 +msgid "Sumatera Barat" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:40 +#: contrib/localflavor/id/id_choices.py:56 +msgid "Sumatera Selatan" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:41 +#: contrib/localflavor/id/id_choices.py:58 +msgid "Sumatera Utara" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:46 +msgid "Magelang" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:48 +msgid "Surakarta - Solo" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:49 +msgid "Madiun" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:50 +msgid "Kediri" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:53 +msgid "Tapanuli" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:61 +msgid "Kepulauan Bangka Belitung" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:63 +msgid "Corps Consulate" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:64 +msgid "Corps Diplomatic" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:65 +msgid "Bandung" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:67 +msgid "Sulawesi Utara Daratan" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:72 +msgid "NTT - Timor" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:74 +msgid "Sulawesi Utara Kepulauan" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:77 +msgid "NTB - Lombok" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:78 +msgid "Papua dan Papua Barat" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:80 +msgid "Cirebon" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:81 +msgid "NTB - Sumbawa" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:82 +msgid "NTT - Flores" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:83 +msgid "NTT - Sumba" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:84 +msgid "Bogor" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:85 +msgid "Pekalongan" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:86 +msgid "Semarang" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:87 +msgid "Pati" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:91 +msgid "Surabaya" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:92 +msgid "Madura" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:93 +msgid "Malang" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:94 +msgid "Jember" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:95 +msgid "Banyumas" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:96 +msgid "Federal Government" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:97 +msgid "Bojonegoro" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:98 +msgid "Purwakarta" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:99 +msgid "Sidoarjo" +msgstr "" + +#: contrib/localflavor/id/id_choices.py:100 +msgid "Garut" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:8 +msgid "Antrim" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:9 +msgid "Armagh" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:10 +msgid "Carlow" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:11 +msgid "Cavan" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:12 +msgid "Clare" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:13 +msgid "Cork" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:14 +msgid "Derry" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:15 +msgid "Donegal" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:16 +msgid "Down" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:17 +msgid "Dublin" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:18 +msgid "Fermanagh" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:19 +msgid "Galway" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:20 +msgid "Kerry" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:21 +msgid "Kildare" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:22 +msgid "Kilkenny" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:23 +msgid "Laois" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:24 +msgid "Leitrim" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:25 +msgid "Limerick" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:26 +msgid "Longford" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:27 +msgid "Louth" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:28 +msgid "Mayo" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:29 +msgid "Meath" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:30 +msgid "Monaghan" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:31 +msgid "Offaly" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:32 +msgid "Roscommon" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:33 +msgid "Sligo" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:34 +msgid "Tipperary" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:35 +msgid "Tyrone" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:36 +msgid "Waterford" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:37 +msgid "Westmeath" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:38 +msgid "Wexford" +msgstr "" + +#: contrib/localflavor/ie/ie_counties.py:39 +msgid "Wicklow" +msgstr "" + +#: contrib/localflavor/in_/forms.py:15 +msgid "Enter a zip code in the format XXXXXXX." +msgstr "പിന്‍-കോഡ് XXXXXXX എന്ന മാത്രുകയില്‍ നല്കുക." + +#: contrib/localflavor/is_/forms.py:18 +msgid "" +"Enter a valid Icelandic identification number. The format is XXXXXX-XXXX." +msgstr "" + +#: contrib/localflavor/is_/forms.py:19 +msgid "The Icelandic identification number is not valid." +msgstr "" + +#: contrib/localflavor/it/forms.py:15 +msgid "Enter a valid zip code." +msgstr "" + +#: contrib/localflavor/it/forms.py:44 +msgid "Enter a valid Social Security number." +msgstr "" + +#: contrib/localflavor/it/forms.py:69 +msgid "Enter a valid VAT number." +msgstr "" + +#: contrib/localflavor/jp/forms.py:16 +msgid "Enter a postal code in the format XXXXXXX or XXX-XXXX." +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:4 +msgid "Hokkaido" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:5 +msgid "Aomori" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:6 +msgid "Iwate" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:7 +msgid "Miyagi" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:8 +msgid "Akita" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:9 +msgid "Yamagata" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:10 +msgid "Fukushima" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:11 +msgid "Ibaraki" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:12 +msgid "Tochigi" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:13 +msgid "Gunma" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:14 +msgid "Saitama" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:15 +msgid "Chiba" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:16 +msgid "Tokyo" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:17 +msgid "Kanagawa" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:18 +msgid "Yamanashi" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:19 +msgid "Nagano" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:20 +msgid "Niigata" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:21 +msgid "Toyama" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:22 +msgid "Ishikawa" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:23 +msgid "Fukui" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:24 +msgid "Gifu" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:25 +msgid "Shizuoka" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:26 +msgid "Aichi" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:27 +msgid "Mie" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:28 +msgid "Shiga" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:29 +msgid "Kyoto" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:30 +msgid "Osaka" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:31 +msgid "Hyogo" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:32 +msgid "Nara" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:33 +msgid "Wakayama" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:34 +msgid "Tottori" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:35 +msgid "Shimane" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:36 +msgid "Okayama" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:37 +msgid "Hiroshima" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:38 +msgid "Yamaguchi" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:39 +msgid "Tokushima" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:40 +msgid "Kagawa" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:41 +msgid "Ehime" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:42 +msgid "Kochi" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:43 +msgid "Fukuoka" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:44 +msgid "Saga" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:45 +msgid "Nagasaki" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:46 +msgid "Kumamoto" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:47 +msgid "Oita" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:48 +msgid "Miyazaki" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:49 +msgid "Kagoshima" +msgstr "" + +#: contrib/localflavor/jp/jp_prefectures.py:50 +msgid "Okinawa" +msgstr "" + +#: contrib/localflavor/kw/forms.py:25 +msgid "Enter a valid Kuwaiti Civil ID number" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:12 +msgid "Aguascalientes" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:13 +msgid "Baja California" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:14 +msgid "Baja California Sur" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:15 +msgid "Campeche" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:16 +msgid "Chihuahua" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:17 +msgid "Chiapas" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:18 +msgid "Coahuila" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:19 +msgid "Colima" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:20 +msgid "Distrito Federal" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:21 +msgid "Durango" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:22 +msgid "Guerrero" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:23 +msgid "Guanajuato" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:24 +msgid "Hidalgo" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:25 +msgid "Jalisco" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:26 +msgid "Estado de México" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:27 +msgid "Michoacán" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:28 +msgid "Morelos" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:29 +msgid "Nayarit" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:30 +msgid "Nuevo León" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:31 +msgid "Oaxaca" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:32 +msgid "Puebla" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:33 +msgid "Querétaro" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:34 +msgid "Quintana Roo" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:35 +msgid "Sinaloa" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:36 +msgid "San Luis Potosí" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:37 +msgid "Sonora" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:38 +msgid "Tabasco" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:39 +msgid "Tamaulipas" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:40 +msgid "Tlaxcala" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:41 +msgid "Veracruz" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:42 +msgid "Yucatán" +msgstr "" + +#: contrib/localflavor/mx/mx_states.py:43 +msgid "Zacatecas" +msgstr "" + +#: contrib/localflavor/nl/forms.py:22 +msgid "Enter a valid postal code" +msgstr "" + +#: contrib/localflavor/nl/forms.py:79 +msgid "Enter a valid SoFi number" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:4 +msgid "Drenthe" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:5 +msgid "Flevoland" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:6 +msgid "Friesland" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:7 +msgid "Gelderland" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:8 +msgid "Groningen" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:9 +msgid "Limburg" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:10 +msgid "Noord-Brabant" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:11 +msgid "Noord-Holland" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:12 +msgid "Overijssel" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:13 +msgid "Utrecht" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:14 +msgid "Zeeland" +msgstr "" + +#: contrib/localflavor/nl/nl_provinces.py:15 +msgid "Zuid-Holland" +msgstr "" + +#: contrib/localflavor/no/forms.py:34 +msgid "Enter a valid Norwegian social security number." +msgstr "" + +#: contrib/localflavor/pe/forms.py:25 +msgid "This field requires 8 digits." +msgstr "" + +#: contrib/localflavor/pe/forms.py:53 +msgid "This field requires 11 digits." +msgstr "" + +#: contrib/localflavor/pl/forms.py:38 +msgid "National Identification Number consists of 11 digits." +msgstr "" + +#: contrib/localflavor/pl/forms.py:39 +msgid "Wrong checksum for the National Identification Number." +msgstr "" + +#: contrib/localflavor/pl/forms.py:71 +msgid "" +"Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX." +msgstr "" + +#: contrib/localflavor/pl/forms.py:72 +msgid "Wrong checksum for the Tax Number (NIP)." +msgstr "" + +#: contrib/localflavor/pl/forms.py:109 +msgid "National Business Register Number (REGON) consists of 9 or 14 digits." +msgstr "" + +#: contrib/localflavor/pl/forms.py:110 +msgid "Wrong checksum for the National Business Register Number (REGON)." +msgstr "" + +#: contrib/localflavor/pl/forms.py:148 +msgid "Enter a postal code in the format XX-XXX." +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:8 +msgid "Lower Silesia" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:9 +msgid "Kuyavia-Pomerania" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:10 +msgid "Lublin" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:11 +msgid "Lubusz" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:12 +msgid "Lodz" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:13 +msgid "Lesser Poland" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:14 +msgid "Masovia" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:15 +msgid "Opole" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:16 +msgid "Subcarpatia" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:17 +msgid "Podlasie" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:18 +msgid "Pomerania" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:19 +msgid "Silesia" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:20 +msgid "Swietokrzyskie" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:21 +msgid "Warmia-Masuria" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:22 +msgid "Greater Poland" +msgstr "" + +#: contrib/localflavor/pl/pl_voivodeships.py:23 +msgid "West Pomerania" +msgstr "" + +#: contrib/localflavor/pt/forms.py:17 +msgid "Enter a zip code in the format XXXX-XXX." +msgstr "" + +#: contrib/localflavor/pt/forms.py:37 +msgid "Phone numbers must have 9 digits, or start by + or 00." +msgstr "" + +#: contrib/localflavor/ro/forms.py:19 +msgid "Enter a valid CIF." +msgstr "" + +#: contrib/localflavor/ro/forms.py:56 +msgid "Enter a valid CNP." +msgstr "" + +#: contrib/localflavor/ro/forms.py:141 +msgid "Enter a valid IBAN in ROXX-XXXX-XXXX-XXXX-XXXX-XXXX format" +msgstr "" + +#: contrib/localflavor/ro/forms.py:171 +msgid "Phone numbers must be in XXXX-XXXXXX format." +msgstr "" + +#: contrib/localflavor/ro/forms.py:194 +msgid "Enter a valid postal code in the format XXXXXX" +msgstr "" + +#: contrib/localflavor/se/forms.py:50 +msgid "Enter a valid Swedish organisation number." +msgstr "" + +#: contrib/localflavor/se/forms.py:107 +msgid "Enter a valid Swedish personal identity number." +msgstr "" + +#: contrib/localflavor/se/forms.py:108 +msgid "Co-ordination numbers are not allowed." +msgstr "" + +#: contrib/localflavor/se/forms.py:150 +msgid "Enter a Swedish postal code in the format XXXXX." +msgstr "" + +#: contrib/localflavor/se/se_counties.py:15 +msgid "Stockholm" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:16 +msgid "Västerbotten" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:17 +msgid "Norrbotten" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:18 +msgid "Uppsala" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:19 +msgid "Södermanland" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:20 +msgid "Östergötland" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:21 +msgid "Jönköping" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:22 +msgid "Kronoberg" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:23 +msgid "Kalmar" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:24 +msgid "Gotland" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:25 +msgid "Blekinge" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:26 +msgid "Skåne" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:27 +msgid "Halland" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:28 +msgid "Västra Götaland" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:29 +msgid "Värmland" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:30 +msgid "Örebro" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:31 +msgid "Västmanland" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:32 +msgid "Dalarna" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:33 +msgid "Gävleborg" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:34 +msgid "Västernorrland" +msgstr "" + +#: contrib/localflavor/se/se_counties.py:35 +msgid "Jämtland" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:8 +msgid "Banska Bystrica" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:9 +msgid "Banska Stiavnica" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:10 +msgid "Bardejov" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:11 +msgid "Banovce nad Bebravou" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:12 +msgid "Brezno" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:13 +msgid "Bratislava I" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:14 +msgid "Bratislava II" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:15 +msgid "Bratislava III" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:16 +msgid "Bratislava IV" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:17 +msgid "Bratislava V" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:18 +msgid "Bytca" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:19 +msgid "Cadca" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:20 +msgid "Detva" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:21 +msgid "Dolny Kubin" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:22 +msgid "Dunajska Streda" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:23 +msgid "Galanta" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:24 +msgid "Gelnica" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:25 +msgid "Hlohovec" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:26 +msgid "Humenne" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:27 +msgid "Ilava" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:28 +msgid "Kezmarok" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:29 +msgid "Komarno" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:30 +msgid "Kosice I" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:31 +msgid "Kosice II" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:32 +msgid "Kosice III" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:33 +msgid "Kosice IV" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:34 +msgid "Kosice - okolie" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:35 +msgid "Krupina" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:36 +msgid "Kysucke Nove Mesto" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:37 +msgid "Levice" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:38 +msgid "Levoca" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:39 +msgid "Liptovsky Mikulas" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:40 +msgid "Lucenec" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:41 +msgid "Malacky" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:42 +msgid "Martin" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:43 +msgid "Medzilaborce" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:44 +msgid "Michalovce" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:45 +msgid "Myjava" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:46 +msgid "Namestovo" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:47 +msgid "Nitra" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:48 +msgid "Nove Mesto nad Vahom" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:49 +msgid "Nove Zamky" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:50 +msgid "Partizanske" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:51 +msgid "Pezinok" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:52 +msgid "Piestany" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:53 +msgid "Poltar" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:54 +msgid "Poprad" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:55 +msgid "Povazska Bystrica" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:56 +msgid "Presov" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:57 +msgid "Prievidza" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:58 +msgid "Puchov" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:59 +msgid "Revuca" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:60 +msgid "Rimavska Sobota" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:61 +msgid "Roznava" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:62 +msgid "Ruzomberok" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:63 +msgid "Sabinov" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:64 +msgid "Senec" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:65 +msgid "Senica" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:66 +msgid "Skalica" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:67 +msgid "Snina" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:68 +msgid "Sobrance" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:69 +msgid "Spisska Nova Ves" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:70 +msgid "Stara Lubovna" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:71 +msgid "Stropkov" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:72 +msgid "Svidnik" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:73 +msgid "Sala" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:74 +msgid "Topolcany" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:75 +msgid "Trebisov" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:76 +msgid "Trencin" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:77 +msgid "Trnava" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:78 +msgid "Turcianske Teplice" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:79 +msgid "Tvrdosin" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:80 +msgid "Velky Krtis" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:81 +msgid "Vranov nad Toplou" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:82 +msgid "Zlate Moravce" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:83 +msgid "Zvolen" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:84 +msgid "Zarnovica" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:85 +msgid "Ziar nad Hronom" +msgstr "" + +#: contrib/localflavor/sk/sk_districts.py:86 +msgid "Zilina" +msgstr "" + +#: contrib/localflavor/sk/sk_regions.py:8 +msgid "Banska Bystrica region" +msgstr "" + +#: contrib/localflavor/sk/sk_regions.py:9 +msgid "Bratislava region" +msgstr "" + +#: contrib/localflavor/sk/sk_regions.py:10 +msgid "Kosice region" +msgstr "" + +#: contrib/localflavor/sk/sk_regions.py:11 +msgid "Nitra region" +msgstr "" + +#: contrib/localflavor/sk/sk_regions.py:12 +msgid "Presov region" +msgstr "" + +#: contrib/localflavor/sk/sk_regions.py:13 +msgid "Trencin region" +msgstr "" + +#: contrib/localflavor/sk/sk_regions.py:14 +msgid "Trnava region" +msgstr "" + +#: contrib/localflavor/sk/sk_regions.py:15 +msgid "Zilina region" +msgstr "" + +#: contrib/localflavor/uk/forms.py:21 +msgid "Enter a valid postcode." +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:11 +msgid "Bedfordshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:12 +msgid "Buckinghamshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:14 +msgid "Cheshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:15 +msgid "Cornwall and Isles of Scilly" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:16 +msgid "Cumbria" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:17 +msgid "Derbyshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:18 +msgid "Devon" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:19 +msgid "Dorset" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:20 +msgid "Durham" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:21 +msgid "East Sussex" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:22 +msgid "Essex" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:23 +msgid "Gloucestershire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:24 +msgid "Greater London" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:25 +msgid "Greater Manchester" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:26 +msgid "Hampshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:27 +msgid "Hertfordshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:28 +msgid "Kent" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:29 +msgid "Lancashire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:30 +msgid "Leicestershire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:31 +msgid "Lincolnshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:32 +msgid "Merseyside" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:33 +msgid "Norfolk" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:34 +msgid "North Yorkshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:35 +msgid "Northamptonshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:36 +msgid "Northumberland" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:37 +msgid "Nottinghamshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:38 +msgid "Oxfordshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:39 +msgid "Shropshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:40 +msgid "Somerset" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:41 +msgid "South Yorkshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:42 +msgid "Staffordshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:43 +msgid "Suffolk" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:44 +msgid "Surrey" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:45 +msgid "Tyne and Wear" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:46 +msgid "Warwickshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:47 +msgid "West Midlands" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:48 +msgid "West Sussex" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:49 +msgid "West Yorkshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:50 +msgid "Wiltshire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:51 +msgid "Worcestershire" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:55 +msgid "County Antrim" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:56 +msgid "County Armagh" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:57 +msgid "County Down" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:58 +msgid "County Fermanagh" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:59 +msgid "County Londonderry" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:60 +msgid "County Tyrone" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:64 +msgid "Clwyd" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:65 +msgid "Dyfed" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:66 +msgid "Gwent" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:67 +msgid "Gwynedd" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:68 +msgid "Mid Glamorgan" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:69 +msgid "Powys" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:70 +msgid "South Glamorgan" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:71 +msgid "West Glamorgan" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:75 +msgid "Borders" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:76 +msgid "Central Scotland" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:77 +msgid "Dumfries and Galloway" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:78 +msgid "Fife" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:79 +msgid "Grampian" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:80 +msgid "Highland" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:81 +msgid "Lothian" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:82 +msgid "Orkney Islands" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:83 +msgid "Shetland Islands" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:84 +msgid "Strathclyde" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:85 +msgid "Tayside" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:86 +msgid "Western Isles" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:90 +msgid "England" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:91 +msgid "Northern Ireland" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:92 +msgid "Scotland" +msgstr "" + +#: contrib/localflavor/uk/uk_regions.py:93 +msgid "Wales" +msgstr "" + +#: contrib/localflavor/us/forms.py:17 +msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX." +msgstr "" + +#: contrib/localflavor/us/forms.py:26 +msgid "Phone numbers must be in XXX-XXX-XXXX format." +msgstr "" + +#: contrib/localflavor/us/forms.py:55 +msgid "Enter a valid U.S. Social Security number in XXX-XX-XXXX format." +msgstr "" + +#: contrib/localflavor/us/forms.py:88 +msgid "Enter a U.S. state or territory." +msgstr "" + +#: contrib/localflavor/us/models.py:8 +msgid "U.S. state (two uppercase letters)" +msgstr "" + +#: contrib/localflavor/us/models.py:17 +msgid "Phone number" +msgstr "" + +#: contrib/localflavor/uy/forms.py:28 +msgid "Enter a valid CI number in X.XXX.XXX-X,XXXXXXX-X or XXXXXXXX format." +msgstr "" + +#: contrib/localflavor/uy/forms.py:30 +msgid "Enter a valid CI number." +msgstr "" + +#: contrib/localflavor/za/forms.py:21 +msgid "Enter a valid South African ID number" +msgstr "" + +#: contrib/localflavor/za/forms.py:55 +msgid "Enter a valid South African postal code" +msgstr "" + +#: contrib/localflavor/za/za_provinces.py:4 +msgid "Eastern Cape" +msgstr "" + +#: contrib/localflavor/za/za_provinces.py:5 +msgid "Free State" +msgstr "" + +#: contrib/localflavor/za/za_provinces.py:6 +msgid "Gauteng" +msgstr "" + +#: contrib/localflavor/za/za_provinces.py:7 +msgid "KwaZulu-Natal" +msgstr "" + +#: contrib/localflavor/za/za_provinces.py:8 +msgid "Limpopo" +msgstr "" + +#: contrib/localflavor/za/za_provinces.py:9 +msgid "Mpumalanga" +msgstr "" + +#: contrib/localflavor/za/za_provinces.py:10 +msgid "Northern Cape" +msgstr "" + +#: contrib/localflavor/za/za_provinces.py:11 +msgid "North West" +msgstr "" + +#: contrib/localflavor/za/za_provinces.py:12 +msgid "Western Cape" +msgstr "" + +#: contrib/messages/tests/base.py:101 +msgid "lazy message" +msgstr "അലസ സന്ദേശം" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "പഴയ വിലാസം" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "ഇത് ഡൊമൈന്‍ നാമം ഉള്‍പ്പെടാത്ത ഒരു കേവലമാര്‍ഗം (വിലാസം) ആവണം. " +"ഉദാ: '/events/search/'." + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "പുതിയ വിലാസം" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "ഇതൊരു കേവല മാര്‍ഗമോ 'http://' എന്നു തുടങ്ങുന്ന പൂര്‍ണ്ണ വിലാസമോ (URL) ആവാം" + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "വിലാസമാറ്റം" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "വിലാസമാറ്റങ്ങള്‍" + +#: contrib/sessions/models.py:45 +msgid "session key" +msgstr "സെഷന്‍ കീ" + +#: contrib/sessions/models.py:47 +msgid "session data" +msgstr "സെഷന്‍ വിവരം" + +#: contrib/sessions/models.py:48 +msgid "expire date" +msgstr "കാലാവധി (തീയതി)" + +#: contrib/sessions/models.py:53 +msgid "session" +msgstr "സെഷന്‍" + +#: contrib/sessions/models.py:54 +msgid "sessions" +msgstr "സെഷനുകള്‍" + +#: contrib/sites/models.py:32 +msgid "domain name" +msgstr "ഡൊമൈന്‍ നാമം" + +#: contrib/sites/models.py:33 +msgid "display name" +msgstr "പ്രദര്‍ശന നാമം" + +#: contrib/sites/models.py:39 +msgid "sites" +msgstr "സൈറ്റുകള്‍" + +#: core/validators.py:20 forms/fields.py:66 +msgid "Enter a valid value." +msgstr "ശരിയായ മൂല്യം നല്കണം." + +#: core/validators.py:87 forms/fields.py:528 +msgid "Enter a valid URL." +msgstr "ശരിയായ URL നല്കണം." + +#: core/validators.py:89 forms/fields.py:529 +msgid "This URL appears to be a broken link." +msgstr "ഈ URL നിലവില്ലാത്ത വിലാസമാണ് കാണിക്കുന്നത്." + +#: core/validators.py:123 forms/fields.py:877 +msgid "" +"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." +msgstr "ശരിയായ സ്ളഗ് നല്കുക (അക്ഷരങ്ങള്‍, അക്കങ്ങള്‍, അണ്ടര്‍സ്കോര്‍, ഹൈഫന്‍ എന്നിവ മാത്രം ചേര്‍ന്നത്)." + +#: core/validators.py:126 forms/fields.py:870 +msgid "Enter a valid IPv4 address." +msgstr "ശരിയായ IPv4 വിലാസം നല്കണം" + +#: core/validators.py:129 db/models/fields/__init__.py:572 +msgid "Enter only digits separated by commas." +msgstr "അക്കങ്ങള്‍ മാത്രം (കോമയിട്ടു വേര്‍തിരിച്ചത്)" + +#: core/validators.py:135 +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "ഇത് %(limit_value)s ആവണം. (ഇപ്പോള്‍ %(show_value)s)." + +#: core/validators.py:153 forms/fields.py:204 forms/fields.py:256 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "ഇത് %(limit_value)s-ഓ അതില്‍ കുറവോ ആവണം" + +#: core/validators.py:158 forms/fields.py:205 forms/fields.py:257 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "ഇത് %(limit_value)s-ഓ അതില്‍ കൂടുതലോ ആവണം" + +#: core/validators.py:164 +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d characters (it has %" +"(show_value)d)." +msgstr "ഇതിനു ഏറ്റവും കുറഞ്ഞത് %(limit_value)d അക്ഷരങ്ങള്‍ വേണം. (ഇപ്പോള്‍ " +"%(show_value)d അക്ഷരങ്ങളുണ്ട്.)" + +#: core/validators.py:170 +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d characters (it has %" +"(show_value)d)." +msgstr "ഇതിനു പരമാവധി %(limit_value)d അക്ഷരങ്ങളേ ഉള്ളൂ എന്നു ഉറപ്പാക്കുക. (ഇപ്പോള്‍ " +"%(show_value)d അക്ഷരങ്ങളുണ്ട്.)" + +#: db/models/base.py:823 +#, python-format +msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s." +msgstr "%(date_field)s %(lookup)s-നു %(field_name)s ആവര്‍ത്തിക്കാന്‍ പാടില്ല." + +#: db/models/base.py:838 db/models/base.py:846 +#, python-format +msgid "%(model_name)s with this %(field_label)s already exists." +msgstr "%(field_label)s-ഓടു കൂടിയ %(model_name)s നിലവിലുണ്ട്." + +#: db/models/fields/__init__.py:63 +#, python-format +msgid "Value %r is not a valid choice." +msgstr "%r അനുയോജ്യമല്ല." + +#: db/models/fields/__init__.py:64 +msgid "This field cannot be null." +msgstr "ഈ കള്ളി ഒഴിച്ചിടരുത്." + +#: db/models/fields/__init__.py:65 +msgid "This field cannot be blank." +msgstr "ഈ കള്ളി ഒഴിച്ചിടരുത്." + +#: db/models/fields/__init__.py:70 +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "" + +#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:852 +#: db/models/fields/__init__.py:961 db/models/fields/__init__.py:972 +#: db/models/fields/__init__.py:999 +msgid "Integer" +msgstr "പൂര്‍ണ്ണസംഖ്യ" + +#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:850 +msgid "This value must be an integer." +msgstr "പൂര്‍ണ്ണസംഖ്യ മാത്രം" + +#: db/models/fields/__init__.py:490 +msgid "This value must be either True or False." +msgstr "ശരിയോ തെറ്റോ എന്നു മാത്രം" + +#: db/models/fields/__init__.py:492 +msgid "Boolean (Either True or False)" +msgstr "ശരിയോ തെറ്റോ എന്നു മാത്രം" + +#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:982 +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "" + +#: db/models/fields/__init__.py:567 +msgid "Comma-separated integers" +msgstr "കോമയിട്ട് വേര്‍തിരിച്ച സംഖ്യകള്‍" + +#: db/models/fields/__init__.py:581 +msgid "Date (without time)" +msgstr "തീയതി (സമയം വേണ്ട)" + +#: db/models/fields/__init__.py:585 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "ശരിയായ തീയതി YYYY-MM-DD എന്ന മാത്രുകയില്‍ നല്കണം." + +#: db/models/fields/__init__.py:586 +#, python-format +msgid "Invalid date: %s" +msgstr "തെറ്റായ തീയതി: %s" + +#: db/models/fields/__init__.py:667 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format." +msgstr "ശരിയായ തീയതി/സമയം YYYY-MM-DD HH:MM[:ss[.uuuuuu]]എന്ന മാത്രുകയില്‍ നല്കണം." + +#: db/models/fields/__init__.py:669 +msgid "Date (with time)" +msgstr "തീയതി (സമയത്തോടൊപ്പം)" + +#: db/models/fields/__init__.py:735 +msgid "This value must be a decimal number." +msgstr "ഈ വില ദശാംശമാവണം." + +#: db/models/fields/__init__.py:737 +msgid "Decimal number" +msgstr "ദശാംശസംഖ്യ" + +#: db/models/fields/__init__.py:792 +msgid "E-mail address" +msgstr "ഇ-മെയില്‍ വിലാസം" + +#: db/models/fields/__init__.py:799 db/models/fields/files.py:220 +#: db/models/fields/files.py:331 +msgid "File path" +msgstr "ഫയല്‍ സ്ഥാനം" + +#: db/models/fields/__init__.py:822 +msgid "This value must be a float." +msgstr "ഈ വില ദശാംശമാവണം." + +#: db/models/fields/__init__.py:824 +msgid "Floating point number" +msgstr "ദശാംശസംഖ്യ" + +#: db/models/fields/__init__.py:883 +msgid "Big (8 byte) integer" +msgstr "8 ബൈറ്റ് പൂര്‍ണസംഖ്യ." + +#: db/models/fields/__init__.py:912 +msgid "This value must be either None, True or False." +msgstr "ഈ മൂല്യം None, True, False എന്നിവയില്‍ ഏതെങ്കിലും ഒന്നാവണം" + +#: db/models/fields/__init__.py:914 +msgid "Boolean (Either True, False or None)" +msgstr "ശരിയോ തെറ്റോ എന്നു മാത്രം" + +#: db/models/fields/__init__.py:1005 +msgid "Text" +msgstr "ടെക്സ്റ്റ്" + +#: db/models/fields/__init__.py:1021 +msgid "Time" +msgstr "സമയം" + +#: db/models/fields/__init__.py:1025 +msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." +msgstr "ശരിയായ സമയം HH:MM[:ss[.uuuuuu]] എന്ന മാത്രുകയില്‍ നല്കണം." + +#: db/models/fields/__init__.py:1109 +msgid "XML text" +msgstr "" + +#: db/models/fields/related.py:799 +#, python-format +msgid "Model %(model)s with pk %(pk)r does not exist." +msgstr "" + +#: db/models/fields/related.py:801 +msgid "Foreign Key (type determined by related field)" +msgstr "" + +#: db/models/fields/related.py:918 +msgid "One-to-one relationship" +msgstr "" + +#: db/models/fields/related.py:980 +msgid "Many-to-many relationship" +msgstr "" + +#: db/models/fields/related.py:1000 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "\"Control\" എന്ന കീ അമര്‍ത്തിപ്പിടിക്കുക. (Macലാണെങ്കില്‍ \"Command\")." + +#: db/models/fields/related.py:1061 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "ദയവായി ശരിയായ %(self)s IDകള്‍ നല്കുക. %(value)r അനുയോജ്യമല്ല." +msgstr[1] "ദയവായി ശരിയായ %(self)s IDകള്‍ നല്കുക. %(value)r അനുയോജ്യമല്ല." + +#: forms/fields.py:65 +msgid "This field is required." +msgstr "ഈ കള്ളി നിര്‍ബന്ധമാണ്." + +#: forms/fields.py:203 +msgid "Enter a whole number." +msgstr "ഒരു പൂര്‍ണസംഖ്യ നല്കുക." + +#: forms/fields.py:234 forms/fields.py:255 +msgid "Enter a number." +msgstr "ഒരു സംഖ്യ നല്കുക." + +#: forms/fields.py:258 +#, python-format +msgid "Ensure that there are no more than %s digits in total." +msgstr "മൊത്തം %s ലേറെ അക്കങ്ങള്‍ ഇല്ലെന്ന് ഉറപ്പു വരുത്തുക." + +#: forms/fields.py:259 +#, python-format +msgid "Ensure that there are no more than %s decimal places." +msgstr "%s ലേറെ ദശാംശസ്ഥാനങ്ങള്‍ ഇല്ലെന്ന് ഉറപ്പു വരുത്തുക." + +#: forms/fields.py:260 +#, python-format +msgid "Ensure that there are no more than %s digits before the decimal point." +msgstr "ദശാംശബിന്ദുവിനു മുമ്പ് %sലേറെ അക്കങ്ങള്‍ ഇല്ലെന്നു ഉറപ്പു വരുത്തുക." + +#: forms/fields.py:322 forms/fields.py:837 +msgid "Enter a valid date." +msgstr "ശരിയായ തീയതി നല്കുക." + +#: forms/fields.py:350 forms/fields.py:838 +msgid "Enter a valid time." +msgstr "ശരിയായ സമയം നല്കുക." + +#: forms/fields.py:376 +msgid "Enter a valid date/time." +msgstr "ശരിയായ തീയതിയും സമയവും നല്കുക." + +#: forms/fields.py:434 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "ഫയലൊന്നും ലഭിച്ചില്ല. ഫോമിലെ എന്‍-കോഡിംഗ് പരിശോധിക്കുക." + +#: forms/fields.py:435 +msgid "No file was submitted." +msgstr "ഫയലൊന്നും ലഭിച്ചില്ല." + +#: forms/fields.py:436 +msgid "The submitted file is empty." +msgstr "ലഭിച്ച ഫയല്‍ ശൂന്യമാണ്." + +#: forms/fields.py:437 +#, python-format +msgid "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr "ഈ ഫയലിന്റെ പേര് പരമാവധി %(max)d അക്ഷരങ്ങളുള്ളതായിരിക്കണം. " +"(ഇപ്പോള്‍ %(length)d അക്ഷരങ്ങള്‍ ഉണ്ട്)." + +#: forms/fields.py:472 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "ശരിയായ ചിത്രം അപ് ലോഡ് ചെയ്യുക. നിങ്ങള്‍ നല്കിയ ഫയല്‍ ഒന്നുകില്‍ ഒരു ചിത്രമല്ല, " +"അല്ലെങ്കില്‍ വികലമാണ്." + +#: forms/fields.py:595 forms/fields.py:670 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "യോഗ്യമായത് തെരഞ്ഞെടുക്കുക. %(value)s ലഭ്യമായവയില്‍ ഉള്‍പ്പെടുന്നില്ല." + +#: forms/fields.py:671 forms/fields.py:733 forms/models.py:1002 +msgid "Enter a list of values." +msgstr "മൂല്യങ്ങളുടെ പട്ടിക(ലിസ്റ്റ്) നല്കുക." + +#: forms/formsets.py:298 forms/formsets.py:300 +msgid "Order" +msgstr "ക്രമം" + +#: forms/models.py:562 +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "%(field)s-നായി നല്കുന്ന വിവരം ആവര്‍ത്തിച്ചത് ദയവായി തിരുത്തുക." + +#: forms/models.py:566 +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "%(field)s-നായി നല്കുന്ന വിവരം ആവര്‍ത്തിക്കാന്‍ പാടില്ല. ദയവായി തിരുത്തുക." + +#: forms/models.py:572 +#, python-format +msgid "" +"Please correct the duplicate data for %(field_name)s which must be unique " +"for the %(lookup)s in %(date_field)s." +msgstr "%(date_field)s ലെ %(lookup)s നു വേണ്ടി %(field_name)s നു നല്കുന്ന വിവരം " +"ആവര്‍ത്തിക്കാന്‍ പാടില്ല. ദയവായി തിരുത്തുക." + +#: forms/models.py:580 +msgid "Please correct the duplicate values below." +msgstr "താഴെ കൊടുത്തവയില്‍ ആവര്‍ത്തനം ഒഴിവാക്കുക." + +#: forms/models.py:855 +msgid "The inline foreign key did not match the parent instance primary key." +msgstr "" + +#: forms/models.py:921 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "യോഗ്യമായത് തെരഞ്ഞെടുക്കുക. നിങ്ങള്‍ നല്കിയത് ലഭ്യമായവയില്‍ ഉള്‍പ്പെടുന്നില്ല." + +#: forms/models.py:1003 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "യോഗ്യമായത് തെരഞ്ഞെടുക്കുക. %s തന്നിരിക്കുന്നവയില്‍ ഉള്‍പ്പെടുന്നില്ല." + +#: forms/models.py:1005 +#, python-format +msgid "\"%s\" is not a valid value for a primary key." +msgstr "\"%s\" പ്രാഥമിക കീ ആവാന്‍ അനുയോജ്യമായ മൂല്യമല്ല." + +#: template/defaultfilters.py:776 +msgid "yes,no,maybe" +msgstr "ഉണ്ട്, ഇല്ല, ഉണ്ടാവാം" + +#: template/defaultfilters.py:807 +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "" +msgstr[1] "" + +#: template/defaultfilters.py:809 +#, python-format +msgid "%.1f KB" +msgstr "" + +#: template/defaultfilters.py:811 +#, python-format +msgid "%.1f MB" +msgstr "" + +#: template/defaultfilters.py:812 +#, python-format +msgid "%.1f GB" +msgstr "" + +#: utils/dateformat.py:42 +msgid "p.m." +msgstr "" + +#: utils/dateformat.py:43 +msgid "a.m." +msgstr "" + +#: utils/dateformat.py:48 +msgid "PM" +msgstr "PM" + +#: utils/dateformat.py:49 +msgid "AM" +msgstr "AM" + +#: utils/dateformat.py:98 +msgid "midnight" +msgstr "അര്‍ധരാത്രി" + +#: utils/dateformat.py:100 +msgid "noon" +msgstr "ഉച്ച" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "തിങ്കള്‍" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "ചൊവ്വ" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "ബുധന്‍" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "വ്യാഴം" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "വെള്ളി" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "ശനി" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "ഞായര്‍" + +#: utils/dates.py:10 +msgid "Mon" +msgstr "തിങ്കള്‍" + +#: utils/dates.py:10 +msgid "Tue" +msgstr "ചൊവ്വ" + +#: utils/dates.py:10 +msgid "Wed" +msgstr "ബുധന്‍" + +#: utils/dates.py:10 +msgid "Thu" +msgstr "വ്യാഴം" + +#: utils/dates.py:10 +msgid "Fri" +msgstr "വെള്ളി" + +#: utils/dates.py:11 +msgid "Sat" +msgstr "ശനി" + +#: utils/dates.py:11 +msgid "Sun" +msgstr "ഞായര്‍" + +#: utils/dates.py:18 +msgid "January" +msgstr "ജനുവരി" + +#: utils/dates.py:18 +msgid "February" +msgstr "ഫെബ്രുവരി" + +#: utils/dates.py:18 utils/dates.py:31 +msgid "March" +msgstr "മാര്‍ച്ച്" + +#: utils/dates.py:18 utils/dates.py:31 +msgid "April" +msgstr "ഏപ്രില്‍" + +#: utils/dates.py:18 utils/dates.py:31 +msgid "May" +msgstr "മേയ്" + +#: utils/dates.py:18 utils/dates.py:31 +msgid "June" +msgstr "ജൂണ്‍" + +#: utils/dates.py:19 utils/dates.py:31 +msgid "July" +msgstr "ജൂലൈ" + +#: utils/dates.py:19 +msgid "August" +msgstr "ആഗസ്ത്" + +#: utils/dates.py:19 +msgid "September" +msgstr "സെപ്തംബര്‍" + +#: utils/dates.py:19 +msgid "October" +msgstr "ഒക്ടോബര്‍" + +#: utils/dates.py:19 +msgid "November" +msgstr "നവംബര്‍" + +#: utils/dates.py:20 +msgid "December" +msgstr "ഡിസംബര്‍" + +#: utils/dates.py:23 +msgid "jan" +msgstr "ജനു." + +#: utils/dates.py:23 +msgid "feb" +msgstr "ഫെബ്രു." + +#: utils/dates.py:23 +msgid "mar" +msgstr "മാര്‍ച്ച്" + +#: utils/dates.py:23 +msgid "apr" +msgstr "ഏപ്രില്‍" + +#: utils/dates.py:23 +msgid "may" +msgstr "മേയ്" + +#: utils/dates.py:23 +msgid "jun" +msgstr "ജൂണ്‍" + +#: utils/dates.py:24 +msgid "jul" +msgstr "ജൂലൈ" + +#: utils/dates.py:24 +msgid "aug" +msgstr "ആഗസ്ത്" + +#: utils/dates.py:24 +msgid "sep" +msgstr "സെപ്ടം." + +#: utils/dates.py:24 +msgid "oct" +msgstr "ഒക്ടോ." + +#: utils/dates.py:24 +msgid "nov" +msgstr "നവം." + +#: utils/dates.py:24 +msgid "dec" +msgstr "ഡിസം." + +#: utils/dates.py:31 +msgid "Jan." +msgstr "ജനു." + +#: utils/dates.py:31 +msgid "Feb." +msgstr "ഫെബ്രു." + +#: utils/dates.py:32 +msgid "Aug." +msgstr "ആഗസ്ത്" + +#: utils/dates.py:32 +msgid "Sept." +msgstr "സെപ്ടം." + +#: utils/dates.py:32 +msgid "Oct." +msgstr "ഒക്ടോ." + +#: utils/dates.py:32 +msgid "Nov." +msgstr "നവം." + +#: utils/dates.py:32 +msgid "Dec." +msgstr "ഡിസം." + +#: utils/text.py:130 +msgid "or" +msgstr "അഥവാ" + +#: utils/timesince.py:21 +msgid "year" +msgid_plural "years" +msgstr[0] "വര്‍ഷം" +msgstr[1] "വര്‍ഷങ്ങള്‍" + +#: utils/timesince.py:22 +msgid "month" +msgid_plural "months" +msgstr[0] "മാസം" +msgstr[1] "മാസങ്ങള്‍" + +#: utils/timesince.py:23 +msgid "week" +msgid_plural "weeks" +msgstr[0] "ആഴ്ച്ച" +msgstr[1] "ആഴ്ച്ചകള്‍" + +#: utils/timesince.py:24 +msgid "day" +msgid_plural "days" +msgstr[0] "ദിവസം" +msgstr[1] "ദിവസങ്ങള്‍" + +#: utils/timesince.py:25 +msgid "hour" +msgid_plural "hours" +msgstr[0] "മണിക്കൂര്‍" +msgstr[1] "മണിക്കൂറുകള്‍" + +#: utils/timesince.py:26 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "മിനുട്ട്" +msgstr[1] "മിനുട്ടുകള്‍" + +#: utils/timesince.py:45 +msgid "minutes" +msgstr "മിനുട്ടുകള്‍" + +#: utils/timesince.py:50 +#, python-format +msgid "%(number)d %(type)s" +msgstr "" + +#: utils/timesince.py:56 +#, python-format +msgid ", %(number)d %(type)s" +msgstr "" + +#: utils/translation/trans_real.py:519 +msgid "DATE_FORMAT" +msgstr "" + +#: utils/translation/trans_real.py:520 +msgid "DATETIME_FORMAT" +msgstr "" + +#: utils/translation/trans_real.py:521 +msgid "TIME_FORMAT" +msgstr "" + +#: utils/translation/trans_real.py:542 +msgid "YEAR_MONTH_FORMAT" +msgstr "" + +#: utils/translation/trans_real.py:543 +msgid "MONTH_DAY_FORMAT" +msgstr "" + +#: views/generic/create_update.py:115 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "%(verbose_name)s നെ സ്രുഷ്ടിച്ചു." + +#: views/generic/create_update.py:158 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "%(verbose_name)s നെ മെച്ചപ്പെടുത്തി." + +#: views/generic/create_update.py:201 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "%(verbose_name)s ഡിലീറ്റ് ചെയ്യപ്പെട്ടു." diff --git a/django/conf/locale/ml/LC_MESSAGES/djangojs.mo b/django/conf/locale/ml/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..644d8bf949938561f4225a8b80d176c72900a14d GIT binary patch literal 4350 zcmcJRO^_W`6~|i)DkF--Pt?VAF_ol9KPCx-sS#3)6QYBeNz8kIthDF#o%cHF*FE&d zz*}e~u?z|oEiF?Jib~2N1qjjb(GOTT5az#=8&b{4l=1nG<+4w4b z|9$(Od(Qv-&$)N%j`L0M5%3e>@4=6QuYfm$1^6lO z&(-?h!Ot*%8yo}w3El+$tHKY!8<~FuZUeXB>;dp5Q2hK5l4<;ywb3-^V}& zo&m+~1#mxj85FFnh?*g|G=P>vU=HEvY(JckV|4DEh zJP+;zSHKkf4=DRPpLU#|fIkLr0Y3y~|IG~B!P~&6@xL3q0ZspcNR!O}hB08Y6W!z| z{@Cu^#`9SoQZgC=#`l*&=$ky{Tm8eW^HrW(dA?A66s?87)Zp_xU*bVx=W{&5OMawa zxS9!r^q4cgGu7d)RK<&Gd}lk#c;$m`XWGB++Fip1WUJ!Z3L-yP4*c8;)ncGSKW!*{?nty+O5z1- z)K_5~p9rFkT8tBwb+!6VtQPVtV{_s$myZOMm8LMUYYpeR)G!jUhSXAa`n7zPTozwV zPW5%xP)8|09b%O|@;!y6?6=IrP(>PFd`z^PLjHuQ|jPxGK^L8!TGUcdMQpacP{M& zzWeRGle(>VN{t;o(sE}K?MWTogI=bm)ZU3b6Yj*MyLZ3ZGdZ=cKhe?4_C8O1vlyy(~_q)G_b4)@fJGdqJY^PV6j+nBCjyaXK6AxOZUeFhp~& zqurMFdQ+UD(ChQ`)ZM-C-Y{syN$0@W-0a+atI6$YOpMLMQKlmT_LpFu>Bq7=mO?Lx zz9pTPq&mCjfz}~+|Eir3ThxhrU(}9$dTmPWUkI|XAGk+jDTQ0}SVLiTaVE~AEa^|F z+4+OB(_>sh=2j|;*UG`!_fDSWnzA=mSzIayXUgK0vbeyK8JS)z2dB&uUobdzJqN!m zi|5SN=gQ)=+N#6q>MM(vs!+vBSzNI(aq>(#cwAT+wqSIgpgv*USVKH}{y;{;cma39%3&Z4A=NBpgBMbmT8*<5m)&jwK} zi`UCatx6ze1fy}At4m&?IV%VPDbt;Mirln^H}vg22#(9B^0qq+?xrnH~%#_CxLYzrto zTF){5ITOsDDvP&_R}_?WCVZuXaD0jM%Hr~{>@5GLPLNDUc4X~E!+iaY-x+qa#K?kE z7C#&MEsGaS7APt#Y(`l8!sg1zgxWEkhGfJ7N}jA)Q5I<&6k1=o3lxkMQWCW> zcmhD~Q^3FkrAT7IJeQPRXP!&Kv4%s`RiIS1JRsdRuI%#CAPV$#`91c607f~!Py3GjS`hA zCi3Xfpu3?H!Q;D$ zc&$pw;oFk>R&uU6nm`WyCbdQ5tGWfbt!NyDQ5WmarYLQ@9@7=Ye+TP)`16R;8y{!_ zo|En{WfU_Z!F=Ri*!Uz+kYQKI76NdAUX, 2010. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Django SVN\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2010-05-28 15:32+0530\n" +"PO-Revision-Date: 2010-05-28 15:45+0530\n" +"Last-Translator: Rajeesh Nair \n" +"Language-Team: Malayalam \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Malayalam\n" +"X-Poedit-Country: INDIA\n" + +#: contrib/admin/media/js/SelectFilter2.js:37 +#, perl-format +msgid "Available %s" +msgstr "ലഭ്യമായ %s" + +#: contrib/admin/media/js/SelectFilter2.js:45 +msgid "Choose all" +msgstr "എല്ലാം തെരഞ്ഞെടുക്കുക" + +#: contrib/admin/media/js/SelectFilter2.js:50 +msgid "Add" +msgstr "പുതിയത് ചേര്‍ക്കൂ" + +#: contrib/admin/media/js/SelectFilter2.js:52 +msgid "Remove" +msgstr "നീക്കം ചെയ്യൂ" + +#: contrib/admin/media/js/SelectFilter2.js:57 +#, perl-format +msgid "Chosen %s" +msgstr "തെരഞ്ഞെടുത്ത %s" + +#: contrib/admin/media/js/SelectFilter2.js:58 +msgid "Select your choice(s) and click " +msgstr "ഉചിതമായത് തെരഞ്ഞെടുത്ത ശേഷം ക്ളിക് ചെയ്യൂ" + +#: contrib/admin/media/js/SelectFilter2.js:63 +msgid "Clear all" +msgstr "എല്ലാം ക്ളിയര്‍ ചെയ്യൂ" + +#: contrib/admin/media/js/actions.js:18 +#: contrib/admin/media/js/actions.min.js:1 +msgid "%(sel)s of %(cnt)s selected" +msgid_plural "%(sel)s of %(cnt)s selected" +msgstr[0] "%(cnt)sല്‍ %(sel)s തെരഞ്ഞെടുത്തു" +msgstr[1] "%(cnt)sല്‍ %(sel)s എണ്ണം തെരഞ്ഞെടുത്തു" + +#: contrib/admin/media/js/actions.js:109 +#: contrib/admin/media/js/actions.min.js:5 +msgid "" +"You have unsaved changes on individual editable fields. If you run an " +"action, your unsaved changes will be lost." +msgstr "വരുത്തിയ മാറ്റങ്ങള്‍ സേവ് ചെയ്തിട്ടില്ല. ഒരു ആക്ഷന്‍ പ്രയോഗിച്ചാല്‍ സേവ് ചെയ്യാത്ത " +"മാറ്റങ്ങളെല്ലാം നഷ്ടപ്പെടും." + +#: contrib/admin/media/js/actions.js:121 +#: contrib/admin/media/js/actions.min.js:6 +msgid "" +"You have selected an action, but you haven't saved your changes to " +"individual fields yet. Please click OK to save. You'll need to re-run the " +"action." +msgstr "" +"നിങ്ങള്‍ ഒരു ആക്ഷന്‍ തെരഞ്ഞെടുത്തിട്ടുണ്ട്. പക്ഷേ, കളങ്ങളിലെ മാറ്റങ്ങള്‍ ഇനിയും സേവ് ചെയ്യാനുണ്ട്. ആദ്യം സേവ്" +"ചെയ്യാനായി OK ക്ലിക് ചെയ്യുക. അതിനു ശേഷം ആക്ഷന്‍ ഒന്നു കൂടി പ്രയോഗിക്കേണ്ടി വരും." + +#: contrib/admin/media/js/actions.js:123 +#: contrib/admin/media/js/actions.min.js:6 +msgid "" +"You have selected an action, and you haven't made any changes on individual " +"fields. You're probably looking for the Go button rather than the Save " +"button." +msgstr "" +"നിങ്ങള്‍ ഒരു ആക്ഷന്‍ തെരഞ്ഞെടുത്തിട്ടുണ്ട്. കളങ്ങളില്‍ സേവ് ചെയ്യാത്ത മാറ്റങ്ങള്‍ ഇല്ല. നിങ്ങള്‍" +"സേവ് ബട്ടണ്‍ തന്നെയാണോ അതോ ഗോ ബട്ടണാണോ ഉദ്ദേശിച്ചത്." + +#: contrib/admin/media/js/calendar.js:24 +#: contrib/admin/media/js/dateparse.js:32 +msgid "" +"January February March April May June July August September October November " +"December" +msgstr "ജനുവരി ഫെബൃവരി മാര്‍ച്ച് ഏപ്രില്‍ മെയ് ജൂണ്‍ ജൂലൈ ആഗസ്ത് സെപ്തംബര്‍ ഒക്ടോബര്‍ നവംബര്‍ ഡിസംബര്‍" + +#: contrib/admin/media/js/calendar.js:25 +msgid "S M T W T F S" +msgstr "ഞാ തി ചൊ ബു വ്യാ വെ ശ" + +#: contrib/admin/media/js/collapse.js:9 contrib/admin/media/js/collapse.js:21 +#: contrib/admin/media/js/collapse.min.js:1 +msgid "Show" +msgstr "കാണട്ടെ" + +#: contrib/admin/media/js/collapse.js:16 +#: contrib/admin/media/js/collapse.min.js:1 +msgid "Hide" +msgstr "മറയട്ടെ" + +#: contrib/admin/media/js/dateparse.js:33 +msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" +msgstr "ഞായര്‍ തിങ്കള്‍ ചൊവ്വ ബുധന്‍ വ്യാഴം വെള്ളി ശനി" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83 +msgid "Now" +msgstr "ഇപ്പോള്‍" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:52 +msgid "Clock" +msgstr "ഘടികാരം (ക്ലോക്ക്)" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:79 +msgid "Choose a time" +msgstr "സമയം തെരഞ്ഞെടുക്കൂ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84 +msgid "Midnight" +msgstr "അര്‍ധരാത്രി" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:85 +msgid "6 a.m." +msgstr "6 a.m." + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:86 +msgid "Noon" +msgstr "ഉച്ച" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:90 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:187 +msgid "Cancel" +msgstr "റദ്ദാക്കൂ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132 +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:181 +msgid "Today" +msgstr "ഇന്ന്" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:136 +msgid "Calendar" +msgstr "കലണ്ടര്‍" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 +msgid "Yesterday" +msgstr "ഇന്നലെ" + +#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 +msgid "Tomorrow" +msgstr "നാളെ" diff --git a/django/conf/locale/ml/__init__.py b/django/conf/locale/ml/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/django/conf/locale/ml/formats.py b/django/conf/locale/ml/formats.py new file mode 100644 index 000000000000..141f705fb9b1 --- /dev/null +++ b/django/conf/locale/ml/formats.py @@ -0,0 +1,38 @@ +# -*- encoding: utf-8 -*- +# This file is distributed under the same license as the Django package. +# + +DATE_FORMAT = 'N j, Y' +TIME_FORMAT = 'P' +DATETIME_FORMAT = 'N j, Y, P' +YEAR_MONTH_FORMAT = 'F Y' +MONTH_DAY_FORMAT = 'F j' +SHORT_DATE_FORMAT = 'm/d/Y' +SHORT_DATETIME_FORMAT = 'm/d/Y P' +FIRST_DAY_OF_WEEK = 0 # Sunday +DATE_INPUT_FORMATS = ( + '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' + # '%b %d %Y', '%b %d, %Y', # 'Oct 25 2006', 'Oct 25, 2006' + # '%d %b %Y', '%d %b, %Y', # '25 Oct 2006', '25 Oct, 2006' + # '%B %d %Y', '%B %d, %Y', # 'October 25 2006', 'October 25, 2006' + # '%d %B %Y', '%d %B, %Y', # '25 October 2006', '25 October, 2006' +) +TIME_INPUT_FORMATS = ( + '%H:%M:%S', # '14:30:59' + '%H:%M', # '14:30' +) +DATETIME_INPUT_FORMATS = ( + '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' + '%Y-%m-%d %H:%M', # '2006-10-25 14:30' + '%Y-%m-%d', # '2006-10-25' + '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59' + '%m/%d/%Y %H:%M', # '10/25/2006 14:30' + '%m/%d/%Y', # '10/25/2006' + '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59' + '%m/%d/%y %H:%M', # '10/25/06 14:30' + '%m/%d/%y', # '10/25/06' +) +DECIMAL_SEPARATOR = '.' +THOUSAND_SEPARATOR = ',' +NUMBER_GROUPING = 3 + From 5bd352a96669a921541188685f5184d81a712dfe Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 02:13:41 +0000 Subject: [PATCH 035/902] [1.2.X] Fixed #14064 -- Corrected spelling of Argentinian. There are arguments that it can be spelled both ways (and arguments that the right phrase is Argentine), but we're going with the OED on this one. Backport of r13492 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13494 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/global_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 4dc65b8cf4fc..6033297e5c17 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -54,7 +54,7 @@ ('en', gettext_noop('English')), ('en-gb', gettext_noop('British English')), ('es', gettext_noop('Spanish')), - ('es-ar', gettext_noop('Argentinean Spanish')), + ('es-ar', gettext_noop('Argentinian Spanish')), ('et', gettext_noop('Estonian')), ('eu', gettext_noop('Basque')), ('fa', gettext_noop('Persian')), From 8419b092aeb9c28c3fe117c04c5d7d537aadaef2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 14:39:24 +0000 Subject: [PATCH 036/902] [1.2.X] Fixed #14027 -- Ensure that reverse() raises an exception when you try to reverse None. Thanks to Alex for the report and patch. Backport of r13499 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13500 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/urlresolvers.py | 3 ++- tests/regressiontests/urlpatterns_reverse/tests.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index cad57a5d9ff1..8ecec941fc14 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -183,7 +183,8 @@ def _populate(self): else: bits = normalize(p_pattern) lookups.appendlist(pattern.callback, (bits, p_pattern)) - lookups.appendlist(pattern.name, (bits, p_pattern)) + if pattern.name is not None: + lookups.appendlist(pattern.name, (bits, p_pattern)) self._reverse_dict = lookups self._namespace_dict = namespaces self._app_dict = apps diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index 3fcc935da2f2..eed7c65dd7ae 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -119,6 +119,10 @@ def test_urlpattern_reverse(self): else: self.assertEquals(got, expected) + def test_reverse_none(self): + # Reversing None should raise an error, not return the last un-named view. + self.assertRaises(NoReverseMatch, reverse, None) + class ResolverTests(unittest.TestCase): def test_non_regex(self): """ From 7930d8c477d2370d5657d61a35323c420a5d4ab8 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 14:59:13 +0000 Subject: [PATCH 037/902] [1.2.X] Fixed #14012 -- Corrected the handling of the create user popup dialog in the admin. Thanks to gk@lka.hu for the report, and Ramiro Morales for the patch. Backport of r13051 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13502 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admin/templates/admin/auth/user/add_form.html | 4 ++++ tests/regressiontests/admin_views/models.py | 6 ++++++ tests/regressiontests/admin_views/tests.py | 14 ++++++++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/django/contrib/admin/templates/admin/auth/user/add_form.html b/django/contrib/admin/templates/admin/auth/user/add_form.html index b57f59b82eeb..262089f4bf86 100644 --- a/django/contrib/admin/templates/admin/auth/user/add_form.html +++ b/django/contrib/admin/templates/admin/auth/user/add_form.html @@ -2,8 +2,12 @@ {% load i18n %} {% block form_top %} + {% if not is_popup %}

      {% trans "First, enter a username and password. Then, you'll be able to edit more user options." %}

      + {% else %} +

      {% trans "Enter a username and password." %}

      + {% endif %} {% endblock %} {% block after_field_sets %} diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index a2700ba7476f..b25a9b9a96ae 100644 --- a/tests/regressiontests/admin_views/models.py +++ b/tests/regressiontests/admin_views/models.py @@ -10,6 +10,7 @@ from django.db import models from django import forms from django.forms.models import BaseModelFormSet +from django.contrib.auth.models import User from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType @@ -579,6 +580,10 @@ class Pizza(models.Model): class PizzaAdmin(admin.ModelAdmin): readonly_fields = ('toppings',) +class Album(models.Model): + owner = models.ForeignKey(User) + title = models.CharField(max_length=30) + admin.site.register(Article, ArticleAdmin) admin.site.register(CustomArticle, CustomArticleAdmin) admin.site.register(Section, save_as=True, inlines=[ArticleInline]) @@ -625,3 +630,4 @@ class PizzaAdmin(admin.ModelAdmin): admin.site.register(ChapterXtra1) admin.site.register(Pizza, PizzaAdmin) admin.site.register(Topping) +admin.site.register(Album) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 1385e5e0aac7..badbfa4cab93 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -2113,11 +2113,9 @@ def test_readonly_manytomany(self): response = self.client.get('/test_admin/admin/admin_views/pizza/add/') self.assertEqual(response.status_code, 200) -class IncompleteFormTest(TestCase): +class UserAdminTest(TestCase): """ - Tests validation of a ModelForm that doesn't explicitly have all data - corresponding to model fields. Model validation shouldn't fail - such a forms. + Tests user CRUD functionality. """ fixtures = ['admin-views-users.xml'] @@ -2149,3 +2147,11 @@ def test_password_mismatch(self): self.assert_('password' not in adminform.form.errors) self.assertEquals(adminform.form.errors['password2'], [u"The two password fields didn't match."]) + + def test_user_fk_popup(self): + response = self.client.get('/test_admin/admin/admin_views/album/add/') + self.failUnlessEqual(response.status_code, 200) + self.assertContains(response, '/test_admin/admin/auth/user/add') + self.assertContains(response, 'class="add-another" id="add_id_owner" onclick="return showAddAnotherPopup(this);"') + response = self.client.get('/test_admin/admin/auth/user/add/?_popup=1') + self.assertNotContains(response, 'name="_continue"') From c543f079ffd2b3dd76729188981f0dabb1c3d66a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 16:09:28 +0000 Subject: [PATCH 038/902] [1.2.X] Fixed #14014 -- Ensure that the "save and add another" button for users actually does what it says. Thanks to Ramiro for the report. Backport of r13503 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13504 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admin/templates/admin/auth/user/add_form.html | 1 - tests/regressiontests/admin_views/tests.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/django/contrib/admin/templates/admin/auth/user/add_form.html b/django/contrib/admin/templates/admin/auth/user/add_form.html index 262089f4bf86..c8889eb069d2 100644 --- a/django/contrib/admin/templates/admin/auth/user/add_form.html +++ b/django/contrib/admin/templates/admin/auth/user/add_form.html @@ -4,7 +4,6 @@ {% block form_top %} {% if not is_popup %}

      {% trans "First, enter a username and password. Then, you'll be able to edit more user options." %}

      - {% else %}

      {% trans "Enter a username and password." %}

      {% endif %} diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index badbfa4cab93..41aade0561ce 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -2126,6 +2126,7 @@ def tearDown(self): self.client.logout() def test_user_creation(self): + user_count = User.objects.count() response = self.client.post('/test_admin/admin/auth/user/add/', { 'username': 'newuser', 'password1': 'newpassword', @@ -2134,6 +2135,7 @@ def test_user_creation(self): }) new_user = User.objects.order_by('-id')[0] self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk) + self.assertEquals(User.objects.count(), user_count + 1) self.assertNotEquals(new_user.password, UNUSABLE_PASSWORD) def test_password_mismatch(self): @@ -2155,3 +2157,16 @@ def test_user_fk_popup(self): self.assertContains(response, 'class="add-another" id="add_id_owner" onclick="return showAddAnotherPopup(this);"') response = self.client.get('/test_admin/admin/auth/user/add/?_popup=1') self.assertNotContains(response, 'name="_continue"') + + def test_user_add_another(self): + user_count = User.objects.count() + response = self.client.post('/test_admin/admin/auth/user/add/', { + 'username': 'newuser', + 'password1': 'newpassword', + 'password2': 'newpassword', + '_addanother': '1', + }) + new_user = User.objects.order_by('-id')[0] + self.assertRedirects(response, '/test_admin/admin/auth/user/add/') + self.assertEquals(User.objects.count(), user_count + 1) + self.assertNotEquals(new_user.password, UNUSABLE_PASSWORD) From e2fbe478bda2e1294035e433cd952c83d0b62866 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 16:19:23 +0000 Subject: [PATCH 039/902] [1.2.X] Fixed #13495 -- Refactored the localflavor test directory to provide the placeholder structure for other flavors. Thanks to chronos for the report and patch. Backport of r13505 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13506 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/localflavor/models.py | 8 -- tests/regressiontests/localflavor/tests.py | 84 +------------------ .../localflavor/{ => us}/forms.py | 6 +- .../regressiontests/localflavor/us/models.py | 13 +++ tests/regressiontests/localflavor/us/tests.py | 82 ++++++++++++++++++ 5 files changed, 101 insertions(+), 92 deletions(-) rename tests/regressiontests/localflavor/{ => us}/forms.py (51%) create mode 100644 tests/regressiontests/localflavor/us/models.py create mode 100644 tests/regressiontests/localflavor/us/tests.py diff --git a/tests/regressiontests/localflavor/models.py b/tests/regressiontests/localflavor/models.py index f74a5051d42a..e69de29bb2d1 100644 --- a/tests/regressiontests/localflavor/models.py +++ b/tests/regressiontests/localflavor/models.py @@ -1,8 +0,0 @@ -from django.db import models -from django.contrib.localflavor.us.models import USStateField - -class Place(models.Model): - state = USStateField(blank=True) - state_req = USStateField() - state_default = USStateField(default="CA", blank=True) - name = models.CharField(max_length=20) diff --git a/tests/regressiontests/localflavor/tests.py b/tests/regressiontests/localflavor/tests.py index 0ea3c52568e7..69682360d379 100644 --- a/tests/regressiontests/localflavor/tests.py +++ b/tests/regressiontests/localflavor/tests.py @@ -1,83 +1,5 @@ +import unittest from django.test import TestCase -from models import Place -from forms import PlaceForm -class USLocalflavorTests(TestCase): - def setUp(self): - self.form = PlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'}) - - def test_get_display_methods(self): - """Test that the get_*_display() methods are added to the model instances.""" - place = self.form.save() - self.assertEqual(place.get_state_display(), 'Georgia') - self.assertEqual(place.get_state_req_display(), 'North Carolina') - - def test_required(self): - """Test that required USStateFields throw appropriate errors.""" - form = PlaceForm({'state':'GA', 'name':'Place in GA'}) - self.assertFalse(form.is_valid()) - self.assertEqual(form.errors['state_req'], [u'This field is required.']) - - def test_field_blank_option(self): - """Test that the empty option is there.""" - state_select_html = """\ -""" - self.assertEqual(str(self.form['state']), state_select_html) +# just import your tests here +from us.tests import * diff --git a/tests/regressiontests/localflavor/forms.py b/tests/regressiontests/localflavor/us/forms.py similarity index 51% rename from tests/regressiontests/localflavor/forms.py rename to tests/regressiontests/localflavor/us/forms.py index 2dd1da6dd0ea..9b77e1078b94 100644 --- a/tests/regressiontests/localflavor/forms.py +++ b/tests/regressiontests/localflavor/us/forms.py @@ -1,7 +1,7 @@ from django.forms import ModelForm -from models import Place +from models import USPlace -class PlaceForm(ModelForm): +class USPlaceForm(ModelForm): """docstring for PlaceForm""" class Meta: - model = Place + model = USPlace diff --git a/tests/regressiontests/localflavor/us/models.py b/tests/regressiontests/localflavor/us/models.py new file mode 100644 index 000000000000..a8a4cf0f502d --- /dev/null +++ b/tests/regressiontests/localflavor/us/models.py @@ -0,0 +1,13 @@ +from django.db import models +from django.contrib.localflavor.us.models import USStateField + +# When creating models you need to remember to add a app_label as +# 'localflavor', so your model can be found + +class USPlace(models.Model): + state = USStateField(blank=True) + state_req = USStateField() + state_default = USStateField(default="CA", blank=True) + name = models.CharField(max_length=20) + class Meta: + app_label = 'localflavor' diff --git a/tests/regressiontests/localflavor/us/tests.py b/tests/regressiontests/localflavor/us/tests.py new file mode 100644 index 000000000000..07fe0578334d --- /dev/null +++ b/tests/regressiontests/localflavor/us/tests.py @@ -0,0 +1,82 @@ +from django.test import TestCase +from forms import USPlaceForm + +class USLocalflavorTests(TestCase): + def setUp(self): + self.form = USPlaceForm({'state':'GA', 'state_req':'NC', 'name':'impossible'}) + + def test_get_display_methods(self): + """Test that the get_*_display() methods are added to the model instances.""" + place = self.form.save() + self.assertEqual(place.get_state_display(), 'Georgia') + self.assertEqual(place.get_state_req_display(), 'North Carolina') + + def test_required(self): + """Test that required USStateFields throw appropriate errors.""" + form = USPlaceForm({'state':'GA', 'name':'Place in GA'}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors['state_req'], [u'This field is required.']) + + def test_field_blank_option(self): + """Test that the empty option is there.""" + state_select_html = """\ +""" + self.assertEqual(str(self.form['state']), state_select_html) From 5d1dcfbfa5632c7ffcd05bf36260ff8755e44d00 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 16:25:19 +0000 Subject: [PATCH 040/902] [1.2.X] Added __init__.py file that was left out of [13505]. Backport of r13507 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13508 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/localflavor/us/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/regressiontests/localflavor/us/__init__.py diff --git a/tests/regressiontests/localflavor/us/__init__.py b/tests/regressiontests/localflavor/us/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 From fac923a3015a890eb5ffbbeef55b12e77eba9113 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 16:56:05 +0000 Subject: [PATCH 041/902] [1.2.X] Fixed #14069 -- Updated Czech translation. Thanks to Tuttle. Backport of r13513 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13515 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/locale/cs/LC_MESSAGES/django.mo | Bin 76848 -> 77011 bytes django/conf/locale/cs/LC_MESSAGES/django.po | 186 ++++++++++-------- django/conf/locale/cs/LC_MESSAGES/djangojs.mo | Bin 2695 -> 2695 bytes django/conf/locale/cs/LC_MESSAGES/djangojs.po | 4 +- 4 files changed, 101 insertions(+), 89 deletions(-) diff --git a/django/conf/locale/cs/LC_MESSAGES/django.mo b/django/conf/locale/cs/LC_MESSAGES/django.mo index 9251a58fbed5f895732155cf7a2440635b01d85c..82e12e8e090118f61d26c8779017aa1073361fc3 100644 GIT binary patch delta 24780 zcmYk^1$Y(L_V@7Y>0t5{nB)Gd1++B-Xu;PA@0>z31w*tkjl;BYOV#VEEi?kHC z@_v737XP>NJk!r!YwPTpIVS|}-Ei6W+!bH<%~0PNE{<`&E>~)7m&xS{_IJ6~R#w*K zy4}jZ@S_;<~5_TAG6~G4X6nh^x>Kx0$<9;~vBS z=64-e0WY8?x{2}eE~?=(Y>clhU!$Y5&_<|!EiLYdnz%b^i`}SOHWTCFTGWnh!W4K2 z-CEgg3jX-ee2&V$#-aETC*XiiJR9iS*}0dYsD6={A9JD>)Ea%Ui`Dl)jn@z3;SdbP zA3C%Di73n>p@(Ay>I!zE1~`uDa0Zj$6;%5ds4M&z)jnw#TL@|aIWayKvbYQe5?4Xp zvihinx9YmGF^p^5sT?(r~-C!hw5MRk~ux{_Z|3tENhw;R>&kky~UK;jFi@or-T zKF3TL6yx;E>$XBs)XK`DI@CaIbyL&=J6YV{>PMsQOUQs$L(53A)LfgREJ$oh3f=r;B%-BS5Xhmee)e^!oY6Mvy&1vK`xBKLYM&CU?1#` zTG%~QyJzU9_y08oP4Lkwe7idxgHSt>95qn{=EYo=Z-tS>9Z&;LLM?bECdUP+iMF72 zV7JAm&CBSk_y0BpeSkbbP2}Cf*@;jLCeCQ)MO{&8tFMOIk>*z41GSK$sD+F{EnpJr zp3gvC&>~EP%h9a?w@^^S{iu$|Fd?2n-ICj=*YhQ6r~G<410_Perb$r~r$qJ3h#Ds! z>ed#;ELaCM&p_1MFrp{>zn;P(5_7QD_s&Y6pjP@CYvV^Oiq(3#Tf7>1jKtrHT$Ql|*2U3S2QOm@%-)Zy$BtMWm!o#XeV0NW3STfC=IZZqmBd=8dp8No;4;(| zJjDF?7PDdY0i0ML^Pw9H;YO^EPf+)|&_E|{fE9?BIJ#YrC?qG5W03PSS2P==RvLp9 zaRsKt=cow+2J<B<08zh_y2baDM&m=HHgPo zp3I3^upa6P2V-trj*ammmciV^oQ3y9jWY^0@dC_>+c6C9Vp)8PT3`|8PNVm~4*$`W z^}u>K9>eh_YQVRc3lk4_CM=DrAC7r(Hfn-nm;`U47W@j;E@Fi9@D@TXs5)lCzUbDy zoJ&FX@+9iY-=nTPVx)6r(HKeG1l4{x`r#%F!5ygg|2S5|3)l_Qk8+;&$*8w#9;)3U z48>KW*nfSX>>`l{523E$KB~h9RQZ7sGXlDX9`Vfynt#rKQV^IUnMZJzo(GS;Jyb)Eu!{USHanv~H zQ5SFn{qR0&oIf?87VwWHzMvZVjdAX25Nd#QsC+i`#XJ@lG)tgf&+_Pv^-v3Hh?=Ms zYMf3Me~HQ*c61Yc2C==Y;jpBPo294lc4tc+by?c-1rY)9RS!^lU3 z>k?|52IHJ@TA?2DP8i1guAvmvaVqM4|JmGbp2KY9pQFCz!pA#Ldsfu@T^dVcBh*4> zVE`_`c(@!J;d-n0pWy6B5W3YMgn~?g8X&zj$YS{%r~wOLAeO`gSlQzGs4e{twbdQ4 zEXJTd(AHVrYob%{kMYSTn8^E|fI?Cd8X&z@WJO(RKGZ{59`#-~w0tL2`+=y14M$zT zILl8(Eo3%oM;Dk|Q4jkW)P>%j$o^}9hnDyYHP9PW{)^@PCpq~b)Wk_p_c#@*T@>ny z3ZQnVl*Lsrn7E$Btx@C0p!)T8Q_z(S!C?FewX*rBD_??YxEa-PH)?`IsCH*8zJ%$B z@1olI{N!9|0#uw5wIdlY7_*=*#9hz|B`_h0@~94VQ4=?{xGe?|$DnT60Mx>NKrLt{ zYQV*)iB?*?4%KfHYTSLO_D7NN-LCT#dXu<;8lcW(r(t8%6}CdHxC3gSJ{FHawV!Ox zL`|^3T!vc6I@GhY5jF7vRKJrRx&Id|aUFHV_fP{pv;1rG6Y3s&PjLnaLMtnsCJFf^ZnnFf(9Cb>M#zq(y6Eg&9``&xdF8!J5d84MP1Pe zRQvOo3vZylu7jpJKPi<#weOB<*9+b1IGBR2Y$WO@lZmK>EJH119csdD7=njT&%k9= z|NE$RPc43nx}Yzpeu2}RI610cW;5?J?!N{oNkS{CfZC!Os0GzUEvUKW`(j1nL0Ay? zq89qr{1?^FKh~KrG3wT&K`o>(s=gwsU#(c~zwSjN5?XN!)YoSh48v|1iX%`zU`)s2 zxCZqz-Xqk4eWp8aPXKD7a5FP%qWq{GDvlZ_+Ts>&3W-Q`!o)ZLHNZHFXJJC(m8g|( zwfsH|AwG-he;>7w*O&x9pe9T>!`ayssIAX{VVKKwm!qIt&=7SCTA&8%fLeKs)%Uji zAj`YWv8bJ!j9Tb?)R)p4)Xp9-k6QgHi*F+1yIuDwXr<3kD}0B#1wJ#K0sT=6ONtsW z0@W@PCdE9cor|`7BUJmgmhXz%p`oas0ViPuuEeCw?>bCDE5CtS`7_kL^_}HxWjfSD zR1lS~g}N0nsD<=J?bry^g^aQMRMZYGK;6=17RRA>Y$xVse%BESIq|(2IotV-W_>I~ zegx`@wxI^vhtYTn^I)<${Mi93VRqb#rSLA6!Sr*T@BcQKj(7(~;x%+vqu@P{Keb>@ ztc_=JEM}bV{E=!6Y9S9%3rVxUxfPM9XD5f5A9cmWP&-r(wIkK7zLCW(Q9IUo0sF6~ zxH}2G*TYfqA=JITjGEx4`2e*8&rlP6L_O`^3!MqWF*$L1RKH?oS=2%*oApq)pyfjL zKLv#t66tU>>K-pgJrny;57!ygfOjo^f|}?fYT;o&JAd+Ng1UfZsPfKW)XrMW$l`cf>NF3_vK7qQDE2yo%joP{ws87aEsQQG9odJ`g;t142Gou!i z6ZOGU7;9oRWar$jxfDu~xQ&%C?GooH?uc69G}N;&4>j;&i&vrsUXPk+8)|`jQMd3U zYN3Ck7Wxj=F7Ov;M?%r_`@f77RFMreVP3Ns>KQ1HT5(;}typaJYf=3+qbB&>;?t-r zy=?I_)WTn*F65KNUQ4+EH;DiWst83bAh{|qy~R=ZD{%qTK=)DYpQ1hiU!oqWc*~qP z92Mt8EwCi&8K{W5fQG1@YK?9cdQi}WLs9p9ET+L&)PjCPO}xYE_hB~T6Q~K^p?2yk zYNCY8odu*ty$!ih^<_~HVQtjg({VZXUlqeisN-a7xCniSe?@-3R%N4=ucePtcAYB4b5hllDHM> ztr?8E6;n|6ez~~=^>ClWY8brI>DL6C6Zdgb$VK4{s)1{j^O;`V9E#eq^{96L!}R#t z;$*9xTU8PBl5dOAI0GxH{%#|U$aD;wa$D0BQ~I73D&{)SR1RZ zb6%@?s4L%!`k*?C5%?!&!1(K(1?9j<;;L8}zeg>66$auT*jVrXMGB2bW5)M;xVZHGcZ{1|9lEs z`D)Za>ro#lTP!|`8t4oL<5kqt`xrIQ3)I%XL5-7qqjN#2u?le{YTTZf2nS#j{1H9x z|6ObFH)??Q$h~oWMr~DtIA;M(Q9IQZqp%w$z&WT1mZ2834z=J-7>2*2?)e!^i`P-( zevV`RHGtP9r$aDmfMlo+X)p-0T3irAh|8d^q%LYfZBRSY)$)T-{l=r(&q6I|6>8@; zqHe{uP3*rK9wyNd&!Yw`v6%-9qft-qQXGlbQ9JbA7TXEbz}-*-_d^Xl1ht?E7SBOl z=t_$>q2@i{rl5wWQ4`-pZRKP0Z_B?&4eY(u`PE7Y79y^PS#dmSA#tcH-evJg)VP<- zJE#kIikjE`7X@wg7t}os*yg;y1u!*nT`Z41P&*Qb#W7^Nvyd8Cl6W%ebKwMPL61=j z3fbW-ASLS7WEz{u<7Fg4*)us1<)kE!1zPGjT9#fhke-SyAoq54fjwVQ!@ zHWs4Vtwk+h8z#m5m>VywToQGQqEQR0ZgCyd1vEk} zq#3GzOH7P0sP;op~*&O{C)8{Dkq) z`-pP|fv7kP^}&@2wbeOM&r(rLj#V%}w!vgL6Sb4;P`6?idj9>-ehM1!2nOJ3)Pk;{ z7IF{uusp^9e2X3ME2>?Gqs~HmqMn8R7XOHvc(S=1wIjb{d^~rQ{ZB&S28js#2eq=0 zW6pxYQ4^*`<+I{6%!PAtAL@#l|KU6fF{m9Ig(Yx2>cS4A`kyi{pxRyggZ)>7yCk$_ zf1S5gvn#q}(1hoQuSP!mi-^`DNq;sqA3KrMWO+X{QE z;-q=Wyp2ILe1d`a*79B_oP`9U78Z&rFdQ{eUev_JQCD68)xRogyat#P-EAo7X`F(( zvZJVuXD|U?MRjTcB=rJJb#jL>B6HjiaE6 zV^JOEnM*9c%HrS5ZKwtAK}~oBb!FEq{}46KU#Pd^tiFF1i=F02apKD8`TpNd zK@In#7H|x84^LbEDr&;JR{sJM5x>R)=zGTLR}>?OOQUwE18OIFV0!F_+Ns&7{=cB- z{g0!d4%<-=(|(H&qZ*#Z#CRKZr7z8|sD%Zeb?Q@~b|gEhT}jl0)lds*h`P`gsDACw zvj3|1-Vy_>!4Kv{b2@4v^HB>~iy`ft+u8s|FdLhhN*FqHTWYDWXkIe(@M#q7kn z&T;>>!X_jNVmsW6b8#g$JMYx{UU0TJ5jG~D6f0nNjKMgoPkYg6ml?ItDAe0f0>iLA z>dHG|D(vH?pl_)us4ZKE+M=DPD>{I>CC4m(%JP>`1KvbU^pDm1Typv)KwWVt>X}My z`3$I?%ZAz+cX0|@QBA98in)lpVkFK(4Y1$pkDwNI6}8oOur&UO$PSciB4 zHpIWo=qt{*>H;jR_x~D&LNo}w>R1)a5|6+PcmVZT{t%O4#%oS}G1Qe0!SXl>8PIhG zi(vfg&VtHeH1TL`g2yp4M&97|>iw@yp*V?^SQ+nQD$IG)`NXPXw!{YHdt)s;ifJ&# zE$3^tFlHf+K}{5kCGa`wlP~*i=QS*inx{3EbW>POAuT?}(im{Zc_u2G?NRUfNYp~N zqHfVEi!mcfgt3kbjG{7xu8YW()7r+zeQCpVzGB89^gbS1&}oh^?tYhqLK z-(zk(j+*#0YN1gNoUM&P)o;YSco&mml84T>YIe*+Tprb~4{8BRAF}^NDeNRs5Z_=l zMm=(_v={2xSdY4=4^U5i#AD~%D<4J?w?aRhi1Bb52H;%OE&Bxr;s?~j-usEu-u=Yw z6h@QK>o5WJ+APErxDs_G`_UIKVJSS#T3- zq1W9MbcOeE2&Q`KOfVa@(v|3gn@|I9Lru63HQ*7{Kxa`4x@z%ltAA|qKjudaq@DLO zXB>Ah1$9h<8Ym^IK?aL+ngvliRMP5eqZZT*wKMHd3+rt5MD0|6RJ*aL1y4cEGuO$x zT}v#n1`|*bhY4{H>XYguCdb>T2|l0(ay@qj3Pkk_MJ+r6HDNZ3^Pw)Vq*)%dfGVDP z?tdLiG&kF!Cg^JR!(ig!sDXb%4KT~%pDkXA>bJr2JIn)?KaQQKzld5;=@-1B%zHMt>H6k@Q?Y?^4|YA0|l4~Q3EDHT}c|$GmzEd0;nA=joQIztdF&^2`)n| zgsENm0emZKNd8p5W zMdl7Sg@h!|qjusRs^b&XK(A0M{cN%CYiFWh^z49{1~pD3s((S$&Xh)7P*sZ?qT01a zwR88Rpa}+{1{{fLaRLV88q}3-$0WEH)$RhS-%Zp+4^bEJ(&Bd*MeO~?nJ5=(;v%Si zm5}ysS1k&9_!^)l>Shi4U?Son7EeS?Jl*027(~3x+=xkt_n_LHM@@7Cli~x^0zP3F z2EJ8)?tcUYZBZ1eV__VLWl#$`fEwr+>I%=I7Iqo6fXAqN`UaC>qIZswsPRgo#)(Ef z12xRL7^wHZku_*zb~byW2I!Akz;N?NtDl0pMYAkkhFZ{C)Wf+Q)$b3hKZn|htCs&8 z-CEIm3c9i{m>zxJI|F4#4Ui9YkBgxeRMFy^W)svx+M&LzdZDhkAFBOOEQ@2XFdo6o z==#9^dt>AWr(rhKQ<={!fm(4iY9Y;03u%Yi>K>?{bjF}w%O$9ZH=`D`%i?3GojGIq zn-)L&!2avre;^_KJ~{)0pjMvT;`FGAvtn}0kJ`EFSPAQ(u51o!p=Zo1sD5`)<3B_7 ze~(&Fi2IXs#Sy5ESy8tl59%Q+gxaBK)UBz4dOc&z30Q@A4eF!!4Qk<`pPgHg3iY(7 zMeR_2)I8Csg}G}|(9eENFa`EUO*|F#iMQC|ji}dWzj+3=)wfaY9+~e^{o{Xe7Lo#W zt1_UjxTxi$ksaavr=S%!Gkc&09)-G+@u(HYn)9sw7t~Izw)_Tj8)`v&Q42YPdK>Pb zJ|AA8#trya<8uE)C}_eIW;)c?WI^4te5iX}3boMkr~w+G7SIaSt^?{}?S)#vkEko2 zgKD=N^-!+0{0{W|`@h2!BB*$P>gfB`S$R^_gqblMi(*QwgXypcYpMYVf`x|J``^Y4GZP*6udmzU><(d1Z^xE2<`ahM->V;TGhHE@0}FV7a2 z!pg)oF+0w}-nbtlv5>cy=Wn}Pm^-i&`EVaEx95XkxR29uIaa6Q3|_zpUoX$o{Tka7 zm-6%S{NS+^a}wV{ZFwSpFV6>0X4DmCLp?Kj%p$0VuncP9jZp2{_`AJ4_pY-w?2Foo zp{RvSLEVBIsC)R*e21Fg3+k5n#dG3hn3^~O^+8q~HE~6&Z;qO{9cm#lZY%Ud-J=nh z4S&KY+=f~4KI$nB4sZrYi&{V=YQecs6PLw0*wWm9dg#34JM9yr;^e4j#2raNR}h7I zs!O0&UeTT%sz5K#XMHKuLYku9?^dXt>xp_xhN8xqff{GN#Y-(-gPw(8gx>$% z6!h(M1B+nV1YVv$#Wps_V>J0)s2%bNauyJTnkW=CQ3^A?<+Gt~Ssv7_DTmtn?@-TF zH;l*pt`QWpMdMIc7OM)JgPL$Ls^e-@$8A^-kE5<6U9i(G3iSzB0Cmf%pvGy7+MyVW z2cQ-{96i7P8%se8m}JgEEo3oj0V^!O!Qvgbg8Tv0KrsoO_PtQ|b^z+uj6*$~b1Ytq zdUkf9F62l;-hW-ebrQP5N2mebpcdqx$mx(6BZ*U^7E~TpUmbO&4KXM7L|ySh)F<3> z)Oedw3p#|lB^Ry!aU$M-y`S$$=zBR~Vy9ykRL4RVS3*6c4N(&`K`o>;s$CbZT(8rE!m3I@B!+f&70KA^Diw%BVT%M*K-PbYJ-w_dH$tRdejPM zp(Z+rt?@Q$#pS{sXJH!RXQ(Rb^-SDBy5(47NQ=$WvFqEpywe+-NH+# z3(S#*_h0X2p)}6J5sixHq9$IA8ek)K#x1Ch=^~tGCOhiNN~0da2B-@dgX%v6wV(y4 z1uiw$p%%0yg7-fig?%KnwRceqcw`OVp$2lLbvgu~b}S6_ks4`ne$>LE&DyBS)e`$)Zslhgf*}Omd8z4 z4&S2|P&}h^3oD@F%BXScIJ#ZUtU-I!$~&Xy;lgyp<1jC-#%y>UtD}D=FVA1S)Wf>O zdr%XHX7=*@?|>ViK0l^l7F>&3zPr7Y?Z9i)_@68vl-1eE5Y)F~imbfT+It#UROHmVT zM$f%OwLgej=n-8u$5w(d7{+RcQ(mg1R?acc!8M1rhp5|wf8!muH&;v{9_lDhc|B*0 zb^ML|NAf}B>Tw1TSGTrl8DuYY2Z=|~{&&kyqn(Z-oNp-a!m>7=@>UJE_%AjeG{X9$ODWS4F1H zWN~-=j{0xMPsBSp%ad5iOghS8N7@|ad``TB^Q6_~`A^>;ym@k((YYrL_-Vq`lC!T} z<56<8>6C(VHu;aVJ4CrL`F(hR+*iECsbeT>E{~K+-RG?6X=rRprIr)fl-J@bY`Gjg^#~NHreIG1CeO1n;lp{IY#nz~n z%Ke^<-k2>m6-C!?j21}Tk@9dxYfhU-oH|ZY=S$sd%!*Aoi*V{#YpSfD&ULJ&U0M3> zp*)K7AL0h8;Haz{Ig$x>)3F2<>1o)Wir&NxDEo3olix|akNi~18?XrZU&$rpd`3At zW8@&uOUdu4_jf43T;qC< zQ>{V+>v%w3A8a*n3UxDR^X;hrpSFKkz7yu;)TeEV|I~+A`6PM1_B_W4a_-JFo^6%m z&12?48s6d@K=a0yTSUB`IJ+~3=bwLnAXkz4Ntgin_Hec5)Ug;(TlpSNpgsY%V|O`p z{rrGSaDikg&c(#vqmKUM`>?=~oYRPVa8@Ok)amSMMf{4kYpDN=fj{AM;(Us64`*-M z{K8q5+-c5ol)unc$3V~j1(Q>Dy&&0z23v{yaeilwvSBzA-es^ZuBQO+DKlh)%m8OR=l=JnK|`BpJPj(- zu)cNpA9-);lXLc>(-rb3DHkK3gmW(Ea?Z-s)g-@;`sS3&GDZ~}V=48moe5q0$;YR> ziJa%(^3;UBo`6q%2GEh5b2Yg}G#twyw<(V#H-h?3fAI)@pPuzj{7xcvZ=RNUm zi(g`QU!JMNmN@3Lb2YX2M~lT0%Y93wws-+v;~Zd%DZ~UlIJ0p!U|}uE|6mu@f%09> zoV1^b4LyT#Pm7XBMWb0H(o*s5$V{H!wz(p)J9+&~_9OCpCjO$}TPw%qt@j(l8NUdk(J^O|_R{)9dIH`+D*YKu zM`z0VtL4F*A$A4)zRfj=d<^wJQx3JZ7wJEfxFc~j$|tP-721`c{YDLFM_aQvZR&r! za^+Ic`8ti$QPGBYuq~=T0 zTa;4#p3gOa(`!iVZ}r37?dg)3@SHUl=@`BoxncMZ=Wpa1GfiqOle~^**p#~R^q)%X zM|lIMj%u`PPB|Cl^Va?b?P^+m0m^r%i_udsnV>U`+EZD9vW`WZKalexPK9+Durc+M zIRCXlym5=QSD)`_Go7|Nws7)`Raafka2z8>-5GZ9YozWtG`U$4EmfPu1h(9av|Ccw6^Nnja)bCZz!B=twO zOSsA^e8}r#;M-Be7L=03dQnbBMOMoBb<%b>xuN8WS%*!;;q=czT|-Vk;?>lRA~%5e zFsBaB*H;IE5)7oHFXic0*@E0y&R^+Vo7@>XKfymaUvVBLzK>a`kKi0kxgcXKLmmCe z=~zg+0@#JP4dZ<#u0xx(KKw0V6cv?7d^`5j=pI2&8dk-as+=^OifBhUZi)W38M zQ^tnhN?Cuz%|s6!4>&85J4O8+&SbQ&Kpf3kkMg&pDDD3u7m3NOuj(>egRXeU%3EwM z&weS_ih-grGXv-sbuVb}oLp{e_&o!@qwXwqi6|GKoRTvyQ{3!%8;5h}df&s9^>8q4 z`f|1;Uz|1vD0ic*=KAH+w_^h1ji7z1*g8$aLf%n5$41$KD`NXLP3w+NX{8g(-K6E!#Y-^FT_yt-COakeO7m3iiq~B5E^PEZb zW!%&j(8eY?O2eGwq8TWZ@=ug2FvvyDew2UbjHUbzC(vgJHp6%1T5yh`UpwMH9Br{Ua0oXDGxvmI7ZfMo4hua;r%Kr&^B{5jvy_1lq?p+^wZ$05{ZKpkml zzl?YY<$XAjID)#u^nFIIAmy9nI#7;B`5``~-5Jg(P91lte?t3p&Q}nFk(frJFivpV zc>bG<4ow&&fV_?`#A|F2#g8awpx<|tXT{!VnaZ7o*5}Cnt7mcjp7Uj0Xt>t%5%7USAPG8u!kvw{6Eh6b~2@?5cV zzDwnfBlE#B$1xMVvfwy+?)}g3gD6iYUyi(vC6p^UMgE}`e;lEnlq-l?R$vC=1SHZ~ z`zPeO*h(HzuEu%In?H}lQ8;K7^(h~xqkdTWc4VVahk;sabdGt{9iz=pl*6puZOWmX z{=`+S-8ig5F2oj)oj85m;#O4xygJ7{?UXTb?4?o3;`|2sBo1iTqkH?_om+R0^BcF^ z-@o<1)?Hh7i}iY!G;Y`A-*N{Q$(g56y1Ye-|?P9Ly31{Qap*w=W%XR$WOv) z<2ae1fY}_g5Rb+H++^-F4`2xSW2gmPK`k(#t!o4}AkK@runCr5g<9A?%)kBs5V4)IF|laTC;ltx+Aipsu7RYC-)`{U)K>&9wT37>9T{YP?OD3io1q z{1esB$J5y@1ffwNtaqm8c!rfpPF4 zs=enV1zpiO)QT=!eAB#-y0WLJg}gxxv(-F`n(zwh*?EMT;1gy;|E}(i=D}XXB~S~C zLABe1etQ28Q_ut_tl~VzCH~Xfz;Pa+CVGas@ss6qb#t7I#05|TH$yGB9VW-FsEJ0S zc3_gli_BH%tM`8s1$}^QLrruRwG+26KE5=4y1NStM%9O-b|gEhT}jkJzCkUdK57BY zQ1`qo>Q?u_VC;h)UFm2FYB&wmaV{pn#i)rkp|Tt<^dK<@Q-9~KbQfxZ!&nnfU||gZo~yumsQTUJK}<+| z6!nRB2}AH1YG<6Dj*|ispuVKCpf0?8PxfCcszpL8ZiKp`mZ*GN%XdW$+zW$oyw%UO z`X#6Ztu{AWej92>_MrOxikkQ;#>4v_3TY_3!U#;tqpE@Op;lf5wUA0?P0UZ+5G&v$ zEPxlV62|N8IJL1J*1}a-4Bul3EZE0!ieewsj(E0E$VK54M&c(dj;Z^)_pTY1BJPd4 zg6)_Wk6;#jk6AH&KgY>~HLw5<$7;CC^zZLD<%sK;voX2e|7#QyQSrtM8sM%lC6*`O z5Vg_;sDXE34t$Qf(o_T4CHxK<&{>D!_zPCXo0tK!4RYVIT9}i#8;0rqUqC?(cAD2Q z6R|H}eY&#js16OVA&$XP_!ny7X@fKd7WI^u8sWZ8)lltf zp~h{D`W)#xg8kQ4^d+Gyn1Ooz)>wm$sMm5A>Mb~c_3#*~U8<4p&jIOB3o3#dxEkup z>thP+g4)S3*bTR%J`ppFVoNnau2JrfUIkGrEsDyQLk(CJwSao)hs`W*jjHc#ac^@l zYMfE1aVDW3&OnVb54C`$9!qRMHQbK6r+ZNYoU!~B^d-J&@jde?>a~1@vCwz4yPyEn zL?Nhg!YodUx;0r*A55MC6m(C@TcRQQ5w}P0JwkutzNn5vP%9sg8h8e(-2zmQkZWGnzTE9C0Dk zjtxeQI~@ISia84dnBQ4IK?ANpUBM>Qinn_k@cLQ(ajZ!G99F{cv2Ob|s0liuZbd)j zqrn-A0q8%@eYg{#Zf!bL`+^w8{7!ilu!Y$lvk*t4zSZ_(Qv40|Uf;!%_z|_>;^W;N zD2tk~8aBYXsQPuNo!DaaJIwv){rUf>H8^DzXEBftS1=CVM&0{o7QaVrrQZa1;CNV? zI0W@UR>$%y&2<<=ely0!-KhRYC$Rsj_>F|F^b+b}e299V|Fe98AKVV9Q47n6y3(AM zFNnIr5~!UlYc@kY<$X~*F&fqX2a9L?!2WBXc~-I1D%PR$TTm6~{vjl)~ans0s6%#Zdjq znpIH?se^i!8loodi0aqN9B2;5K)wItC}^N*Rx#IHY_38rWFzX~+Jjo?MXUc4)&Dle z#>c3I{Ece&3iT!T9yLz#DQ^1=7)S4a4hq_uLP}t1vl_-HZh#uNE$T`-qT2VsDD01U zaTVskKT+-DOm*8OK=n(8dT3Ljeh|rw9<97I1x;KTHDO&0!B(hy-wQRsP*l4y7EeQ6 z(HvC26&7zr^*d;uLiN9lTKHe69eOy`-v4JLw4#5lB4nETvtLroPrfO7Z;?3{btOws z3s{G`HM>v?IcN1ZQT-mHZpBMf`*)~sOaEy0U!TcA(eCRPf%>S;g+;L<>SwwUs1+~7 z1h@<}QH*&IHPLC*4*h`|=a$9qFqqhPy8Cb@M)l9&u|!_fz~xX^R>$(qFod`pYJj1r zg-pT3I0H4|8r0TqLv8&&48;@Xb<{0*fok^-HI66N40q)Ls0P8PilmkgH`AfEE*om0 zg-~Bc6;WH;!fb2xoh=@K+L@uKg^oonFdDf99%ms14Y&lgvdyRge?m1pfJyKqCd6Bo ze~D`U+46xi-5m-;{mhpIQ(-wwf~`^W^hYgxEQag-Uqm4>i9M)?=#005hX!>k0%o}j z2|?{l1nNrCT0RHr3Jas|X=#gVpmwYQ=EOFb1E-sZFuUIW=M)NH#BBG9>Y@f}h83_g z=E5!59dBb+tTV^`oo)!0BHoJ)@DoO2{kiUs?|rc<@dD%rXXg>t#BTH0|1lKyQz(KJ zf3)A-P+PkTbt?{`o{eMXY19>8MD5UZ)Q&u``j-~JN9~y3eD@)agXM`+THIzRl zkk-{Ch8U~Kus8f$#E~L-$nBpY9V*bzfrf~J%(Yx0(WQ9pkC`T3)p`> z6wOKK;p&PSaEQgDP!r8Wt$ZVDD_^5t%hC(o_H|GTY>c|%?x@$b7iuR*q82_8bwRT* z87}l#Vh3vF`%xWESpFPpz^fMjW$^>lfKO5F-=Z$SS>%53grL3yGNbCVqsGaPT4)i} zj(BQN&}-Hab>+QLTR#Z3b>lG+&O+6%K@GUs;-64gbP%Y(R~> z-}1kpF5r|`4-XFoJp(sTD}IK$6~&jj4J)Artc9AOk;Pq5SK7I^EBXdCaeZsh46_h-L`@Kl+NpV{iPoSNv>Wv{{A%^rFe&j9 z)Y}t#h1)MBs$VvXi>=W8CsCDzKh{7kq&}))3(NOFEugRIK`mqq>I!F~u4tLnZ$j9d+-^nDwy)aVOO0 z!)jE&*Vq&ju5y1=>x!!X5p&@Kj}^jJyIWQTqp0{6)8G_~x1es-P0Wp-u>$5^oiftm3g zR>!oP+X{jc0qC*(7z`wyivBnkbt@O6o`Dq@gzHf4wxjp=|NYkBs5Llc@gJyx zu37#LYQkrhe}j4kKB5K;+3eP*zy!o;QT=mcd@O`ocm>orRWOm>|Js&liyEjaszV>t z%15FG8jsrgsi=XrqONEsR>ni9f#Yp)U(3YUh&VlJydkLiA5i_Lqeu5>?2l z52NBU7=nLdFg`;q=o9Kn0=K&Pq^N!wQSI}hCMb{Ex$3A}Q5V&&HMYkdsPQgsW&fK| zxJ5!wZ>eqkp$Pk-w&(+TcLFtV&~|s=M5uw2qZX9O;{2#9EoX5})Wj`N?Yf}G8-Uu$ zksd4jU=`C*124eRxE>4O-E$)OGxTiT7bpc~g6Hh?x>>SiBU50vn zJ-<`X2gWlji*a|lJ5mFS60b)sTjVI^c=OI*BFXEyWMeZz@Q+M=3jfGtrSyPy`<19dO^p(Y$*jz#sGh`JT?QSDY@M%;pG ze;GB;b@XUU?@&;~$Ec2PEOz#|6UQ}!%}`YPWT^IOQCE~5wcx^*FOOPy4YLmF0ve!p zy2T#uzplK4RrEzI$b%YiG-}1s7z<}xJkMN&nqWD4ZxwnULh}G>K_^h{E}BK@%fE$o5CPf-KBL@neks{eaThynZD_Q_G>M4%R!5!F5) z=E8!g@oROkLN~J)s^b9EL?ciO7=xPN2h`5Y$Hce_weY>De#fKFqzm<^#dT}>5Vgct zsJ-#o?_PUc)XfjYRz5sM_y|WGaQC{$A@}#?u{fW0+p#9r{n`D*T8`C;520obIqW#4 zFvVe>D!q73NJL_H%#KsAAMVBCSl}1;Bd(XZ33XGRn6J$LP~-U?aX<2sqi$|#%!&7(k*824WY~E$EF}h}!At7>R*64cp;7RJ&Nm-G#I{ITOoP=80denkqP!sO9{9&9%d;;fUvlH$W{fo(n1AcWE zk{XKTQ{3`T3}YtUxVn1BPJ? zYNAu9iT^-d`Cq91_b?v*gE^Sr`9wiaWA;<-fNfD7yJB4IgX%CGp0)yb*TEE zFcBU_-HI!y9e82BL)~iUH+P4VphqjsKtWfM6V;)hS;F$=E&j%=i&|h4)P!wNSJv0^ z!%^c*K)oH)tbQ5lR&7M}JMbI(uPZ!9Lf$}a)nnAX{|7aI-)T3W47Jb*RQn95fpTMf zEN9lnNaB{L_QS9mjKXJcwH8QPkUT3H3}pM_s7zMJIfq%FUQ$f@K%~2iO zpcd8#wbg^MB#y>1cm#7|&?WZ|28FRc@dWc0>RYw&W%j=yg}xLD;70QvmL`t4;{F=j z0`*xw9FyXHRQ*NNl_$UI{zj4oHC|UNgv(J2x{ehv&7bbCxgD?x@u5H2|DqI9U333M zePawKUW$ovzj+?(5kJHlSn|62JJDp!OS~R4;Z1CWK{wn5w?>V#2=)4H!%FxE7RSiH zJnrwm&Hr+@a0u#I*k@kFe8m5v7MkOxduuwP;@Q|2&toa9cFVnhiI|&sHKxX^$kXn8 zKb~UAJml( zMfDqty0Yo0ffri53biva<}Osf{Z@bU3Hz_aS*y5$n&6iC1oe^p1~stnQ+I&)s5ptm zsZjkgT0WOq*z#qt1NGHV3)+UsaQ9R8UsraHguH?p_$F$=N0xtO@dwm`{hqn?@v$s% zGOUJ8P!p^|jk5vu3AqD@;W3PlRsMDt(9lCc?_&>4fMYNN&NTO!x3C!bz~}C3SRQq2 z8lgV0zQ^)78MS~5s0Ca_E%*+$!6#N<>mRqBrvU{uXlAyximql)vp;G;59(fzMcw;o zix;AHa1CnbHe+4fjg2tj3wNGQsE77@q#Zy1Q_w)ePym@WJFfsL6QSC~iCaQ!}#(+M$`Rx&P|8gv4-Mi`wd_H|{|BQ7bNv zT3A`s0CiBG?JY4W4lt*p##@CNXEW*<*lF%X?c5=&KlR4m|MQl(iW=Y+>PjA)FRlKA z8SAaPkRa4UnH1GNGiu^om;?)2{u|Un>Y*;EF{Z(m9tygm;iv&7qgFTzy*p#^T5~(< z899jhaypHg@EofB6)cUnu^?vn*Zn+chOvl8pxTW_JtLmUR+xiY@lw=6cA*w>5R>9b z)Q@JjF%f=7O`PbRyPz;soC$S7QI;=iab?s!uWzQ?kaJ>`Q?J2V9~(NfgH)}nqe*^Xg&9)o%QoM#mDN%u(! z4F1o3ZBm<2sI4uIYFEyzhicyrwU9ojTjfDr@l4AvMeWEs)B<;!C(--<|3yIq+()hW zx%n?@V&|j#)CQpH$?;E&vYZOup$TH$2W zy`G0!=_1qsTT!pgUR1lEQ4i~B)B^6JuJ{eAUF=WpLm7n1C&LI#kEyT>s^7Pt*nh43 zdlH&(I3~lHm;yIoBpyfoc>V}=1@S+-_c$?X!I@AOk{vZ+K~%ePs87JEs2ymGdNx{N z0qpIeP?*9x%!l_dFNQllPARN`8h8q7q4Tg3uEnhQ3VUK|A0O`@9-^@+@lR&5SU%pL ze0pI9^4C!PV#oIJ{=njiqHux4K-AM+%h$*IAMVVsY>)Ga9D@8i9P)y=x--8$4gYh`gy zOi4Tt^Lp=w*|ErQEDv(%*`e4|OdWwHX zZP`WCfVa#?r~zMC{1&y)&!}aw>v_a8Mi{kN4N)Z6tD& zxQ`VuEUvpnEl~^Th?=N7YN9^o5X+B7-Li?OTeA@LEx8BvOr1dWyN=qSd#G`r$Mv`s zZ%F8#d_r{$i05`pf^~?qpsr*vs@+)BC)`xjEn9(_-~eiej#+#GweV}G3Gbj5@W_1S zv5HTqfqmn<4dSEXWVjL|Q3D-A4RjiHZ!e&3%{|n^`Nra)1n#pFhT4G)s0%2Jy1;U% z{+`+tw4&Cihpj7S#QvxSEVBC5s4I=Z9C!+K#UD_gaIu5k0TZDXln!-E@}la?qaMP# zsITSDZrM4yuO|TuckbS6jM^QU*$?`W)7w`ae?_Z#gn%;x{^t#w`V1$!xL8j46_sa zhxmAZ6UvL3iN|6m+>FJT-$|Ls$NTSc8euWw16Tq-qF%#dp+4R}$uz`V#M7}P{({NsLvA%b*_GN!T85phr&#epe&yx)$MumSNP)U7*@`ck@%x`nq;_xO?d5_K#8L*2^Q;qF@$7|#2z z0TPfet5{ih4cAqx#QB-MVES z3R-y#YM>pc_jSL;=TJ}WRn&y{Pz!y58t4P+;fs~h9VY{NpK{bK%!gXYIMizyje0hg zTI_jCK@$f=xB~=ZN8(Ua$HAyix-qCL`w{gJZb4naZB+jks0F=8Ezl>GYaG;qLNO9k zp?0<;vLKIBj)FSWMGe#p)uAnF%X*+bQb$-k1+}oH=6cjawF7m9zo6P*Ks}_lP~*Qs z-NKKU8e^wcJKn!^6lRjhg&O!MHperVQ~5|AX9BiFeK0*iE%a~H&b&oU5HF3pBS}yT zN{^Z-59-#{w)*C%53;Tp!u-w<3YvHdYT&u%63eeaZS_|C27kt~7?Re<`{(s~s0GYM zJxhx%UXB`fgSpe{51|%*1ikNl{xNZjCe_e44655jXsJI{MUJgZF`4n>@>IyfY z?(G)Tz=zFWQ4iNytN#P_EIdT*&>QnJ`Va?Z^tksZPDXd&tY#Eyf;^~<b@|QLkqU zv!~_9pdPMi7SBfA^QD&Gh`PXSsNXA&pcZ!1LqS*c3^m~k)D`Y$2^R>(vzqw`^&tK+PY^J2W4^Z zVM0{9M5uAXPzy|hx@8$q3(92{L+w;0^#1>evbhV%gNlowwz#Cl z6;SVWRn#r1hnlDd>S6PsuJlLL2h9f5E%+JL?lkJV~=5FdoNKP z(x4Xli!PgE8^LHy$m(cKXGMioRUEfPbQNok-5-H1OrjpajS7G}89yC>6c9a=0gw8a>O8Id@U3pAz{jcJ5jaLZ=MV zrm?s)HmCl}F@bm|XIT=a(>l!SS-FI8`8N44ftupX~y}z zUE^_b-_R);=PdGXY4raB(83A zJcz|jn#I66E>p3c@)~?iT$K(!lmoFY=O^mMlg~ou%AEfZ>k*8H-(nTo{DO&zD{u~` z@0X*Cl~>S4hvwfvn~&wZAwN>kU3s+~L%5lDb&by}%4upEEzFj@72h`srN9D%zEx z?|#Z7IG+>OQw2wP-AE4;?51N;DpJv~Efqb8Yg6{&EKhzX@j>!aC~w02Au*S4F8y2G7DZI#;-SYZxNSw!1@N#MfMEZ-Kha_ZAI=~wj$to$4KA&jNt1UXMT8qcuG zG3HV85Dl+!_M>q<%Pl0{K%7-0a1`f!ORfy{KVTs8wc@nl)UgPEv+_+GOMM*tmfhvh z_4A++oFiGBb1`ub)X|IlcP!AuIgPkGXGL;}+|Eu*;(utniuxxE{4YKx&WTTPFK18M zEafag?i6Pk%I|5bqmTFhL&+^Wk4d(t!46e$Hnm0>F)u2R(TA81*8Uwc0#59gY=NZ_Scsg~Ti9d3- zBG%E!#o0(39ZPT&^>LWwBIi?ePy7G^jB>BzV$wN7T2B7?%4g9+enA_qR zRBxl5j`_6P$=T5joh{^#TU`_6w|u8EPT{;jS;zNymNEU@@!ae0MT0sv(Ks4ZqG28D zaESb8&Lp(yPNzS}|4O+axkQ|^IG1vkr>-jbwbVDFT$(W|*%*tdZ|+X$93t;ec{4fh z|8}Q3eD4kT)Mo%4$vIb&t4qVt404U~aB@SaZ%b}Dj{F7bve*u55*wrbmZjHa#3DRn^(khVsn#okQ_uK9RqBl^4N{sQ8(>8umKvAOUPM{_;1b? z);<CE7Kj97Xx8 zwZB5Us#c$y@=fYG=qdPtpaYFsQCXU@jzyd!$bBN00&6f}J<8)bKX7^vjIs8Lo6;tl zwmPyoS1=`d@8NyU=bVR$Z(>I3BRKm}&d(T2P)8qfI_A+XA9f^e!Fcb8tJ7w6 zZ2m1_HY&=I_;MVe(QSgRG^~QHh$m5B#5a0zgHWya16@PN6tm&CQr7R%Y3QNjE@v5X zr>MWd8A|)o#AP{aQvPxjp#2ka=`e})Rb2*aP!5k+d8^Ik-7n>uF;E3e%K-XA-4hx- zA{S*1yD{J^>dsOZk8*Cx$vLAaccI-TYggA6l%4z^)ZZijqt#U*9!dE*V@#l2)`wRr zfW%qr6imf5D^ql$DKDd81sh;U^tFb`JjEGWhn}^G)DGu7MO*;KaQaaHfcPopft1V9 zZY$?XP90mwk0PH1pKlRPTeM3UQHZGo8FvF$QPo`&y+h+R&)KW z=F2gj@rKYoEP7bu(2!SD&$Ll?;IioDjZ=F9D2-Idua3pUSIO_Q{7=}Ja}49%vQ-}- zK1QFoxDF?fpJR35i#|BKIEmaIT8YuLG;Tn)o`E|8YigF^A~% z^$|rplEit=2Gr}%`MntA%TdMJ&m}jEF=BK6|7D@kZ_(?Ur1QMC9$grr7TM4AdTF^- zyY;jTIVw|%XDF^w&oH~A?J|%e_0hk7R(Iy@FHnxiWn44S`>aSaFkp8vK1%mw6 zxg|EGQByi*<19eAee`!tLp%1P)Wcd0qsI>7f=u(qhH7Ya{}6BDEK2V$#|GMdLl8up z3YL#=?JH7ujrNJjHzU_E`f1Z-e(%Xnjt*)T?m16MM+kM@Y15Oo{+v4eIEzvaw=wHm zzBg?f(k~hDEz6Z7H=c4nd}_IuIFvEA(|?`i{-xg$;xn9y^kv-07SO^bIYPtif02QDQ{K%PP5CvBrO#q)h;PX?;~YW1Z;5-7PlE#)<09_01tce*l5;b0{8)UP z?XZfxBx^+HZXW6hAXAdapYsJ>zZ_W^dKf`197tVi)RBVr%ZLY2-jCym!>P+h-zVhq zP`*m89c5q2_wXU@PIG4E)NzCQd$eEgeg*O4JBi68^5F!xjrX59=un?Q{K)J0K)l)p zQGAzjYWlUHJTp3ei*QeRTAv~NN!4~#Ag3cEEp@E5(NtdZRoRz*HPxSdO4?SpnZCtH z;`OvGjz3#o{fCqL&f*e$T{&AxhBC;vHgGKLM1$!zdA8`|EORrNHFJdoN7HlP zSHt(GJe7P&@;Vk$uILt>7Z^i5DJO_oR$?0BAQBPQ{vo;cwvzjlt8!k7#qT3A6b@QN zUCJluILKl`@4rl|&OpsII>&74j?rck<%HJmFUpBHV-r`jcB8Q}xdgU=OvI5fXIfSc z^l2Lt-Z5Rmm}dRo2gkG;x70r-c*>!iaq{QLRX8$tz9M-FL|@&X>i=P`!ZEQAWQ&NY Udu?cYlZ42W6&HY)%B0DprgSO5S3 diff --git a/django/conf/locale/cs/LC_MESSAGES/django.po b/django/conf/locale/cs/LC_MESSAGES/django.po index 4984d4251710..b1a2e697d44f 100644 --- a/django/conf/locale/cs/LC_MESSAGES/django.po +++ b/django/conf/locale/cs/LC_MESSAGES/django.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-05-09 14:26+0200\n" -"PO-Revision-Date: 2010-05-09 14:09+0100\n" +"POT-Creation-Date: 2010-08-06 18:35+0200\n" +"PO-Revision-Date: 2010-08-06 18:33+0100\n" "Last-Translator: Vlada Macek \n" "Language-Team: Czech\n" "MIME-Version: 1.0\n" @@ -70,7 +70,7 @@ msgid "Spanish" msgstr "španělsky" #: conf/global_settings.py:57 -msgid "Argentinean Spanish" +msgid "Argentinian Spanish" msgstr "španělsky (Argentina)" #: conf/global_settings.py:58 @@ -122,138 +122,146 @@ msgid "Hungarian" msgstr "maďarsky" #: conf/global_settings.py:70 +msgid "Indonesian" +msgstr "indonésky" + +#: conf/global_settings.py:71 msgid "Icelandic" msgstr "islandsky" -#: conf/global_settings.py:71 +#: conf/global_settings.py:72 msgid "Italian" msgstr "italsky" -#: conf/global_settings.py:72 +#: conf/global_settings.py:73 msgid "Japanese" msgstr "japonsky" -#: conf/global_settings.py:73 +#: conf/global_settings.py:74 msgid "Georgian" msgstr "gruzínsky" -#: conf/global_settings.py:74 +#: conf/global_settings.py:75 msgid "Khmer" msgstr "khmersky" -#: conf/global_settings.py:75 +#: conf/global_settings.py:76 msgid "Kannada" msgstr "kannadsky" -#: conf/global_settings.py:76 +#: conf/global_settings.py:77 msgid "Korean" msgstr "korejsky" -#: conf/global_settings.py:77 +#: conf/global_settings.py:78 msgid "Lithuanian" msgstr "litevsky" -#: conf/global_settings.py:78 +#: conf/global_settings.py:79 msgid "Latvian" msgstr "lotyšsky" -#: conf/global_settings.py:79 +#: conf/global_settings.py:80 msgid "Macedonian" msgstr "makedonsky" -#: conf/global_settings.py:80 +#: conf/global_settings.py:81 +msgid "Malayalam" +msgstr "malajálamsky" + +#: conf/global_settings.py:82 msgid "Mongolian" msgstr "mongolsky" -#: conf/global_settings.py:81 +#: conf/global_settings.py:83 msgid "Dutch" msgstr "holandsky" -#: conf/global_settings.py:82 +#: conf/global_settings.py:84 msgid "Norwegian" msgstr "norsky" -#: conf/global_settings.py:83 +#: conf/global_settings.py:85 msgid "Norwegian Bokmal" msgstr "norsky (Bokmål)" -#: conf/global_settings.py:84 +#: conf/global_settings.py:86 msgid "Norwegian Nynorsk" msgstr "norsky (Nynorsk)" -#: conf/global_settings.py:85 +#: conf/global_settings.py:87 msgid "Polish" msgstr "polsky" -#: conf/global_settings.py:86 +#: conf/global_settings.py:88 msgid "Portuguese" msgstr "portugalsky" -#: conf/global_settings.py:87 +#: conf/global_settings.py:89 msgid "Brazilian Portuguese" msgstr "portugalsky (Brazílie)" -#: conf/global_settings.py:88 +#: conf/global_settings.py:90 msgid "Romanian" msgstr "rumunsky" -#: conf/global_settings.py:89 +#: conf/global_settings.py:91 msgid "Russian" msgstr "rusky" -#: conf/global_settings.py:90 +#: conf/global_settings.py:92 msgid "Slovak" msgstr "slovensky" -#: conf/global_settings.py:91 +#: conf/global_settings.py:93 msgid "Slovenian" msgstr "slovinsky" -#: conf/global_settings.py:92 +#: conf/global_settings.py:94 msgid "Albanian" msgstr "albánsky" -#: conf/global_settings.py:93 +#: conf/global_settings.py:95 msgid "Serbian" msgstr "srbsky" -#: conf/global_settings.py:94 +#: conf/global_settings.py:96 msgid "Serbian Latin" msgstr "srbsky (latinkou)" -#: conf/global_settings.py:95 +#: conf/global_settings.py:97 msgid "Swedish" msgstr "švédsky" -#: conf/global_settings.py:96 +#: conf/global_settings.py:98 msgid "Tamil" msgstr "tamilsky" -#: conf/global_settings.py:97 +#: conf/global_settings.py:99 msgid "Telugu" msgstr "telužsky" -#: conf/global_settings.py:98 +#: conf/global_settings.py:100 msgid "Thai" msgstr "thajsky" -#: conf/global_settings.py:99 +#: conf/global_settings.py:101 msgid "Turkish" msgstr "turecky" -#: conf/global_settings.py:100 +#: conf/global_settings.py:102 msgid "Ukrainian" msgstr "ukrajinsky" -#: conf/global_settings.py:101 +#: conf/global_settings.py:103 msgid "Vietnamese" msgstr "vietnamsky" -#: conf/global_settings.py:102 +#: conf/global_settings.py:104 msgid "Simplified Chinese" msgstr "čínsky (zjednodušeně)" -#: conf/global_settings.py:103 +#: conf/global_settings.py:105 msgid "Traditional Chinese" msgstr "čínsky (tradičně)" @@ -305,15 +313,15 @@ msgstr "Tento měsíc" msgid "This year" msgstr "Tento rok" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "Yes" msgstr "Ano" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "No" msgstr "Ne" -#: contrib/admin/filterspecs.py:154 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:154 forms/widgets.py:478 msgid "Unknown" msgstr "Neznámé" @@ -359,7 +367,7 @@ msgid "Changed %s." msgstr "Změněno: %s" #: contrib/admin/options.py:559 contrib/admin/options.py:569 -#: contrib/comments/templates/comments/preview.html:16 db/models/base.py:844 +#: contrib/comments/templates/comments/preview.html:16 db/models/base.py:845 #: forms/models.py:568 msgid "and" msgstr "a" @@ -853,7 +861,7 @@ msgstr "Uložit a přidat další položku" msgid "Save and continue editing" msgstr "Uložit a pokračovat v úpravách" -#: contrib/admin/templates/admin/auth/user/add_form.html:5 +#: contrib/admin/templates/admin/auth/user/add_form.html:6 msgid "" "First, enter a username and password. Then, you'll be able to edit more user " "options." @@ -861,6 +869,10 @@ msgstr "" "Nejdříve vložte uživatelské jméno a heslo. Poté budete moci upravovat více " "uživatelských nastavení." +#: contrib/admin/templates/admin/auth/user/add_form.html:8 +msgid "Enter a username and password." +msgstr "Vložte uživatelské jméno a heslo." + #: contrib/admin/templates/admin/auth/user/change_password.html:28 #, python-format msgid "Enter a new password for the user %(username)s." @@ -1418,8 +1430,8 @@ msgstr "zpráva" msgid "Logged out" msgstr "Odhlášeno" -#: contrib/auth/management/commands/createsuperuser.py:23 -#: core/validators.py:120 forms/fields.py:428 +#: contrib/auth/management/commands/createsuperuser.py:24 +#: core/validators.py:120 forms/fields.py:427 msgid "Enter a valid e-mail address." msgstr "Vložte platnou e-mailovou adresu." @@ -1491,7 +1503,7 @@ msgid "Email address" msgstr "E-mailová adresa" #: contrib/comments/forms.py:95 contrib/flatpages/admin.py:8 -#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1101 +#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1109 msgid "URL" msgstr "URL" @@ -1541,7 +1553,7 @@ msgstr "komentář" msgid "date/time submitted" msgstr "datum a čas byly zaslané" -#: contrib/comments/models.py:60 db/models/fields/__init__.py:896 +#: contrib/comments/models.py:60 db/models/fields/__init__.py:904 msgid "IP address" msgstr "Adresa IP" @@ -4473,22 +4485,22 @@ msgstr "weby" msgid "Enter a valid value." msgstr "Vložte platnou hodnotu." -#: core/validators.py:87 forms/fields.py:529 +#: core/validators.py:87 forms/fields.py:528 msgid "Enter a valid URL." msgstr "Vložte platnou adresu URL." -#: core/validators.py:89 forms/fields.py:530 +#: core/validators.py:89 forms/fields.py:529 msgid "This URL appears to be a broken link." msgstr "Tato adresa URL je zřejmě neplatný odkaz." -#: core/validators.py:123 forms/fields.py:873 +#: core/validators.py:123 forms/fields.py:877 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Vložte platný identifikátor složený pouze z písmen, čísel, podtržítek a " "pomlček." -#: core/validators.py:126 forms/fields.py:866 +#: core/validators.py:126 forms/fields.py:870 msgid "Enter a valid IPv4 address." msgstr "Vložte platnou adresu typu IPv4." @@ -4501,12 +4513,12 @@ msgstr "Vložte pouze číslice oddělené čárkami." msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "Hodnota musí být %(limit_value)s (nyní je %(show_value)s)." -#: core/validators.py:153 forms/fields.py:205 forms/fields.py:257 +#: core/validators.py:153 forms/fields.py:204 forms/fields.py:256 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Hodnota musí být menší nebo rovna %(limit_value)s." -#: core/validators.py:158 forms/fields.py:206 forms/fields.py:258 +#: core/validators.py:158 forms/fields.py:205 forms/fields.py:257 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Hodnota musí být větší nebo rovna %(limit_value)s." @@ -4529,13 +4541,13 @@ msgstr "" "Hodnota smí mít nejvýše %(limit_value)d znaků, ale nyní jich má %(show_value)" "d." -#: db/models/base.py:822 +#: db/models/base.py:823 #, python-format msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s." msgstr "" "Pole %(field_name)s musí být unikátní testem %(lookup)s pole %(date_field)s." -#: db/models/base.py:837 db/models/base.py:845 +#: db/models/base.py:838 db/models/base.py:846 #, python-format msgid "%(model_name)s with this %(field_label)s already exists." msgstr "" @@ -4559,13 +4571,13 @@ msgstr "Pole nemůže být prázdné." msgid "Field of type: %(field_type)s" msgstr "Pole typu: %(field_type)s" -#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:852 -#: db/models/fields/__init__.py:961 db/models/fields/__init__.py:972 -#: db/models/fields/__init__.py:999 +#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:860 +#: db/models/fields/__init__.py:969 db/models/fields/__init__.py:980 +#: db/models/fields/__init__.py:1007 msgid "Integer" msgstr "Celé číslo" -#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:850 +#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:858 msgid "This value must be an integer." msgstr "Hodnota musí být celé číslo." @@ -4577,7 +4589,7 @@ msgstr "Hodnota musí být buď Ano (True) nebo Ne (False)." msgid "Boolean (Either True or False)" msgstr "Pravdivost (buď Ano (True), nebo Ne (False))" -#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:982 +#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:990 #, python-format msgid "String (up to %(max_length)s)" msgstr "Řetězec (max. %(max_length)s znaků)" @@ -4619,44 +4631,44 @@ msgstr "Desetinné číslo" msgid "E-mail address" msgstr "E-mailová adresa" -#: db/models/fields/__init__.py:799 db/models/fields/files.py:220 +#: db/models/fields/__init__.py:807 db/models/fields/files.py:220 #: db/models/fields/files.py:331 msgid "File path" msgstr "Cesta k souboru" -#: db/models/fields/__init__.py:822 +#: db/models/fields/__init__.py:830 msgid "This value must be a float." msgstr "Hodnota musí být desetinné číslo." -#: db/models/fields/__init__.py:824 +#: db/models/fields/__init__.py:832 msgid "Floating point number" msgstr "Číslo s pohyblivou řádovou čárkou" -#: db/models/fields/__init__.py:883 +#: db/models/fields/__init__.py:891 msgid "Big (8 byte) integer" msgstr "Velké číslo (8 bajtů)" -#: db/models/fields/__init__.py:912 +#: db/models/fields/__init__.py:920 msgid "This value must be either None, True or False." msgstr "Hodnota musí být buď Nic (None), Ano (True) nebo Ne (False)." -#: db/models/fields/__init__.py:914 +#: db/models/fields/__init__.py:922 msgid "Boolean (Either True, False or None)" msgstr "Pravdivost (buď Ano (True), Ne (False) nebo Nic (None))" -#: db/models/fields/__init__.py:1005 +#: db/models/fields/__init__.py:1013 msgid "Text" msgstr "Text" -#: db/models/fields/__init__.py:1021 +#: db/models/fields/__init__.py:1029 msgid "Time" msgstr "Čas" -#: db/models/fields/__init__.py:1025 +#: db/models/fields/__init__.py:1033 msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgstr "Vložte platný čas ve tvaru HH:MM[:ss[.uuuuuu]]" -#: db/models/fields/__init__.py:1109 +#: db/models/fields/__init__.py:1125 msgid "XML text" msgstr "XML text" @@ -4669,22 +4681,22 @@ msgstr "Položka typu %(model)s s primárním klíčem %(pk)r neexistuje." msgid "Foreign Key (type determined by related field)" msgstr "Cizí klíč (typ určen pomocí souvisejícího pole)" -#: db/models/fields/related.py:918 +#: db/models/fields/related.py:919 msgid "One-to-one relationship" msgstr "Vazba jedna-jedna" -#: db/models/fields/related.py:980 +#: db/models/fields/related.py:981 msgid "Many-to-many relationship" msgstr "Vazba mnoho-mnoho" -#: db/models/fields/related.py:1000 +#: db/models/fields/related.py:1001 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" "Výběr více než jedné položky je možný přidržením klávesy \"Control\" (nebo " "\"Command\" na Macu)." -#: db/models/fields/related.py:1061 +#: db/models/fields/related.py:1062 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -4697,74 +4709,74 @@ msgstr[2] "Vložte platné ID položky %(self)s. Hodnoty %(value)r jsou neplatn msgid "This field is required." msgstr "Pole je povinné." -#: forms/fields.py:204 +#: forms/fields.py:203 msgid "Enter a whole number." msgstr "Vložte celé číslo." -#: forms/fields.py:235 forms/fields.py:256 +#: forms/fields.py:234 forms/fields.py:255 msgid "Enter a number." msgstr "Vložte číslo." -#: forms/fields.py:259 +#: forms/fields.py:258 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Hodnota nesmí celkem mít více než %s cifer." -#: forms/fields.py:260 +#: forms/fields.py:259 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Hodnota nesmí mít za desetinnou čárkou více než %s cifer." -#: forms/fields.py:261 +#: forms/fields.py:260 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Hodnota nesmí mít před desetinnou čárkou více než %s cifer." -#: forms/fields.py:323 forms/fields.py:838 +#: forms/fields.py:322 forms/fields.py:837 msgid "Enter a valid date." msgstr "Vložte platné datum." -#: forms/fields.py:351 forms/fields.py:839 +#: forms/fields.py:350 forms/fields.py:838 msgid "Enter a valid time." msgstr "Vložte platný čas." -#: forms/fields.py:377 +#: forms/fields.py:376 msgid "Enter a valid date/time." msgstr "Vložte platné datum a čas." -#: forms/fields.py:435 +#: forms/fields.py:434 msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Soubor nebyl odeslán. Zkontrolujte parametr \"encoding type\" formuláře." -#: forms/fields.py:436 +#: forms/fields.py:435 msgid "No file was submitted." msgstr "Žádný soubor nebyl odeslán." -#: forms/fields.py:437 +#: forms/fields.py:436 msgid "The submitted file is empty." msgstr "Odeslaný soubor je prázdný." -#: forms/fields.py:438 +#: forms/fields.py:437 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr "" "Délka názvu souboru má být nejvýše %(max)d znaků, ale nyní je %(length)d." -#: forms/fields.py:473 +#: forms/fields.py:472 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" "Nahrajte platný obrázek. Odeslaný soubor buď nebyl obrázek nebo byl poškozen." -#: forms/fields.py:596 forms/fields.py:671 +#: forms/fields.py:595 forms/fields.py:670 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Vyberte platnou možnost, \"%(value)s\" není k dispozici." -#: forms/fields.py:672 forms/fields.py:734 forms/models.py:1002 +#: forms/fields.py:671 forms/fields.py:733 forms/models.py:1002 msgid "Enter a list of values." msgstr "Vložte seznam hodnot." diff --git a/django/conf/locale/cs/LC_MESSAGES/djangojs.mo b/django/conf/locale/cs/LC_MESSAGES/djangojs.mo index 1cf448fe723feb6170beb69ca0f7b096b966f10c..8efa6f6e4cfb247d91e4bb15ee7fefe1cb9b302d 100644 GIT binary patch delta 62 zcmZn{Z5Q3}iq+6U*T77{(89{tRNKJFzKTp>su_V<>!N|bSK-U1S KZnHF74hsMqe-JwW delta 62 zcmZn{Z5Q3}iq+6m*T7Q2(8S8vK-<8`zKTp>su_V<>!N|bSK-U1K L&S0}NTMi2V8JZ9) diff --git a/django/conf/locale/cs/LC_MESSAGES/djangojs.po b/django/conf/locale/cs/LC_MESSAGES/djangojs.po index 0b958e290cc3..e4f06a96ede8 100644 --- a/django/conf/locale/cs/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/cs/LC_MESSAGES/djangojs.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-05-09 14:30+0200\n" -"PO-Revision-Date: 2010-05-09 14:04+0100\n" +"POT-Creation-Date: 2010-08-06 18:35+0200\n" +"PO-Revision-Date: 2010-08-06 18:34+0100\n" "Last-Translator: Vlada Macek \n" "Language-Team: Czech\n" "MIME-Version: 1.0\n" From fc3c72a47bf186199b62b93eeef659afada385f4 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 6 Aug 2010 16:56:24 +0000 Subject: [PATCH 042/902] [1.2.X] Updated German translation. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13516 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/locale/de/LC_MESSAGES/django.mo | Bin 77515 -> 77651 bytes django/conf/locale/de/LC_MESSAGES/django.po | 154 ++++++++++---------- 2 files changed, 81 insertions(+), 73 deletions(-) diff --git a/django/conf/locale/de/LC_MESSAGES/django.mo b/django/conf/locale/de/LC_MESSAGES/django.mo index 5f88e60a6894e364019b02b7fd37566a0f3d32e6..fcc537c609beb37adfaa4ac03251711ef3c10978 100644 GIT binary patch delta 24708 zcmYk@1#}fx*N5?m5|RLckOYSy3GVI`ch}*?fyxYx+BTADuHZ_a;D(|K)%Fn!o2-691VF$2fn-Nri1QI!=N>$5~rRS;x82 z(s5?^IL_Y~LENvk<7CH)*aWv@aZK9AaZ+M!ER5YS2$x&@6NVGt$4LBu%;#~!+d57G z61g!J+nbXy3-K0=hd0do<}*x8{w-=j!R_1y7BZ`2L-K7<7q-*#mrx6PidmT7c|*aM zM09({;liDaW;WEsdC?DxVknlh`l=X0Tn9Blb8`@e5zoPdxC#Sso4E%y?g0#9e&>V= zco8+xHH?opQ4JqsBYbK3>K)vLHbnJnZgB_H#NAO_>_OeKSr`x3qIPT(CdWhQ(aLU6 z2*kVQ6IA{s4#9Ug0sD94*+BnJ?!8Qc>KB9gFb8TutORQ4#p%nsuTO4 zh{9|VdN@{~u3#r>fD@<=XE7;WMzw#6y24MW_7RY*0i zvNQXyd(?r1ChCW}$3rcifEq9k)nNhZN`6EwXcem89#p$SR(~3Ui7%qYyMbx&31-C5 zE^fa(9xD_LSj$@6);Po2C9sjuokL)OVq9Dgj(o8)J{!A^`C*v z<8i*H5Jh4ss>3d~!a0c=_&ln^pQwlCw)qA%VQ@G1*-3$#ASY(Sf*69Wu@81fE$kMm z-D3>U`~Q-HCU|ER{@vYXzIpn9oOU=GRsYC*j$y=aP@i}~ zz1=NOgWAE|m;y_pzN8wXF1&AV_FpR+PC_dlhq|JvsQgUJFGLNz6cgbNt3PJ-=THl} zY+kqg9n_9IM)iA-nmB|fG8Cgc6rw3)#MD>;HBejB$~&VL(%&40`nLQIE8re1hyi^a zrxF&&Iyf3@<0UMPS^IJI*a3^-a@3A^Zc@li;UlKQoZmW539N~_cayL*E<;_xUCf8C zF$-qx&x!RgFM6;bZoq2z0Clem4shf8SdMs!tH-%VAsLD61Kp>&yx9n~(k@sYS6~W! zf|?*`5TEl{3U$TZuqf_DjrSf?V$#8UIALzggaa`ZF2Y=T|94YJPT~owK|H?lWDbnQ zx~MB0gt>4zHp1VrH0B!WF1#mdoRO%B7h-1Ij^TI{%iwF&0t+*DYQ6up`H!xw2iC>$ z7=_nR1HQ(b7&gqEuoS9(80Nt_s0of?QoM#*@C#JCG{fD8w;*ak)i5LWMUU>~JPNv( zr%+e^7Io!mMz~j20b__8quLL{0NjL$aR=)CKY>;8B6h>*k?zwz1@%_VN3~moNpRIj z_Fo?;yGW$QL#Qjbjq30(s{Rw|^$Z&2z72`6K5-PPT{n!xZ%_*whw3*UBXAjNhj*fO z@^|cx0i)SkeKhtN?M~o9KjJZ{m5#T39BROMsMm2R2H;wYH=ycwSbV@dfg0xm>H_}4 z0KAPF=bCG3o9zY#UTcGRsnjC@2mf1t*x zKh7PeCF&vXh~dod456To(@^jG59W6BJZ2&P1obT!HQs&NGo#+`QdkNbq82h6gK#0n z!{yiz*IRwy1b0V5(W3^5Dahog0ivxztmU(#2F#DaSOP<^lEw8Iuke+_Wg691qEdWFh=w0z(sHy?_cI4SBLr$n{OhPtBss2wV4 zab-+ET-V}OsPVg?`t|lu(3K6w1UMPBvIVFsUxI458P#zQYJx+kc4saA1Je=TM78sq z>|SXIDo%mgk@T1VV^J64DPV=-n2bD6s?mkreqsaIk=K_V^B>qATPRH-=ns`5|-zl%$|BIHmin`)ksDU0^{-yaIb&q|gx&wrw78H(p$kL$}T-54I zqsFO-`cA2dT2Nh7yGH2!{%=k}0}V!X7>8QvG}M9?SiH>q8MPxjQ3D@EUC~KY`wN&8 z|3ZCThfZ^UQYwvV-yPNN8}z8-APTy&5vZR`CZZOy47HGTs0p`WVmyR;1}>rc-$u22 zWbtd%1${*I3!d)A$x!_=nR%vj|204f5?WC?)D~4oEvOD^K}{{+7t0e5!~(b%wb0k* zCse<{ICsJ@)U8R4T1X*OeR)*BnsMBJ-HV1KwBlx{ug}gHj@>W`4oCffF$0U?8r08t z_fQM=o8i7aL8ytM%uJ|>@}YL97;2mf7B}-yNJOF|hGBoy0OKs4jR}cYqE^1u^7}9` z@i|og+o*-S#H9EyYQlsw-JMO2+WPbujyX-wR}^#$8lY}LGt@xsQ7i9a^}Q`W(DEL0 zENbVbpcc9S^`*21wX^%pqgH>~;%mtG9_Ka%t@JT!g>O)|z;Bj2U?6H?5vT#vpxR}` z2+WPzxeAtVh-%-)@?B9oGz9fC;3Q0gD=~ukox>Eg^1o0ke~h}f{CKOA*M z+fW1T!wPsBb7SOOes{o%m=(8TNxX@rF?yc+{ofkX5%0hlyn>#p6ny9NTMO2}T6hk} zVul6ouT*PL3%QG0Na}^|t%yNAJK4>As4Fgt+M%ydJ5tT+8(Q2PwPT$Yvj2LDyOYp+ zJq#5eLfz|2s0pr_cThX<7&Xy5)YIH+nt#v!C#TSb zL^>Rey2r~=&%|%2hwChAz?&97Kuz=xweavC+~0f}qb^_>s{K~f0{5V<_&n-iyoB1x z`yL9~s^_RHdXG`)x5$lCqE?;()gdRUT_Mzfr7SLIaW&LKQ1DT5%oJtypaJYf=3+qbAsG@fp;WUb6Tx zYT++Y7xLa>pQT)YheQwsRU|43Ki!- zEwBXY87PmsfCi|YYK0yZdQi}WLs0j8ET+ad)PjCNO}xYE_hA;|lc)*apmyp%)IdT-W!dj@er^9mYzbb~2P{%3Oa1r_u|Ac|K5w(yVsCN4;e*v|CE9NcK zc#lvQ_6~JHK`Y#LNl`nH3Ii~91^cgobCA#u6tsq=F_5^DSrh$<8<<~X3gVWiw`LIP zR!l|R`{m{i)WdxWt73wcZokIZl(>(FLQV>2Q4O3`?q_;6a|mk7)}z`T#AtkJapY?E zR+Yy*0Z zs4L%!`k*?8Y49PY$N1~r1!cz=;wo4Od!ZJ-3WM=DHq!h5JB5ZMa`A|3ot7#}a9o|)?y4{uri5e5;z!a)3lx|IRH=o#St2UE}hVW@^F zPy=M}HsE(VtIuO`G1Ne1EME!LzK-Rapq_zNsPXz*{ZLFuJOq zdejHX7K@Lf20Dug@K4m!dmlB>Q`FYKLXDGbgL^@#urhHBYTTZf2>WAW{0_bE|4nP~ z3^l-88@;pl-#s zP3*rK9wyNNFQ5i2zL^IME1;g zq2}H1p`eCmP!nH6ZRLIQndRT22KL?R{?STeEJ$1zGvj#FLN=nVc$dYeP~-k#{*AhT zN2qx{|4`6Ye?;BWpl$B^n;%mV*TJ&b1GOU?u^1-a?k=P{mLQ&j`dm1PTF`yef)ej= z7mxyVYcrzq+1$Lx$xlI7ToQFpE1()SK&`Nu*#)(Q15j5m8nuPvQ0-@#3sDc(QdIj@ z=4PwkXC6U6z5k~vXv@xf6Z|!t`2e-$Pf#oVfLdt4PIux2s0F4#)n`W4=dt=CR$l?N zpn9kUHAcM^?a=%8{{txKibkNWXbh@h9BKhSpdPOEs2$p5^#@S>&Y&i~fV!1eQ4`)X zAEWv`M?DKZyWDmm=!qc_NkJV-peFhXwWXC%1J*=!Y+`X+)Wki^-sZPfKM2)+4C;cW zqAqBmEBB+W{DkGNpcZrsHQ+UsAuDQ zRJ*mP1#H6z{0(#AB@YEn6tu@(Kr+-$q(ZGcBWi#=sDVqMZczo)!m3$Z8+8E>YQ>FETh$J=6FpGR!ne54hldK+W5#{%)}Q~){U!Ag zwxwOner_L5z*Kk&i{m5ILNXuVhZ4+#;d=iEQ%FW)DyqXOREI;D9sLfvKkeo=o1hjp z%A9Dz}cnSmYf_Vis@lDj$ zKE$N>8nr_qhuvo>1?obwVg{^;`oQUeYUe?XKN-FM{$~b-cqHbbwrB}zL2J+-H>2+D z4y!+i9f(g^zTgpep=D6*Dp_0?QxZ49NbHB&kx8iWe>}qeYh~+5=!e67sFmHqAbfyo z_&>{kz*)q;N7*Hui+cD<9&?|Cnixpj0V`p548pCb3)yS+2h9`5*#CG`oF|b4ub^JX zr>KQ}vV4-`?f{uk?X#k;Brj?Kl~A7pjZx$DLM>zzY5@~a7c#@*?=hHog@-~2g$-7* z$0`n^1~`X$D=woZzKa_0snx$hUD+qpL+p3LZI=qwE+cA3bD$>7hoM*!wd0-G2A^~kjH7_ z7MymdEADB2i(1G~i^rOiEk6_Wwf#Nn%6_)|PSiLDP!k=u{1uCDqZaZKL-hU!opJ|A zirT8QsC$_Sb#F^pz6NTC>Y*lVirVrHsApjqs{Jg~YqfIr0!O3IY3lX< zkENi2r=k`x2lcRgZ~2v|Tk*5i??P?$Z>XOEFIqnIjQhSPK}}o`wG*WcF)_xXo~a_JovDrbWNe{^X($Y`ig|IZvW0te4O_|Rns%A{F@pFQYUmrN&3uk( z_b=*2bbfc2kN^`BhMVb8dzc5cCuLCms$(qH!%R5%cW#QVWeEuluo5-VR@A2ciUsfp z>U;7N7RF+KxW88R!Pdm5uqEcX#7~eo9gE>jEQ)C^yT4mC#tOvKFh5?p%uT68A;F*S ziW*=;;=!md(hFD-pP~jVc*R}FH>iPr!YcS0>tLCy?!Ys!Gx1Suj`{y`{{$fpwF5U% zpNYYqYi=PsRwGdZ8{vG^WAhS=VfyRt??BD5E%74M!~r+l0c&Aa;{MnGf5ejb4zpvC zzun)h+GA$o)mR8U=dB{-rn^-&u>=+4QCEHhdtulu_ew^f?%m&*8{^$}f0-+cg^Al@ z23&|uaUbTwG%UlXiCJOi~e*Rd$`J82%c_p~uaQ85ei;CfWU8yJ9TAG$AC zCJZ7jgjKOLj>L(mr#s_8-Df5trXbFZ8n`OzEvk=tc-x}x@lYI$TTt!tJ#k;(66odOEk7T10n1SR)}qGUfVzMk79V`V{_Dz5l1PK+P!m75 z2Jcbzeox&2gUm#zEsaDC7=!AU2Q^VK)Pl-dTn$5r>tRA{iAk`>Q|`Y87(qf#LJc$r zHP8}N$JJK91vT(KRJ&uSE4*O-iRyO)HQs%TUzqPv{rvuM>l1h=Bp{ItwPi7=0dk-w zDu9}xB&uUM)W9{YzOm(7V^{KBQSC3IzP_%b#(iPFLyhb6%pK1YOhFBjq6SWhT0jiy z%5!2pEQ^A!!7WWxBK3v z!&n-W#t7_)dNzhy{X*0y;%?N!|3yvYyl^LukG%;aPzxPrPC;GROmm?(&;4IUK@Zs) z)PO&uwt6dSNA{t1;)KPQQ0;D^`aQ%p_z!BQ>b`XIE6sJN@iw5w*@fzVM0w_SPE*j9 zT*f5$5EJ7^%O`r}c1VZ1(pc1$eFcBU`wfh4#&<(49Xz?r5mHlUN;A?lBFjT)}s0)ooJ@k2BbN|($ zG>N2G(Hb^IEubB00o_nn(BI;rn1y&8YT%8iE8T_acO2FJJgWa?)I7JX{t>GE3;lan zb?|-T4it=v!!Z<7qOLSEYJqtzu8112HmZFi)IwUKcB+%b15gVXVeusFL_7=C-|@V4 z2Z)DSaU#^ck3elvENZ|4s1D^(16D&#*wFGVQO`gJvn%Rh>t*#r%u(h9RDaKO3c7;% z=8x834eH7_SiB3hBl|HD&!853&+4C`27HNr_z|@rpMTwUfvA3=sBtnN^Lm`z6m&&} zQ4^N6xVqU0wG(Yn1NTDhL|@c|gRugR#`1Uq3u2OY?w>YQMzyPk>Q^5%UsFuQ{7wf7 z+JRA4F%k8Y&c<+DiQb0`HNbI;&sqF8>Wc287W&F!zxVDf2s2Zm`e#NhC>MJFr;NfB zw4xH23@fPuTVYLXkA-jxYN1cf*Qg!)gj!(G2lwZJDAY5P7ghfis$VtKEvsiXLGRE1 zEh!|Yq8&z|2cvN|Y606Z9mfC2{lu)O39_Revf^eX)I{~ryTzy-?0~xBL8yMCP&+*1 zBm1uo%SfohI@FbHv-mLTwL53tLk;i&wL_tw+=nVL>K0^1O;iB2u#%_+R56>QZoxOG zb^|_f|25DE5?aVuYcK`1RkJL=z+8%2;97GhYJta417Al?eAnv#XL+Ch-0_1@7aE3Y zm(oK)9b-`gNo~#;0#p9Tlg&|_w(`oo-hsd^?L#vVs3vQ zr$2s&SAn$vnzU(SU^Fq$qCfTZ=fc)XZh!-&x3$qAMYW-7pj6^LwO{m9x>Wh-l${SgOj;N=)Cu-|F zsHb`&YKMM6UD0xje?cvD8|t3#MNM!DOW;GSf|(Pz&sc9PPrTJbLAT%|>I!lsbf1NS zsELYOTo!eul~L{LqMn7OsEN9x7CIia(AlU3tw!z02GldQ-||OM<9NUanB zI=!`g(?o8=4yXn6M14sOL`@Kf>Nnrw^{9n!K~1H= z?jwGW8fbTzJJ12tC*U#Et-4|HGm8Tgy9YTWb| zXG8DL{{<{j0yR-Z)azCQHE>-_hpkW_!6Q(g<=ZTNX2vA-@&0{SQ*1~55111JB7D66 zlU9DrOS}ZX#)}cW|9blKM7nQ5D@;K=4RuS_qCR*oV{Uwp88Lg5kN1Dltc`lTCSYSc zit{i-GIzmyP~%+3MHrggjn`o+;y;t~{%a*~NQ}U`DcmoQ-%%6SOzGMSV~FD{-hr{i zS1}#>r}FXslZH%KoVYfselqH{+=6{@KWd?cQ~Nk&aJq+r8eTv>WFJsh7@NjjX*JZu z1F#-0Lf!LMsAt9}t$VNiQTIC3j6&VY^r%~z1@$`SL_G`nQO|;>Bn3Tm6|F%{Yf#_f z7N~*Rp(gH%YTw`T!%#mvjztZ;*j!_7M)m*IJcL^KNn`;Y=L`kCua{8+JV9N_8`Q(+ zo6cQ%7;2zM)Q+V_)t5)LuZ#NnZjKsf3F?DsE!M;3OXK@$x^ zeNc=;4X^}t1?y0s1A9?db{5tC3TnUys0IIv`Tz>dU<*Rs^JrANSX8@0*c;2CM+0u7 zpwIl@unNXx^l?^VFVwwCk;xq}HR>6NLG{av`lKt4T2NKg!W*O7wYB=5s2%g5+Kol^ zpP7mGUll)CgB4cs6Kde?SRId`UaQC$_j95yY70lAKJg}6JQX$YT=PfN1*}8u|eA0+;eele%TqDjqniEJ;xKBYi9Qm;3udBy+JMX6YA#r z<#dgYT1X<)-X}&qo#|2I6hd7{S=7AMa`J+!Lvs@Ou_bEYnW%y1nTt>pFSq)2sDU=1 zu5c&n0*+aH3bj*znGaFz-~KxZ#ykdNNR>eR{;XlUjY;^Y!E0w;qH}d8W;1ad&J%{pVvc@lMXN zBvvw$j<2u-ZH{t2A>P4x%Ib1|(RY+DpF>~MxhDK#agt*|O>4q_B>WzJ#r{e1MW@=Dt1(EPv9=3_Yu zrHL-lAdZvoHRl!;3&@#CjS$;gq)8lXJw4+ohc#q8d zm!C_W3$$s9$2s|(#K}hcuH>#WzY~wbP?Fa;|D@cJvmXNwq0vJI=uNzfSVsv{hnalFancsOz1wYC;PjH%5XkZ<8$m^4~I!>i- zCT%_+^}c9(-0~eU2d93_b<8f)K9_?%u z#$P%0qw|lPWyqc3{EG5N+UgkK z{ej*sJ5Nb=romR?ew;0=Q5KA1!kY}%nS2eKd@b=2&N0@m82NYf?@8`1=V%-EGk=k* zMsSBV4LOUdpRRue6>mScGXKMx4A`FY7L9#rGY1C}&!EnS@_*#o5$n)zsX8{0*Rc%8 zP@jNFPIF$N{0$Dr&&O*~VB(eJgiD=Qr~4DQ_a@{ri#{(AOLAsm}m9 zl5wsk*N}!|8RQ1#5#)wb-;vxh$~CP|UdlS=ao&#GT`S7dk{;7-q=Gi>6mJ`T`Xm1* zsA_j`V?&S4D-*?C#g*gSkXs7ne$$g;Ree#1jPm}9PE(6vffA5R-t;pA6 zzJ=7~Ye~g65}%L9#5IUxIFDJUhnSG~E(4{thPlaYrW}e(t$!69V13jqnsSG@U+RQ= zex&9B=WDXvIrCB8#krprSsC?5Opjk4vj}Q(7N@QZZFf>GfP0BI(6$ZU<$OUt0)wg3 zk(W!$LwO}_UJ@_RpM>Nl>7kL1AvRGZ>_zUdn|2=B0L{oH;Ve)5oO6}64`7VjH7SLxXwjvkHxr+EN&d(p*P62Pg&7v}p!E|(@tiPxn#F^L@z@O+j1Ic%x z{s+oQtnKggpGDk(xGLq7*8Vc>iqn3B2DGD%S&TOIK3}r6o1m_^yl;$9JjAtl&2kC z!U)e>bCHhWzalpj|Ht_YxkgNrO3Ng#<7;d}U0M21BMzYaGpCNKv};N^C*=#){x90q zu=@Oz|E8{so`NX^ooLjK%5s!-EaDtR&WAW9)?vU#)KB94WP|wP7Hh9QEod`?wmPhOMjwI?XfKsx$To?(^E z$c^RviO#jiou%^we8~BN^DyykjHNye=OD@j7-Jdg_?DcG?`fAGI}^8Nybr{+X|vXk zKg7;PMMV;ykKbr?i=ZbBt6&@AY1EhSj~m|5`}dch=o+q!4ZoGLewWTj4;^h*KpE1T{%H{Oe8}Ue7 zv`&erm}zB-&Me9+Xjst(7#_E~QIw~uwHSd@H6-UrS{K3boc>IGpZFQ&p_D7qZY$?% z>$jQwcjU9rQk-2W zFXH@sG$J>ZV4Nj}(f*JvMydJKPvO+@FXvp&!xs0U-S?a!Ox_w#`||Br;)~8bnW!f5 zW9yJR?s?R<-KX6WAyqBD>$$i9MIoJ5{>%ht{CcZ)CC(aC9%t1PRdE_A; zMdAWy6Y87ew+!<6sBY~Sk{iJoew_dRve4*k+@!BFc-~r%9*j_%tS`Q{Tr}kq=4z6hs|Bh;rQuOk?vu`g|6$ak<+{DyhSRj2-M%MH>`;~Yc3w#0qNXT+h5aT)j80+N$Y&AEvUX$&@Avyv7Dme2;Q^ z`n8}uJMKyIl%7~xpC|iC)pk@Sry~n3b*#71R9^Q**`I!OX?vD@8rs&hnOb88;-6_- z8V_1t{YR7g#^RDJ#@R$Nl0n+qzya8W2D5GQoN=pKr1Weg^RH!2U`Bey;y8Nl{bKlm zlxL9tioA{`lq4`%~q_g%9$aS`r+@oBT^NKIOk8GrHz$)rd zK0!zQu=M%JLZLPTwbJMu^Qk*Vo5_^Jt=$dENjL+Et5~~nSeaa6TR>Lg=#9U$tQ_Ri zY2*8j8N%Z3jf~tFG2oZTz*YlVb#2uxF5lbejkl&O%N1NWNA7~@@)RzbzwpNCzy6kL fcDTP!wzv;>gX8|W6|%9#)k`U@$Hshb=M?-uX9OFb delta 24659 zcmYk^1#}hH+Q#t-gdic1kU$6$+>_w$?(XhTytsyg1@|BYifeKA7PsOQ*Oo%D!Yu`g zw7~a&&b)k!S!?&V_q%=1%sENgdw;p>d-{&A=Wa6J*$&q@U&l#<4Kh1UfS=<`uA->p z9Bkt_vwR%q5GJSGsGZ|PV0UbW^RNWI#?+X%z2g+YnwS8`T6r1<3m8QFI%+{rQ40+1=o*QQDVIlWY=OmhqZW1!b1=Vi zjZ7Q@KAjwgjXS=kKWgG+7#CAvVhp$XT`F0E~~5Q71MF!*CUP zw6cR_{P4JW78SpY(RdRlVUuroHt+%JUjB{h=hww?@?!vML1obwtD)*^qsD85@v#La z!wy|I|DSfYX1hk3+d`EAOI5*PhsWsn22%? z)GaH3YG0-+=dXKInSds0gu2JAtlSN?qrRvP!%#aJjatwIRKG>2cB`y@6DFd(12x`3 z49ByW8UIA}3-WYxGhwKeWk7X^L>+Z8)B>wmxv|xEMBU3isD+F{EpVQ>1a(rY&0VMy zIf04rBC5UTHW}^cchriWSox*-7PYgFsD=1-cLz#trb7*s12thDRQochTT>Oa&}OLd zyQ3D;51H5F3?~yxU<|6m_ilx=0X6V8RENE&hvukx4K?9Y)U)#eH9?Xdj*|;hpg)$w zepm~&up_8;XE2`L|I1`F!A+}pfa>_Cw}IolLrwGv^J0>o?unJbtduLE2JV4c@BmDS z(Wr^$piW?sl{cHa(O2*PK{EOPIfk0(KI$a?#sG}l%QXnKuym;UT&NQ%VfD393u%d3 zNGH?+dZ6xkf7GoWfk|;JdNkl1GHSRC)p0E*!7ZqX4x*0uBI=|bq6YdO>NS0Xn)m~% zU)*oqagw2KZ5U?5yr_AaqTYtK-*WyN$&4T{4|Df+S9%IH!DXz2H?b(@>f<;au_LPf zw0RK&DPKc<;yuP7{DeBez`lHZV>s$dswisX4f}HbT2Wg9+Ibh$j=n|3`&&F3HSicr ziu0|0t<`TuEohH!kJhDPnjtOvnk5$%{7=(@Bec$$*Axf;u?lp zVFXshZ!i^ZL=AijBheY^?lcOEQ5=a3=HW{cKk8$348_G5hPyBdFJc*VM!5B5F)ziYs0k)v2rfn~cspvGdzc*Gq81b|(*5!& zg!w5qN008|G%`AYJ*b`EL+vd7DEAXC6{w z)B@G6Eo$7ZsLzq;(VV}IVjKbOU?uAH+iMLDpkB*UsJGw(HpJ_wc2Q&8p9Auv7E}#2 za0}GVJE5MPVW^Xwi#_o;>JzbG3`eU8%EY)odR0cPv^px@05xE9)B-wUJnUiRzNq@4 zR*o^JpvIYv8fOv4!f>Npj(^7*KNSEAZ& zMD^Qk{)AePIlkxP*gkzs(mrk z!pfpHP|f1?Pzz~_I?-0=KK}dOh5zew2Ggs;R%bMMNNDKb&qeO z2KW=Tqc^A%`fTMyQ`~1F1XZ6AHGTxDUjftyR!I+;05Y{v3u%ekd3#jD0jQ2@rwPWP z+Re1`cbJ~?a#XvcsGXj%@-5Vf{Eh+m7`2gqOwUI$>fk%o9UvGracV1P#>A8(Q1`4T zYULGC3u=rSupMfmE>`Y|>enB&uu-V?6H(*O#=d(07m?8b$)>pt!%#cSfLd`j)IbHT zTn^Q~j@cMBK`XNpY9T#Q&rDy`#A8tXrkQiRa{m{S(TY}}2HIc^c9{FkBdCS^jC#1P zp%(hm>ffUJf5N!vH{D%GB2>Gis4u(ZsB!XRe7*l=$Y_OCPz$MVz_qI2eL)I?8FC-gUJoR3xxndN?dq{BesxjkewKp6|vLJiy+wX>cU zAA~`aCty-sfLh2p48hH)36G(U_5$kYZ(uM!G~c6cLBMRcohO8h21<)sc}7%&oEFb( z@j_-P)X`N!Ewn!B%cvddDIa2vu==r9o{buR0cxSkkp=Sm9~s?(y{G|yMD5@VYQQU~ zo!r9Y_y~1$A1$6_j@v#JDxL{-LIqGi`&GbjY>nza95v4@3}t?2IT_vCeHen*P!G|c z7WbR$-inNgB<@1DT@q9|IqJmHqaNZc zsMosiLe5`-XabsG8ft>s=J%)*SdN-#3+fi_K}~oLQ{r`0znA7a)Wo07M2p;8kQ{Yu zGh%uyv5526y>CfC55-{ALo^;W;5;iYK~1y;wep`)M;W---BAlv`<|!;_DAh_BI;qB zhC0c`sD-aVZD^~9OemSX7Py33`At-ZhZcW<8t@-0|7+#AOWXkypxP%xZ6GzKzzEcL zKzUSsWz;xzPz&`mB%>qgih8=opms7Hb@X#lPwz_9C*xLB{V~*lXHexUs2$x#?eGEW zgXaa-!Z=IaldFrRD9^>pdjGGI$w?r5nLA)r)U!|zHEhZea{+ zp-WK<-H2*;2z4T-P~+aT_^+5i@Bd?OhKC3B417SXIN@^lRy0BFv^{FT?x+d+S$Q04 zr_-#w9JTOus0p`Pc{geU2P}RHy?_5dZxz=Spuv6IgwId|Em+|Wv<&qLwg&Z3{bc2H zR(^n5;A_;5|3f`X0V~~;N@-?AEu;W?|Nd8sOj-ifPz(45HF0m$GcgEr;26{d8&M~< z6E)E>)Pk;}Ugtlo{vGNejK9i#d%{uu3Zwc}TE+P*(3n76?1X;U6}6DwsD?u{|7ECWVGC-(2dw@y>I8nlcz72z?gP{bJYU8A*MM&c_~B>MZ?*fH1enP%74@O0 z*Crq8R#Zma`<7;J)YBb<)$u5*U*H>Y;Yf6ZJtg0mE@AX2gT21^td$(Pty~7qg=l-UbulC~SgLurdCDmGu6X;!)2` zAO`gwuSHF89Ha0dX2+1t?rT{R6H=axdS>Qee4KCbWtf2Sdi28`=!^SN&%hy6{}bqa z|1Xl!05`3{eQWU8%73Eu7Ujv*apbnQYG2XNCpBO~>Ehfc;+ua3)p>~o970-+6R~FU27HWbvsFUl0 zx)r@p?S^A#oV1V#74w39#$oEbH6Bx>ONs0Ed?avju8 zTU)ssYT_ZNcH>a<&i0VeQ7$%DTg4{Sz$xv*Tja zf=;3qa1nKDZ(01l#h;-z{06yo9_J$&H4ND6t}w*Rh&sYNs2!9*9bp+%` zGMA(JtwG(2-KchlF)N-$wSSG?fB*lUjE?j(s-fS0w_^~hoEkN8Rx_s=W%c<`6O}}r zRAtnL8d$sys()9rCu#$I_H+MrBtr;j=cBD+CTc z3cU}Zc?-3mhp2Wh%{Tiwe+~GafR5}x)I^DYbnjIV>I71u2FQY;m>1QrDry5YP|rp~ zRJ-=51@yw?I2d!|G}Jr?PzyQlA)}-C1-0^9r~w|M27Zlt7CxdD7WXH&oB%aI64XMH zq53DsK+K40pC2_&5!3?9qT1KSJm{%MMiY!SCz#Vv9cQB^T7(*K8ES&nsFT@^A$SC} z@aw344^ZPiH(#OJzc+mkco)on|3OAal?HVZSy9hI6fX4Pp~8)L>!5q|6A!z;q%Ok_ zw7Y}lu-p;%11tthP+o>w$UUrvk5R8-{-f>>qm?lY&!5wVj5CLUI@0=>4cnsb^#lyV#n=+}U z)KPChozP*_!*mhT;BS}-KcRM-@r2v15NiC2W_65Dxh{HiL`}(PMeWcRyQA(^Z&dv- z?2IuMe~x~X-&y&yl@pzGe}oId6x2tePNV{A{AQ?ybwHiSpp%@xRyLnN0$hS>_ya1w z4d>tO_j87Elhgk?K}%h>0k-LVxUH@%|pG7>*iX z0_r`Uj+%HOYQU9NzY(>t9hegLqT2m}YIh5Dat}}wK1Gf926g0d&bSY2W>h~<4KnK3 z0R6EgCdMwPr+WbEVH;!RSWHZLDe4F}Vp=?e+Q2>Y57dc1N1d?GS$9D}W*Vfu$I0eq zoG8>n3R=08S<&J(P~Y1PQ9J8w@jj@DhoUALW$~F-UVvK2I@AUap!%Oi?|=Vum5lD? zZPdMe?X95loO?w6s1CuXBM(PC3x!ebYocDuR;Ug1#Yh~60k{@paJSW$I`58K8RO~w zuT4e|OGB$@jaq4EHNd{8qaKX<8E~@2kD%W7Q>clbqfX)<%#0sV<7T|zE;s^JUk=s2 zDth!V)gz-1kj5B@{mdBD4(6CEPz&2?^*^B&cHZjmqbB?dHPHvuL*{eQoiHJ)e=;-t zBKKbna}rR)yw-^KBNs0mj8;vW50)Mxl^tcEEr^D{lRMctZ1<_Qd>d=`W7F6x>3 z3w44CuDG9!DKMOJK2*G}hm3aK8nvU&s2%mRa$j=@CMP};HPKvDzcr|Sn=vu&LM`kl zCc&T08>ka~j5?ursD2*btL_JaKV~72A2mQz)Bvqf6ZJ$L@c=A{9@Mwy4lIIyV^hp^ zjqeeR!8Z69Yh%^x?(Y%vuo&elSXl3W;0^aL7*((U71OW^9z`uE;HLWrgZvmqc@kE} zl~^2~qZX3mmOD--)UDcp_3#~b#p<_tMR6zA!sK_faqfQyGWuwoY;MFFlrLZtOncXT zcm`l`$}6xe{)!zi<2`r6ahQ$rCCrH*un}hY)%}~$FpQwQ56k0Y%C(RtL0ucI3NYCc7sz-z1jVDUK5 z-S&x5{X_(`_`YyWiW(>tYM{)hjuBR01T}DZRJ$6e z9X2vsp!&5#joa1A{mp1hsP{j{8cf3g8qP=Ua1CmJO{j@>p(Z$pn(#Pk;0soN%i<5P zJMkB&_Rat2K3i>3NWRY15~q$x~LOrj>)h)2BF8|Gf?f9p?3NM zY6Dv>z8AHSL#X%uxcMvU)_g#nM8Y@jLXvvOC=iC;lRyoe&Ek11UI=vpr7$U0MYU^+ z+EF{J?{4LRsD+NS@_5wvGg1BKqc-YUPDW4tc2tMM7=ouz4ez2lK0+yBYF`kw(^9B@HBs#wqWU*S&C|)P_c%Sss6&4@;EX{HG}+3tF)`(ZsGY7wEpWS) zPoW0<1vTMKRR8;^lX_<5e^CqYee2dIMg8wjoRnlVz$nxJ6HqIjfx7o|Q77~RYQSBn z_9svSocj3!qM-IHtfV zsD-w+`i`h^x?^1Ik6OrJRJ-A*eq&JMEI{vn|F?#WcCs0D#QUs#%)Ed)fm^77pP&}@ z95vx#X~^6opq>9`6}}(br!y%A6HkwN zMhc(?C~xIzR&I*gQ5)1kdsulW>gdOtvrzq)pcb_H1Lv*Cc2Jccn3rAE$Ro0r2n}K z$cyPIk3=6_hMHgnYKPm+gQ$tlp!bMTCwLFF<5#GD|DpH&PyEsCkQUV;3u-5MtXvxP zx>Yk!0IOoLirW-~u(f#pyGH%3j|%IdpXd@yR95vYxhM}NKlvDRQQYM@o99qdEhtD~p^ zf5zl^6?JrfqIUcd)jrW@_auT*^*J#di(@)$h8lke>cpmDxQEOtGTHDLrp4!&1O2(v zxv?PXA?t{Gx_hBcEE=_wQK%D{f?CM;s4uUTs2%S{^*e}qh|gg`^z-p?Jj#?HQvmy5 zbzF@a_!X+-C#;2u*M{}t$>Xwufaj+`1*K%6B>-#$O_c>mvI56 zkLTm@{+CSqHCF)jmvT}FS&ib3tW(;cKQ&7*scc=|4 zw)h6rE!d4($l3TFAMeu{mcX4L5_PosQ4owZlMvAMev1hAL-44IG6Uu#lCDTe&=Hz$&Qr^-xcL zbJSDb9rYbB8a2*Xb1J6R`#*<_p8Bn*m0z$1zoMS%$Ec%wi+ZYk6T2rAirP`Qm2;pb z$b-7)MUV-cN>~!xVKrQWK^f8ed4L+XxW&t&_vinrWVDlds2#RM{S^DH#V?~KyoXxAW7L<_ z->3=vle+zaQRS?tg-4+#EQlJvxLL{Kbg4*>JzXW z>Q*(e@;6oEH{<%nO#ju;x^!2=E?kHu@RZg2rEy=& zDAXrsacqj4v8>+zL}}fDYM>sneyANTMm>~AQ4_z!2ADG3z2`kppKOCsw{RHhUXL-S zp>E}T)U8~KdL37xo`v=3egAio(Etam!3k?{*2>pV1KmMAY!6WF|FZZy)X$EeQ3Hpi zbIoK%qWTv$OQ9BC5xwt!6*78X>!Jqeh}uan)WbIfwes<(fu^F4ezw*Bh-!Zt_0f72 zHBPGZ?gvw5tWCKv>NOpQ+Q8iOy#IP#78B6DS&rWKKZASEGN2X^g@v$$mHVOw7>QcY z1XRE2<~-DbmSINRfVu@2Pz$(%YX2~U$E|olKoh+}eNgyhbO%U<`bf@#`Wz^N+F4ap z`}(K>+oC@Ed!s&phNE{usFRzAYPT5GZWH#!{T?zJFi$4;Grt&Cqr3>$;1krnnwi-h za5m~0ScK}g7WDzP9krk%sGVO#wYzQgk5MP~7S--Es=p^7i(3(DrbAU^Lk*l4YhoGH zYc&;f<7w0m|3%$G->hyq0jhtHnHsf$EU1&rj#^-Gpp7yRJnNn zRgo^2J78AS0-{i_OG(tkHBl2cH(Q(SQLklZ)XoN>`j14tX46p%T8Nr(4QiYX7T=PK z_h0vPKLK?-iQ3sU^B!uTN2mc_qYu8f^1rAJI1%o^@lgx*N8P$GGn2(5Q41(;mM%j^ z1D8jA?N&hz*dDc`uBZj{MJ;dy>a%_tY9T+wzN{4NQOyw|+R7!=NOv(mzDM1R$VhkU z(x@$#M=iBFs$Wyok~^YS&_;v1xMtznNntDou^OiE{ud~ptZsp~1z%y5kE!xUGXKMSeAtag zKYi$Nl_S4_)J0q3+C=;V`V*^1ich(kwN1+y2dO(uc_i)kS$qcVx)U!#dQE;mmbUT8 zI%S8_a2{z7g|-aB4~I@4^7>LQNmf@2JTKOVv^m6Me8@se>YEK4gbWYDmd#TmO zJzqc0AJ&7SQ=VxvTDd#6qW;S@nerY|c>=4LNmm){M4OYO=al!7&RAXUulkOP!$)yb zI)6(8esXhKlKR>nPZ6s{r9AMp-J*LS2C%Xy zgSArAQj*nsqzx{1Vd(zzPxBW1k;0oWF+ z(dHN?r(BsdoW5VK9+qEC8(o@z6Ky_L@D_PbXfT7s_nh+^72gw2tX6id!^PD1!&1~& zAvGhPmDD~qx_WBQKScBqu-&F2>uhGUgp@mzAI@maX!DSy>nwF~se6T4u`#I-N!MCa zdHtHM>j&DErtbmrqe(9*H&g{zCEdsoOt7Dh#i>X~!;Vz+rd*$V98yK%dng|wKArq# zEJS<*u>jIj^4S?9f;gYB&QHh>EZ!?S|KtZ<=Mrt2DeEe3D!+)hu25p#$m?UFg2iSsR#A;<Rqv~|r!3wHbCLA9ANo~&Qp=wsKAf?1 zohIh#OyfCLIo>>J9--k~(jXc)ve;tE8!6}12weJo?j5o6)KA8Q$j=x~2a>KOc;52& zaRT*;upOt%rTz0iBBF4aU`f(a%HN`{{>1yRz!9XGl=U-S6=K0|XQvJ2ziIme_0Jjj zJ^o2K55B;kNcz!vIjJ=`MQ0oFpRKM1^7jL$8qOeHC9kUw{=%4k?s)F~ z?@xmUHqitcRH0#g>u^MI>Qj)urPFodXUG>J9!&aI(No$BTq~TZwxl4W&G5t?qIuTn*zNYobM_$)_ z(*4*wbwWL@=`r0#Dqz!2^R{6$;y1}>BR<5ImxEY+VySJ!)YMHR|A};iHu@DuS1;Oi zq`cI|?oI41<^5LvA9nZUnMz`TpS|7q^0V@ID~lx-`w~iJ9kO_K{vXknw zuolEe*`_*>|CN-B_Oq~&cQEd0VFF<^noFQ6`7c){@<|vdGj^wge&!o1i50-Fua(3; z(C!c7LrCYT>_#jT)*=4ztM+Y(*J8ef)a7kM#V!J0uBVi1QqDp;X`LQp0Obb^l+GGP z5!*rDAD3JIsyNX4s98Gl9b+%n3-&Cd<}v9XqCH9Z$nPT^qD6K_U5e@P>uU~$+N5IC zl~H^01@I@zn`zq?ACO)V55a`g>B_^_@{nIeo7a@*$K@tR5saphuAw$jCG17)gd29A z*Z|Fm1(7OH{)@EQ+Q(&#_vDk2#?j|4>50{8i23vxj;)DhA+4r-i}dA#+sW@O@CH)p z%V4@XlaD4pm=tIW;4gg6AmZJqUqn98+FqvrY|0%eS0#VO+TWmEG1_m{fOfSvi_xZ@ z$0k-Rl+HJ4oQ{film}bgK=Og)BS{z3kgFv5I;2yy&qg^DeZ$EQA-@yt*= zK)EgcfTl;S(<_^F5Oq7P{yKHD=yRHKJ!@Bhb_1=gLfxtFM*SVD7gzLooS2|~rMrSZ zSfvl~R+PV7g>6A8S*#EF6jWp-pI3>tKN5>3R?IqVQyco{q^7}7>M*CKYF&X4dh=_TnH<@=bG`n06M zR{*E7<^^*gH)^J*600*9dB-QahS-mU3Yn zPl`kRW6Cec4KWA4@zZ{*Tm;ybq}e>C5$*8LyEp)8`w4U1;a;)_a^a zR3x?~sl!DYm8C%uVx>r3$uA;(xf&CjPGN!thSUD2Ek>dF)K4YpdQX~1I&S4YwELcv zkjdNN**NKVcE0M|lZk3ler6q_VuP9mx9m*w4@5dJ)HrHKlJ;7!(bR3R<<-TZwCPW3 zLA)q!j*#z0Ud_{xzFZR-PoMIsVn;R$4thiNTpMLKu8dvTES)C-xfpf)>RLwmHt_=% z--pdf;~4LETlFEzC+U+I*W)DO^Q4&)dIQKr#>xlT?sG)_M=T^;vH=j2Qe?P>eSz} z*f9O;k*gF6S?9Lcf<`Ur7(psTzH@Bv7QtNxk?U=(M$uz8<-$z!)`n_ob$?UdLMlP; zFV`m8)})Y#HkB=&#M)P;?k??<6K_SVYwYtDq4EADIxRL~%haALNWuCtZfXnA zPoTO^&@h5n1qKQvKgH^=QrDmSeo`#?e{ceQmSR(UORObn4E@?s?n^u)4r7dK_>(Ol zCGj+*t(23*;p1$#RpcjFJ2r2tU{3-fr78K5UeWc-m5ZTAQmBVRsY{Q#Qqz6~sRU@(SC#b6~qr$1f~-xh?Cql-hW2Y zp)rH_5!dw}<+V15%72hgPrugW=fnoIPVLD;>q|sGsoJi}#B^n&rLOfhn)2(u%KOr< zE^RLoPfOccHd9;7KzSo=OW{$AtN&YuR+5)muPQUG9o2m(XI&MqdB~#$GmV?)%*p_tq;oOM|M&`|*K5xNd`SWbMb>MKC U*d7o3x81pYJ@vK&?`9SHKQcKPa{vGU diff --git a/django/conf/locale/de/LC_MESSAGES/django.po b/django/conf/locale/de/LC_MESSAGES/django.po index 521bd3c89c49..fbd9ce1d1871 100644 --- a/django/conf/locale/de/LC_MESSAGES/django.po +++ b/django/conf/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-05-13 15:30+0200\n" +"POT-Creation-Date: 2010-08-06 18:48+0200\n" "PO-Revision-Date: 2010-04-26 13:53+0100\n" "Last-Translator: Jannis Leidel \n" "Language-Team: \n" @@ -72,7 +72,7 @@ msgid "Spanish" msgstr "Spanisch" #: conf/global_settings.py:57 -msgid "Argentinean Spanish" +msgid "Argentinian Spanish" msgstr "Argentinisches Spanisch" #: conf/global_settings.py:58 @@ -168,98 +168,102 @@ msgid "Macedonian" msgstr "Mazedonisch" #: conf/global_settings.py:81 +msgid "Malayalam" +msgstr "Malayalam" + +#: conf/global_settings.py:82 msgid "Mongolian" msgstr "Mongolisch" -#: conf/global_settings.py:82 +#: conf/global_settings.py:83 msgid "Dutch" msgstr "Holländisch" -#: conf/global_settings.py:83 +#: conf/global_settings.py:84 msgid "Norwegian" msgstr "Norwegisch" -#: conf/global_settings.py:84 +#: conf/global_settings.py:85 msgid "Norwegian Bokmal" msgstr "Norwegisch (Bokmål)" -#: conf/global_settings.py:85 +#: conf/global_settings.py:86 msgid "Norwegian Nynorsk" msgstr "Norwegisch (Nynorsk)" -#: conf/global_settings.py:86 +#: conf/global_settings.py:87 msgid "Polish" msgstr "Polnisch" -#: conf/global_settings.py:87 +#: conf/global_settings.py:88 msgid "Portuguese" msgstr "Portugiesisch" -#: conf/global_settings.py:88 +#: conf/global_settings.py:89 msgid "Brazilian Portuguese" msgstr "Brasilianisches Portugiesisch" -#: conf/global_settings.py:89 +#: conf/global_settings.py:90 msgid "Romanian" msgstr "Rumänisch" -#: conf/global_settings.py:90 +#: conf/global_settings.py:91 msgid "Russian" msgstr "Russisch" -#: conf/global_settings.py:91 +#: conf/global_settings.py:92 msgid "Slovak" msgstr "Slowakisch" -#: conf/global_settings.py:92 +#: conf/global_settings.py:93 msgid "Slovenian" msgstr "Slowenisch" -#: conf/global_settings.py:93 +#: conf/global_settings.py:94 msgid "Albanian" msgstr "Albanisch" -#: conf/global_settings.py:94 +#: conf/global_settings.py:95 msgid "Serbian" msgstr "Serbisch" -#: conf/global_settings.py:95 +#: conf/global_settings.py:96 msgid "Serbian Latin" msgstr "Serbisch (Latein)" -#: conf/global_settings.py:96 +#: conf/global_settings.py:97 msgid "Swedish" msgstr "Schwedisch" -#: conf/global_settings.py:97 +#: conf/global_settings.py:98 msgid "Tamil" msgstr "Tamilisch" -#: conf/global_settings.py:98 +#: conf/global_settings.py:99 msgid "Telugu" msgstr "Telugisch" -#: conf/global_settings.py:99 +#: conf/global_settings.py:100 msgid "Thai" msgstr "Thailändisch" -#: conf/global_settings.py:100 +#: conf/global_settings.py:101 msgid "Turkish" msgstr "Türkisch" -#: conf/global_settings.py:101 +#: conf/global_settings.py:102 msgid "Ukrainian" msgstr "Ukrainisch" -#: conf/global_settings.py:102 +#: conf/global_settings.py:103 msgid "Vietnamese" msgstr "Vietnamesisch" -#: conf/global_settings.py:103 +#: conf/global_settings.py:104 msgid "Simplified Chinese" msgstr "Vereinfachtes Chinesisch" -#: conf/global_settings.py:104 +#: conf/global_settings.py:105 msgid "Traditional Chinese" msgstr "Traditionelles Chinesisch" @@ -311,15 +315,15 @@ msgstr "Diesen Monat" msgid "This year" msgstr "Dieses Jahr" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "Yes" msgstr "Ja" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "No" msgstr "Nein" -#: contrib/admin/filterspecs.py:154 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:154 forms/widgets.py:478 msgid "Unknown" msgstr "Unbekannt" @@ -859,7 +863,7 @@ msgstr "Sichern und neu hinzufügen" msgid "Save and continue editing" msgstr "Sichern und weiter bearbeiten" -#: contrib/admin/templates/admin/auth/user/add_form.html:5 +#: contrib/admin/templates/admin/auth/user/add_form.html:6 msgid "" "First, enter a username and password. Then, you'll be able to edit more user " "options." @@ -867,6 +871,10 @@ msgstr "" "Zuerst einen Benutzer und ein Passwort eingeben. Danach können weitere " "Optionen für den Benutzer geändert werden." +#: contrib/admin/templates/admin/auth/user/add_form.html:8 +msgid "Enter a username and password." +msgstr "Bitte einen Benutzernamen und ein Passwort eingeben." + #: contrib/admin/templates/admin/auth/user/change_password.html:28 #, python-format msgid "Enter a new password for the user %(username)s." @@ -1437,8 +1445,8 @@ msgstr "Mitteilung" msgid "Logged out" msgstr "Abgemeldet" -#: contrib/auth/management/commands/createsuperuser.py:23 -#: core/validators.py:120 forms/fields.py:428 +#: contrib/auth/management/commands/createsuperuser.py:24 +#: core/validators.py:120 forms/fields.py:427 msgid "Enter a valid e-mail address." msgstr "Bitte eine gültige E-Mail-Adresse eingeben." @@ -1506,7 +1514,7 @@ msgid "Email address" msgstr "E-Mail-Adresse" #: contrib/comments/forms.py:95 contrib/flatpages/admin.py:8 -#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1101 +#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1109 msgid "URL" msgstr "Adresse (URL)" @@ -1557,7 +1565,7 @@ msgstr "Kommentar" msgid "date/time submitted" msgstr "Datum/Zeit Erstellung" -#: contrib/comments/models.py:60 db/models/fields/__init__.py:896 +#: contrib/comments/models.py:60 db/models/fields/__init__.py:904 msgid "IP address" msgstr "IP-Adresse" @@ -4509,22 +4517,22 @@ msgstr "Sites" msgid "Enter a valid value." msgstr "Bitte einen gültigen Wert eingeben." -#: core/validators.py:87 forms/fields.py:529 +#: core/validators.py:87 forms/fields.py:528 msgid "Enter a valid URL." msgstr "Bitte eine gültige Adresse eingeben." -#: core/validators.py:89 forms/fields.py:530 +#: core/validators.py:89 forms/fields.py:529 msgid "This URL appears to be a broken link." msgstr "Diese Adresse scheint nicht gültig zu sein." -#: core/validators.py:123 forms/fields.py:873 +#: core/validators.py:123 forms/fields.py:877 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Bitte ein gültiges Kürzel, bestehend aus Buchstaben, Ziffern, Unterstrichen " "und Bindestrichen, eingeben." -#: core/validators.py:126 forms/fields.py:866 +#: core/validators.py:126 forms/fields.py:870 msgid "Enter a valid IPv4 address." msgstr "Bitte eine gültige IPv4-Adresse eingeben." @@ -4539,12 +4547,12 @@ msgstr "" "Bitte sicherstellen, dass der Wert %(limit_value)s ist. (Er ist %(show_value)" "s)" -#: core/validators.py:153 forms/fields.py:205 forms/fields.py:257 +#: core/validators.py:153 forms/fields.py:204 forms/fields.py:256 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Dieser Wert muss kleiner oder gleich %(limit_value)s sein." -#: core/validators.py:158 forms/fields.py:206 forms/fields.py:258 +#: core/validators.py:158 forms/fields.py:205 forms/fields.py:257 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Dieser Wert muss größer oder gleich %(limit_value)s sein." @@ -4595,13 +4603,13 @@ msgstr "Dieses Feld darf nicht leer sein." msgid "Field of type: %(field_type)s" msgstr "Feldtyp: %(field_type)s" -#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:852 -#: db/models/fields/__init__.py:961 db/models/fields/__init__.py:972 -#: db/models/fields/__init__.py:999 +#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:860 +#: db/models/fields/__init__.py:969 db/models/fields/__init__.py:980 +#: db/models/fields/__init__.py:1007 msgid "Integer" msgstr "Ganzzahl" -#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:850 +#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:858 msgid "This value must be an integer." msgstr "Dieser Wert muss eine Ganzzahl sein." @@ -4613,7 +4621,7 @@ msgstr "Dieser Wert muss True oder False sein." msgid "Boolean (Either True or False)" msgstr "Boolescher Wert (True oder False)" -#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:982 +#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:990 #, python-format msgid "String (up to %(max_length)s)" msgstr "Zeichenkette (bis zu %(max_length)s Zeichen)" @@ -4657,44 +4665,44 @@ msgstr "Dezimalzahl" msgid "E-mail address" msgstr "E-Mail-Adresse" -#: db/models/fields/__init__.py:799 db/models/fields/files.py:220 +#: db/models/fields/__init__.py:807 db/models/fields/files.py:220 #: db/models/fields/files.py:331 msgid "File path" msgstr "Dateipfad" -#: db/models/fields/__init__.py:822 +#: db/models/fields/__init__.py:830 msgid "This value must be a float." msgstr "Dieser Wert muss eine Gleitkommazahl sein." -#: db/models/fields/__init__.py:824 +#: db/models/fields/__init__.py:832 msgid "Floating point number" msgstr "Gleitkommazahl" -#: db/models/fields/__init__.py:883 +#: db/models/fields/__init__.py:891 msgid "Big (8 byte) integer" msgstr "Große Ganzzahl (8 Byte)" -#: db/models/fields/__init__.py:912 +#: db/models/fields/__init__.py:920 msgid "This value must be either None, True or False." msgstr "Dieser Wert muss None, True oder False sein." -#: db/models/fields/__init__.py:914 +#: db/models/fields/__init__.py:922 msgid "Boolean (Either True, False or None)" msgstr "Boolescher Wert (True, False oder None)" -#: db/models/fields/__init__.py:1005 +#: db/models/fields/__init__.py:1013 msgid "Text" msgstr "Text" -#: db/models/fields/__init__.py:1021 +#: db/models/fields/__init__.py:1029 msgid "Time" msgstr "Zeit" -#: db/models/fields/__init__.py:1025 +#: db/models/fields/__init__.py:1033 msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgstr "Bitte eine gültige Zeit im Format HH:MM[:ss[.uuuuuu]] eingeben." -#: db/models/fields/__init__.py:1109 +#: db/models/fields/__init__.py:1125 msgid "XML text" msgstr "XML-Text" @@ -4707,22 +4715,22 @@ msgstr "Modell %(model)s mit dem Primärschlüssel %(pk)r ist nicht vorhanden." msgid "Foreign Key (type determined by related field)" msgstr "Fremdschlüssel (Typ definiert durch verknüpftes Feld)" -#: db/models/fields/related.py:918 +#: db/models/fields/related.py:919 msgid "One-to-one relationship" msgstr "One-to-one-Beziehung" -#: db/models/fields/related.py:980 +#: db/models/fields/related.py:981 msgid "Many-to-many relationship" msgstr "Many-to-many-Beziehung" -#: db/models/fields/related.py:1000 +#: db/models/fields/related.py:1001 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" "Halten Sie die Strg-Taste (⌘ für Mac) während des Klickens gedrückt, um " "mehrere Einträge auszuwählen." -#: db/models/fields/related.py:1061 +#: db/models/fields/related.py:1062 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -4736,55 +4744,55 @@ msgstr[1] "" msgid "This field is required." msgstr "Dieses Feld ist zwingend erforderlich." -#: forms/fields.py:204 +#: forms/fields.py:203 msgid "Enter a whole number." msgstr "Bitte eine ganze Zahl eingeben." -#: forms/fields.py:235 forms/fields.py:256 +#: forms/fields.py:234 forms/fields.py:255 msgid "Enter a number." msgstr "Bitte eine Zahl eingeben." -#: forms/fields.py:259 +#: forms/fields.py:258 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Bitte geben Sie nicht mehr als insgesamt %s Ziffern ein." -#: forms/fields.py:260 +#: forms/fields.py:259 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Bitte geben Sie nicht mehr als %s Dezimalstellen ein." -#: forms/fields.py:261 +#: forms/fields.py:260 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Bitte geben Sie nicht mehr als %s Ziffern vor dem Komma ein." -#: forms/fields.py:323 forms/fields.py:838 +#: forms/fields.py:322 forms/fields.py:837 msgid "Enter a valid date." msgstr "Bitte ein gültiges Datum eingeben." -#: forms/fields.py:351 forms/fields.py:839 +#: forms/fields.py:350 forms/fields.py:838 msgid "Enter a valid time." msgstr "Bitte eine gültige Uhrzeit eingeben." -#: forms/fields.py:377 +#: forms/fields.py:376 msgid "Enter a valid date/time." msgstr "Bitte ein gültiges Datum und Uhrzeit eingeben." -#: forms/fields.py:435 +#: forms/fields.py:434 msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Es wurde keine Datei übermittelt. Überprüfen Sie das Encoding des Formulars." -#: forms/fields.py:436 +#: forms/fields.py:435 msgid "No file was submitted." msgstr "Es wurde keine Datei übertragen." -#: forms/fields.py:437 +#: forms/fields.py:436 msgid "The submitted file is empty." msgstr "Die ausgewählte Datei ist leer." -#: forms/fields.py:438 +#: forms/fields.py:437 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -4792,7 +4800,7 @@ msgstr "" "Bitte sicherstellen, dass der Dateiname maximal %(max)d Zeichen hat. (Er hat " "%(length)d)." -#: forms/fields.py:473 +#: forms/fields.py:472 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -4800,13 +4808,13 @@ msgstr "" "Bitte ein Bild hochladen. Die hochgeladene Datei ist kein Bild oder ist " "defekt." -#: forms/fields.py:596 forms/fields.py:671 +#: forms/fields.py:595 forms/fields.py:670 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Bitte eine gültige Auswahl treffen. %(value)s ist keine gültige Auswahl." -#: forms/fields.py:672 forms/fields.py:734 forms/models.py:1002 +#: forms/fields.py:671 forms/fields.py:733 forms/models.py:1002 msgid "Enter a list of values." msgstr "Bitte eine Liste mit Werten eingeben." From 3ccc25b10c1992238f756e27c5f984e6a02f4a86 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 6 Aug 2010 17:16:19 +0000 Subject: [PATCH 043/902] [1.2.X] Fixed #11159 -- Added mimetype detection to the test client for file uploads. Thanks to notanumber for the report and patch, and lomin for the test case. Backport of r13517 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13518 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/client.py | 6 ++++- .../test_client_regress/models.py | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/django/test/client.py b/django/test/client.py index e5a16b6e7989..498af5c3444f 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -3,6 +3,7 @@ import sys import os import re +import mimetypes try: from cStringIO import StringIO except ImportError: @@ -138,11 +139,14 @@ def encode_multipart(boundary, data): def encode_file(boundary, key, file): to_str = lambda s: smart_str(s, settings.DEFAULT_CHARSET) + content_type = mimetypes.guess_type(file.name)[0] + if content_type is None: + content_type = 'application/octet-stream' return [ '--' + boundary, 'Content-Disposition: form-data; name="%s"; filename="%s"' \ % (to_str(key), to_str(os.path.basename(file.name))), - 'Content-Type: application/octet-stream', + 'Content-Type: %s' % content_type, '', file.read() ] diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index 6da7ae4445b2..692e0f63d3c4 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -11,6 +11,7 @@ from django.core.exceptions import SuspiciousOperation from django.template import TemplateDoesNotExist, TemplateSyntaxError, Context from django.template import loader +from django.test.client import encode_file class AssertContainsTests(TestCase): def setUp(self): @@ -821,3 +822,26 @@ def test_unicode_payload_non_utf(self): response = self.client.post("/test_client_regress/parse_unicode_json/", json, content_type="application/json; charset=koi8-r") self.assertEqual(response.content, json.encode('koi8-r')) + +class DummyFile(object): + def __init__(self, filename): + self.name = filename + def read(self): + return 'TEST_FILE_CONTENT' + +class UploadedFileEncodingTest(TestCase): + def test_file_encoding(self): + encoded_file = encode_file('TEST_BOUNDARY', 'TEST_KEY', DummyFile('test_name.bin')) + self.assertEqual('--TEST_BOUNDARY', encoded_file[0]) + self.assertEqual('Content-Disposition: form-data; name="TEST_KEY"; filename="test_name.bin"', encoded_file[1]) + self.assertEqual('TEST_FILE_CONTENT', encoded_file[-1]) + + def test_guesses_content_type_on_file_encoding(self): + self.assertEqual('Content-Type: application/octet-stream', + encode_file('IGNORE', 'IGNORE', DummyFile("file.bin"))[2]) + self.assertEqual('Content-Type: text/plain', + encode_file('IGNORE', 'IGNORE', DummyFile("file.txt"))[2]) + self.assertEqual('Content-Type: application/zip', + encode_file('IGNORE', 'IGNORE', DummyFile("file.zip"))[2]) + self.assertEqual('Content-Type: application/octet-stream', + encode_file('IGNORE', 'IGNORE', DummyFile("file.unknown"))[2]) From 0ffbd440971c1aba8d0b8571ce468bb8b33d922a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 02:16:28 +0000 Subject: [PATCH 044/902] [1.2.X] Updated es_AR translation (via Ramiro) git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13523 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../conf/locale/es_AR/LC_MESSAGES/django.mo | Bin 77570 -> 77722 bytes .../conf/locale/es_AR/LC_MESSAGES/django.po | 178 +++++++++--------- .../conf/locale/es_AR/LC_MESSAGES/djangojs.mo | Bin 2828 -> 2512 bytes .../conf/locale/es_AR/LC_MESSAGES/djangojs.po | 32 +--- 4 files changed, 95 insertions(+), 115 deletions(-) diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.mo b/django/conf/locale/es_AR/LC_MESSAGES/django.mo index 1aee7ccc22dfb650894db710eaa247171c83c745..c538f9472aec7449e844cb2182fc5ea3d13044e8 100644 GIT binary patch delta 24795 zcmYk^1#}hH7RK=j5iGa_0)${e0|^q`-JReN+@ZK%+=>Uc0!51zf?JUm3I$rMrNyN< zEwotC_y6u}-eT4oerNBab7tn=1p3~XYkrHa`+2UV^qcE&O!jk}jMyQ&<0J`ioQ>6# zb(|Y*9cPY@=7)7By>QH2LT6I(Lu1~`T4a28YJ@2K{Fp|0=)s(qSnwh+_;!Z9I6SzHbi5m!gu zvPP(dx9!IM>mGF`p@{~f?(s;AzeEifi|VihbtPY;7PKDK?;xt(F{}R>6A@oTjdue> z@Cjze!0v9pNRJhYqgGZP)uA?OtDB=1*u~;ORzDtfFK43`vI@1pJ?0_QP8~OYL+!{t z)WV*j+I!wn&=vXia95NN6{j$RP*;`-wU98>Kt;^5sDY}ZCai~Q-xhUix}p|36tz=R zQT^v2^LU(P6oN>sLUq{hRye0o1D{8A_yhIO+%aFFCQQ`ReRhIT6XeId7=?+k9rnjw zsD<4|wR?>IdjFqO&;)O+!mpRxF%Y#A=};4eU?k?Zd|M17?t~h68fwAwFdZ&MO|%oW z0|zbs#k_=mdjD@w&2-HF*pcXI< zb!!_v9fIBIu;Sp5Flb96GqHf6z)a&^SwNw6m+<}szUeh$FiGxx7vZ2N) zh`P1KF(=kX%`*h`HjM7W{%@wRoWw$``-QvG2dI@k$9nh%i(}2cj?)pxqv{`-&oDXh zE7T`mf`0CnhoE*a0)w#(>PxB_>cR)~WB;|H(Im9u$*3!uiOSEl{8H4wt1ubvwfd7* ze-5>v-_7fm{}Z(%k5T>Jq9#tv6B&p>9tv3~WXDWc1vO9y)XKY|7Bbizh5ELfgjMh$ zMxp-z$Ek*;umO(8`gjRTVV;3pJ$A;DxCXT&o?8?mD7?o|%s3huPhm~Gh&?gOSodk4fqJVJquMRU zl(>E@`>zj_{UkEsG1L{@L3Q{KRsR9?dL|g>z75H+F>w&8T~ADleNhXVjOw=-)8J~< z4(~(lAFHj#uo*@*HQW%fACvz-bi~htrQ3LJA06d23cpA0v-%$hKLA85=>i5cgk6M85 zM7MonRGik;C(W3?_D9H4v0kT+woR-gr8n7@X!qS)+t6AI#wWV!PTiprE zV|UaC+9u2UOm*u6Fd_NGQ+fXrQ%FNX17xv^T&OE8hR~^Ny3iX_*?$di*AjoD26}TGUYK~_y3|LuAr{?Hfo^9mVa)(Mcw0gGu;6KQ430iddNai z3oc>x+V?`W>x&+B97aJ`HU{;R$yC%rR-+cO2{qwvOo7Kx&%h;A z|2wF5k1T$Px}f){eu-wgaXM7L9A@Nf?!N{oO+qWGgxaFos0B4ZEvTjC2ViC5p;!bD zp%(hm{DA5g5bI8u9Cd3lp%zjURbLs^uWl^&U-u%KgjU=N_4U~eQ(;d`iK9_JV9ddi zxB>Mu-aXWUedoAuPXg3LL1qrrM1@d0R1!5#6^mPWC?q4%1(V}o)Buw$o{ve1*P&Lv z%kqaY1@Spl|2wFKJjc}dA8Nv+bKRXykJ|dImx7)CymrZh`MScfbJD!qT7y z3_-Qaj%hFgwR2T0AB}3?-ts+AJ2V3IGvG7~!F8C1`JLkwwDPN{l|M$^Tfh14R)(S; zq9Uk#UDU1Uj#|h7)Q*itUC0E>&qD3sQq(P7ZE+lG$M#_X=68Oi5RR|Sum$elXg0zq z@}p5#v>P?hVXT5bV+5vM$nOqV74zUOEQ7bO9A;VMe*d?_P~yE9hL_P(lR~`3{MLeX zupXYniI{DP`zzH3)I#o}7LsYHdn>|F&rUwG5bBCcpmwMNYDa2WeYC}`Q9IUkDf_Rd zxEBe%*P~GJG1R@jgqq-*`6p@z9-}6DgL>NIEpsOf!gR!0Q2k1n18U{jP#yB4+7(3&Sk~f77S}?JS0B~B1?mDiU|Jl2 z`VN?is`t#Kpn(>mR=NzeBXOvw`!woGen)Nn4b;~Ch5BTCi>gn$!W}RTDh@#{GzV%y z;iwOuqF4uOB0J}C7Evfe;s#d5%q!iexHD>nvr*5&V${GZEMA8icr$9E-KYf~LfyhM zsD(a6E%X(tU81ku9Z8AazyHfdK^3`C6GoaPP|rX`)QTIRZp8|#--znB12w?`i+@2~ z=_QLFqZa-gbs=vp_F2URct|9mpo)~J1*B62X0bRgZXqs=8t4wH{Ug*T;4{=i6@RrG z2chC{)B;PRo`K4!3uuDcsTlOA(3^rL9D%y$6EPFUq879jHSu1nKa9DFPopMyh1#i) zsELxUaTkyo^)?hh)t5&-g!NEwPvmW$Fbh7n zIPEv?t*VTXQ?%1)ic2TPeefj zBu6z2Mh%e7+koHgtUl7>lBj{oTfQ2qeFMukM?C{EsPP6^{YXqoJOR~zE+*0Yzl4HT z{tarN&8QEQofe-!4RjWh;2)@`_daT%zffEM0yR#$ZSDnS#OlOhsB!yXG8~M}a1wgo z|6A7JDQbY%$h~pip|+}VoV$SLsGVw$d9f!Z#)YT}R-+cQ3ANzumT}^VYC-o= z3rexqT|hAE)@DcL^SXJDQ<#FTxD4u^RzWpvf?8oKvpZ@FhoG)tJZcLkquS3im!cl7 zRjBss%^g;M*!&TF_5S}%L0fj-o8X_}%m=6~e}Y=^JJdq`_qh`%K`k&CRi6u0A8GZ) zt-cCsL5)xgYKD3%I->XA|A$b}6^%h%(F9b(SkwZ(LOooYQ9HEV>c2}L+L`eCT{ z6Hpg46LmpLEx#VsKW;z!ufi@8x`KVEtvrgl@>7<-j9Sob)PN6B3x0+1@SVjUO}_)~ z1PM{?lb{w9Y-UF-DE|TOzZOt}ge-#^umWn!Dx)T9j9OT8)Q+@A4bTgNa44$ZY*f3s zsAppts@+D^0(N5>Jc0%Al81sON^sC!KswY;WJIkzJ8FPP)WD@tx2OtgVYMu-kGg

      k3 z{fQ_LweT#ce&MKbqs&rBdyiAWEjYDND{hL~s*b3g=#6?72H|WU9x6P5=?}X<8U1#I zpNy&h3)^D0qdc%U4m08fERN4m3(5Pv`!Aw}F_qr`FDR(tcylGTCjJidV8{>dCt7v0 zFKS_n&9BW3sCyfSIq-zVe`6+M|6|++%!b);2Bu|xCys))?j&Z%i>O=j1_SUtYC^x` z?iB>0;xwqO&xGoqAGK4(F*8;}{m9h?Q{oh>UyA<38`1mU|7@e6t=VlJHcy)8Q4?LU z_z89)eu+S9mBSrmzup<-09D zh??jm>a9438tA%t-|C;C`u&Gmz(>mmopRe}M%Cv+O_(18u{i41c&bpyN})Y!!U?FA zPsc!WCtuhW%YlccJLl*XWpR3Np{*bEvh~fYR4jw3-UPSC}?HXQ5_ng zR@}mE;KW$m&FcGDeh_LQqflEr!}1GJlYbJu{b_s8V;rMU#XTa0SA8+8HSp)Txu)CHVDUEnp;PTa#t^gYM@ zSHmLb+_#`CZYOSsUt!RBcYI}Omfj3KPM(7 zu7P@Xnt7}+7_}wyQCsvi>WVg?u4tRZyUc@_hWrnxE4+;A_XySRU(|)YK`qShSNGve zg!)bhLA~~#auhUid(_j}8};op+!`!L4R93I@dRqiAE73EiTZl|fYq?rZ*Dvq+Y#?W zeVc|{a^n$LpZErr*ZW`aclQ^G{uoBZPArU9u>hw0!yUK+h7x~?b#Wu6#dlZ?Q(bo3 z*TM+m;aC(mVF~;b8)E1c_ditXk5%>lucgq4iodZoR=Vn5>1+%p-fsSk6^I|A+J|3r zw{Q%WAzqE8@eX#ztk>Pwa|&wb&Y&*vDK^2N8@!?(3cV<(VkK&&Cs4P*@20!bTxJU_ zM1DFJ!6T^ludxRfy5;WVa?C({3$x+}%!gTSyZ!56CgL&Z(UmNxkOyC&wlegN8#lvF z#N#jrJ~C7M>HhLr6kCz+g_-dbR>a4c9V70#Uowrc9C2S%yExPW&fjJK^HO+5A}40L z=PsZ!YQ-Zl7j8f;@B(UpkC-1b-*^9pqYnBLFTfPI4CCXs*Z_B9S4{lCeb@(}AMxl1 z?7tdLAdwQMV?tbky7wC}H6BBCxQu$;ZlL--MZFELaTxkObYDLYh7wOeJ)~W0oUtj>fx7hEoy90r!`jnUugDuW(<}(YU z`g=-I(AHJ7it4D2^(}6JYSqHf7Y48)XA+zB$H1`M;f0IFXUCc+9?3C3X(oMQD0P!lf4YWNLS#(z=mi~sHJWLeZs*1>TYgX(t|z5o9I zltK`RSE#2o*;Du4g<&4zC^N>Kisi}gLcJaTVF)ID=Dwccs86`ssEJmgE@ZR03wseC zdS>7M1^#hYTm*IHCC!SKuW8mdqfrC4Lf!LDsAuF0)DDeA?dVk0PQ;=Xv=Hm!Q_Gk8 zm;Kk%TjgJOr8Q9xQ)AQsF_!OwX^8uplQ1RmGE9YAQSE;~E$oze(el?&^Zbc={~wxu zp6BirgrHWM8#O=y)PTiMJ5tv2RZ;EgS-y$c8k3Rlgz7&Kwe_FIpKS3QOiF&)r}|A4 zG{G)Z$L}x|{)pPjE2s${qOSNUs-5o(w_hOY7NkbC%WQED3?q&}T}U));?}77dLr#T z&HxI@Nen|ZoNC6Ru4oZz!f(tt)ByWY_xyWIjb||h-nIJIrt{L>`UI#8N`h%I118n? ze?bbGs61+-`Zyk&qZV`nHQ_zf75-MvZd;wU8_5QQ;;9 zHN1=ZUVevO1NCLq6g6Q>)CIK1QrH8_;d(594_~wY z>Ja>&+b{&RkesLi!Z8R7qgGxEwU9=rTh<2ku=Ym1wqsHKW}q%$p2c5V{0(ZMyDa|U zKkmOeo+BZzp$2$}TKO}J-=MDC=Z(8{iOuBLkbD}ffL&1w-Dqw{_1lknIFDggyo7q2 z-g>M-(zkBMbf_H&MXfXp<6&V8!=jiOTcd8(NUV)ZFh4#-Eg=0ncY-XKmbftL*{F%C zk3sFErz-^w*xwR!P+R*I>b2a0TF4>P6`Vo6$2U*|{cXN7Kcf1_fA8*85bE{Jh?+1G zwIijF9rrj@DCizHK&`MF>K2Sb-GYgzhjb=t!nszz)bgt=zuw%6dM5Uu7INC+tEioQ zh8pJ;df)$#R+03BJ8^2%N;9CIi7?bHD2Q5M5!AwJq88c+L$C#^ekf|EW}^D9L_Lg~ zQMY0rs@*yC{`Y_PRUi@Y(fx5bJE}o>%!|<&fn!n6z;?`zmoXReSb8VOjk;w~s2!_{ zx{z9^1xKTHpe<_rPU!vbe}_}hJsO7PS$Hys09D~(xC2GJ&sAu6h)EheSN&Y^HnzI<9PB9e0@CLpYwbB`FO8z3AU!Mz9ixQWXo^ibSI?ZjVL z6@3%YjTr2hKu0<-1V#{4lEhCG$FJA$Qe5g=eUTzKPvu z#FLJKUXx6y2H~ic7e?(!84SfLsE4XEYQa5F3mAlIH_GZKSv<|+xv23LnX6C_>1Jf% z9_Kp>`VRONHP9dCEw_R55HphZ33PWL1hvpe)CW^B)Xr70`i55D2{m3%iw9Ud9Mx|e zre}UGs2I`iq#Q4nb?4zKDM^O*i1y$f5s0nXb{Ug-9dxtGCMKbqR^g^{8g6cO0HPIA{ zm!c+IZSg_#7<&Ky{|p5UaKXHaTF70CAEU19g~cE73~_?w?hgEhYJUxb@eb;l`e<>o z6mFacHBSlD1y@YL`>%(o5eeOsHfB%Mgu_rDq2n<#&O!~a6?LV1t^Npx6Q8mCJJff8 zUrN_>s0s6-cCa++6R<%_-haJ5T}Wt)JgBW(U~aI62T>2{1=PfsPz$+%8u$U~S$Jdh zKB?RtNr?Jnb4%9<6Y>t|so!J%Dz7MMZK-3P6 zMqSVfi+7{0^b+b@_Ae}o0jb@G+f#-@77}w%593bEjZtZQyuT{7MSTfK?~W z>weN@MSXcyL)CY(cos$xZ^3$a7xmB<4f1i;;2aFq`(HAhkN0o0TH*>SW}_BVIK4Yj z6O1AL0>|Nb?21i;eZ1d-M^Fp6hgz6_2KRLf#cagoQ9IKSbz$SNG;YCsy5uJmwB^AW z-7lMLsLz81SPK8aoLC@}kN0Q1XbdA>gvuXAE%*U8!o(r&t!s;VX1bwnVK2)MMBU0! zQd>Tef<8c|qwZ-e>fSFzE$nO502@&QZnyf~sP;!t1O90FpHc09xBN}icgB6xcyFxU zH#4_f?`dKR8XyDeY0Zk7DAMvpP;W&k)P!|W12slH)vYbx12s-x)ay6|HO^Yp1#Q3z zxC8ZCzs}72ucy;5)W`c@sRg4x%V(mlU@2;V)z}f&TRvG9cR@j@h2+7KSP1nR_eS-f zgj&!{RJ-}8_kTI+t=W;qT7i)W(Rtx&z*|LqjC_508Vf3yZCQ3GE!Z=!bS zPxBROrxNCIzvWV+?rkpAy)BKppz5f84N((!L_KqZ(WAsj3hFouHQ*Z5#9L8cCOc3A zoI-ulT|-@wlh>WtA2m*TRKI+vt&TwLL~+!F<;*Imov4+U_g@3IBH{f#9&;0qMRnYS z>bTwFy{H8qMqS}4)GfM%n)tr?*nDdB&ru8V%jfQN64V6;=i~j?#05xbfQn{S)H6^M zHDN>4y>D&#p5_457LPzJcp}Eb>8ORzvikX`@mHXpoze=UDVcpMD2iIxVzBgs4Gl|*)a=hAyrTlRY&bq6I8!WsD*g?QqV{6C@hTgP+Rj8 zY9TjJJ8=iKQ;$*i&dKjim;!aB!Dc9GC&Ew*4M#1cAZmd{Pzx!8>>TgETX349CTNH1 zFc1sj5Y#}MPz%|HTG(ET4_bW8;*+Rx&Y>o{i{bbZ^{izt;2MSAfB&yWK@-$R-GU~l zd)^VXb$zUU1Zn|ePy~0;ml7B-!kX%E~1jMzhZDs~JMBVqqV`+cD^0R5DL*ElGDDTJeHlFe$ zXt$Vi59K258HAq+oxTLxwz9-J{-r?xXD*A+(MHdSj*79Pt7q_JqelDmh;tvc`dNuD z59hx1Aap7-ZFY-$VH@f{9n*>TaaJU;j+t~+z|OQe!TE%EFXtJni}HwB{UO*LZ?lJvyc5TtNN}?T%4yO8zh&CHE1pbLv>YInm1A1b;-+<{UY_BfTi= zXXmeo>)RZU;_=D7h=Fxnp<)l^P56Si79D&kC&b2_K6IKwJ|EWN{7759W63ZEYtrTi z3?i=1If}lYj^0*YM;jfQe=BX?SE5jk=n@TLIr%zsZd0*@d{VWtV*{?BzCV_uz6NJ= z%3+)xVq4YB;CW3Zm}sX>MbX*HXo-kBQy#@=EopO)Q^y(V{HS}5xv&{$F-{#DO_lXi zxsGpWSDwD#Q69_r53#IoXkl#mqnEWit-(oTH zTgWBld`vkHW8@>x=c02E`SHVhd40#!#+lU3rOl_K(PwRcvV0c|=S)wd^qIU z)8sr|X}rKHr-ryq3h?TCW4D3%W$qB{sMIjBHy0{j^Uh5+?%roxioHPr!DcnwB11c-wgZ~ zpAZ*RjNftgqs`Zx<;nfRS%LC<+UgkM{l8#x%g$dUyU}15@j%Wt)+jdyG2tx+>qfqg zO}>%%N6rb>t|a+4^zTFNF6Vd~_Y+si)gt(lHqo3V)KAyHmWtP(TA3fQE(3PryiMbH zv{`^diRVz~L-`}Qj>J0jJFAXuXYO*f2RoL z@`L$VJm|B7@wZJcit8>bsCzO}Vc1DM(qz zBF;Op2kHfR+R|gTjTB|m&hWP3w^H(dP|i($m@O|axrXF2+K8E`o2s(f(nf!kqT>tV zPQ+iMH|CGm#0M>YhQ0iFrjlFYq}$GEZt*0G#Y)S4N@cWoDPHCrY>SCvg5I3DIUBRE z*5t?8MRlTli!+?|^RS6`Fz#t_5*cVTpG0OVJ{>v8Cu5*6>_uKblTAYY=FExutex7g zA@`1U_sI|E{Fz)2a@nu}`Fo$Wk0D==`Ib^ws4W${NqjmU6W1XQ<2-4d9%54By9|`s z8b*-YK{*gtS^pY1#QLaN7RsGtw>C)S`I?#soG;1t;w(gYKj%?efkU@#qBDeIq>hjFH`1@QNJ&QS8*ssD;{N^AQo{pS&PCay{Ow6*`8cBN>)O#|A| z-YiL*MxU-+x%70tLgP>>+7S=4MGc~yf^q@QU(}GJEae8AKhZuXaSr;1QXWcq1y)ug zj%XK$e^Pap*rJr;FB+Y}oIb;24>t<(bfimi!t>T#gfe^uawG8{&aLE{GEGJ;le~@= z*qpkG^q)oSPx)I;9W`m!l5&2^7p(nN+SRf8!jx}P*IiG+41%sS>PTfJ$~u;Fjw9zo zoB$Mhz^9{_Ehw19`cO_wMJ~#Pbkg=9xe??_ScmPzLG;f@T@y}! z;%}%MOKvdnaZVlHudhx7r5H%Z0LpW$vK6_BoLlHzkK9>0Kfs5a|8gEDzJocb58)g} zxd>ydMjeC5=~za)!q|14AWU()#b1T zJ@A;7ciLRu{Zg(i169Es43Gi;qQMh#1+3v04ETz=bJQiHT$pk&XC&p`wA*IwqHRG1 z$p1n8eez4Kt~T*_%1;?%I^{|}yi)N=T(nNfsF-VIiq1UBYiU^31{fWCplOh&rnMM@ zGc_dVX<8S@DV%;xeV_O#<&l)D(ry>$H`Z?l`AOvS;WN&Glzll%aQ^=we~DRtrB4@< z-HDU9^&V$E6-jJK>TsS$6=_hMTv^T@l$UdUI+~K3Nif+GqiBE37NgW+>Su83_>XfT z=W&bs({34OVkU2gKgY|=lk!>TK1@`X__1|}h<(;9RjaNve@mtVLrtc3Ea!gfHJ-XS zTV6vPMwV-DSUR)bHzIkR(LP`_W z@v~zU@ip>?Eq?%8aZX~qJGSbh#6Qv}DQ?86j@s6KDY-F>;mi5|mxV@W zW2d#q=6P*BdNV?Ovhnbx<+4ydMJ}(!Wr)jiE@UMA6ZH!U|pe6&VmbSGC0%=p-^2x1zHR^8AJ}vpS1)ClQ%)L;;*H==$jhXXw!cjc_=1 zSy4wO+OH-aPWdoSB@UskD19H3D?<4ixlWYhQ@)FjXm^$~FQ<-M)IXs8Cig3d!AQ&| zQ53&)+j#%XPlsjXQf{o%JXBNw9eqkN$c}uKd9P{>g06frlpR} zHk!(tJ}djtuOV&Ek`JM6U7M*LW+VQVw&m~#%d7u*a(yi>!(zPu1EI7G(!mDy$L=(k zZ2f$ITllQk~Y&Rr?Pf8 zD5vBMAg*EUCS!GSDQp3Gh_l3PZCgEoPuIA&U9u&Qy*D;(T$&+U(+0#0iRls3Gw#TY zV+9Hp3y+8jjf^T05fvU$C?XA=Ru^aoJjKW|N8gL@&3KpRT_yN^nGp4{@sP<=2S9lZE{snp$(%oG^5C)JBw>T05 ziDOW=tPrYwx$f-0?om||ny3lt9=Eo*2kMIYqdE*nUCCI~f+nN-EkU(gZS@;5ka!zv zyn~nlPh%85M)gbX>ERYqqgIv))gd=(t4p93Sk2<5R^JJAFZ-buG7h!C1?IP?omyk= zK<&tJ48(J&_MYn$bVYYiEBedgXXY!^mAywT#J{IIP)ai+YM>a@g!xeI%b{*fb<{#z zpvLcsTF3xoUXL@9LIjC%s1A$W3THiP;CNJr-KdA=hKK3?noY$y{K45+fj&*me97YpYK@HpswctUR z3P+$Onv2?jB^GZoccQP}|AQ3t0df>I(QVXDJjWnR*xNNZYGD~s^*K>HQp)Pt`y6CFft@j28^-9ruZ59&32ftvU& zs$arB?l_^STbmlQVSdy+%~5YdyFTpy1`4A{EWo^d-Ibn1O>hzG;WaFdIr}+IC+vi( z|Jgi;A;ecupLh>2IetLxU`T&HzA*#pOR6~P!W;Kz|FxoaBy{E7P*>Ckl^1MAKt5YM@G}mDfNmq?y?k z3ln$6D!2rT;61E{=>|GZ1MGzLaVM6-z(IUZU{x%M6Hq(iIYc2Jg&P=&!Gj&AH0DL! zyIxoh$D^*`7#76K7=wXB947}B!UEU^i{LD*g(uDMpUr8&CtE#N6n7>0W6bEI~LL8PM5_Y4H+P$7h%oOAdG6vUZr4cqFFQ`@eyL z8k{iy#%#nX`0CS@l|pssg3WLqmcys0h36mXew|iAP23u@<1h@v6<8j3pceQB(_<)e zr_uYLkAF18nizpgF*WYMJa`Vvp)<;@FOT^No1-R}iYag@YQbAk#={WW-qZUt2Megsxx}>h;@g4Gy4Q%af?L;4C)AtEhH)#<{-(3ZfQN z12u3<)RlKeJv+luJ2?+y@fhk8vG91dRuhyP@BZ|vidtz+RK5{vz%Nh>=!AaQ%i{j1 z`Y$aWZ+?XuXAWwdCFqB%P~)saEg;@wiT$XC$58k5G-`l9EdLOFiJw{g(tMA4Eqy1r zKVnm$78HS+C=+U&7>f&_ZcPc)2a~4?1>KXzmgtIp#Dh@-jX-~#i0U{Uwep3ifmfm0 zZ9w(gY3@fY;0UVyDT}X|w~&4w=RO608azXN;=Q)mf1*1;5Ne{7s0lKlCXBNBJXT-C zEQ^(>uZG&OuTbO8Le2B7xdsz?-+u}ka64*(gQyiB^ETl1v-)4LD)~ED4RcO%+YdlZ zFbs7oCLtdU&V1B3;gjuFpdRuV)GaQ9K|Fs>0}6WI+nU47*%(891M1uDDu&}8zlFfq2qMA!wJVsEQIj@psaR(~EnYIw~W+_nY}tico1fG;o*KcJq0fT?br z61Am~sDZO#dCY|kG1l^j%;Ok9{uCy`%Tw8Z4RG5k{z6^pzo>`OcbfZNN1*aCsP-jL z3oDPhfEt!>fLcg%)Q+|`hoGMJS*V>_hU&j&8vC!rCK4KGyH)JBhQ}>`8a44H)IGkA z8sIVNie8|0=%dAfU%Agj3RHb&)cCnj{R*Kzuu6L<1W~AqT1YF@m3Kfj9E9qqcA8)! zs@*J$=VK)C3RJrzs4G2X@o%Udxr0IY0CgcRP0xD@>fk%w9Uu%faaxO`Fez~^)IBSX zT6tyEf|{ZRY>%3#o5iuHegjbp8-r>;1vUO0?63EK2?Y%hI>T+48g+%4P%F-c8mO?v z6;SQ#nN3j>v^G1V77~kkX8NNh9*62T!<_4t`~M9Et!O1`p!L>Zo4MCKj9SP~sE6wc zYN5}p{uQeK2TX|mU%LwlM72wX`m#%j8mAy8()(YIf>u}!wUCAuw=}z;cA_6@;8Cb6 z8HZ{=74zV1EQp6OH@-r(&pOj>7lZ0o0QFE7M~{AyRHUGlw?r+ZBWl9lm>fr-?)?nZ z0EcSQlH1%;vVxv(8RhPsk{s0AEH-I|N2h5T*x?@;~xbKnz)WUXSFdjfnbjG}nn&=^Fhn}Oxd2exw+3x2@MhqdJ*F!-Al(R$~)WB^}R~Bpe zA()(aGA6@CsD-S<6u1dB;ZfAqo<(i_uNa2+%r~f85H!ba=Se|91Eoi;JTt064$J4a zd=aw@YU`??7TOT?Wz-(^ln*mUS^Wfy=b*-4gj(ndWP$wokAiN&Zq$JLP*-pYHQ*)G zmHdV&@jhzn-djF+uG>BhDxU?lLxoVk{VHJwY=i1Q5;f0kj9`9e1qI#PJ(vQopdO;f zmiM3M-ipkqh2%o*STWRD-O`p8cSr45Kg^3B%#9nR-v1jEa$%DB?w@dq zp?07*YM>!l1t(xWJdM5a1LnZk1@52i7GOEztJoA%FLb{f`eHQkEUbyUkRQ;F-y#-M zkHTaM6Y(0B!1jyXg?xir$VJqxxPf{$?wAizSNsh1u)aa^oGwK%YLQQxEQ{h!qzh~xa)Wjdnz$NZ2NQt_& znK2SeEn)w4?^}`3LopQf5KTf2xWM9XQ4?)Ot^6m{R)#Efuc#%eeJpB$15sBz1@$n_ zK<(sG)WX-IE@+E~LIj1~mN<`E`88CBdzSwjHQ-B&|7UT+Z`}bCquPg}E+8$2V=mNp zKt)u2Rn$23Pz&`mrl2k9j(WPsp|0d>)Yi{KJ-w??pNv~j^+!Aimj4|S>-~S=E%5N5o`JWh6$h+vZ$&fIm3BZ4*b_Cu0E;K0 zu5^aQD^LqxhnjGU#XC_KaKQ2>(fjxRzgWdpC1`LPH{v7IK#Nwo11(2=f~`e8RQoMH zWARx61J95dlM zi%+9&)jQ0OX})*A8){)?;y4_LzhXM9yUzWqot`*>c$USfe{f%WPgx3$sc4S%aU<44 zpY`r*)c|z`v8WHK$(R9`VP-stTF@PgMxPDbU(AkLcv}p_G1v^h!lw8qR?+)khDSY$ z#CX(u{5@)dW0(i;VRlTh$$c$LV*v3q)H5>|6X8P3FUQ2hKcGKuLtosBdIo+(^*@f@ z_x~IP4RFmG+_nY}EPjp}=(XiPqS^;+cJs+m&p;~F#JN!Qg)taQqx#pvAZ&0()RkmG<@2NZl}EL&gPNc%YUjG4 zZbffYyOG!hr*7r_*A@Otq7}YJJ-uISyUtYRZ-;9XcAPhb%Y+~K|rff2Bi9}5_b+L7*95>KEO;=9ZJ_XAZ>p9|wKJ1#{n z=mcs3=TNuyH_P9){3Fx_zd&xC$9Ydd4TE;OD@ z3cU}Z`5S6M_fYMgnJ@OT{~GWO32oUs)I>@5x%VnLY6sGw2FQvLm><=zI_d&yp`MM# zsCFGt3+Rn0aVX}+8K`*8u&ldv+y3Zu!Q^FI5BE~VAMiF zQT_PXJ(ey+7a;1J1 zw#92$7fT*?KftD9apJY8h1|wc_z3kHWpwf82_ia1ZJV4qJQ* zwe=TK1KdOH)KkoeA2AbVJnsIUD37Xdgnrn;?1mb*H+obUVu{h_6x2j<7O%vv#OqNL zho5j4nhEtZXScW*rXwzk;n)(jL;W!kPPTX^YQbwx@cwIM$4U6(X=`}V;_En;{2iQ& zT~4|aen1Tz{FB=+3M&!k#KhPKeQ~JOk2J@l7BUS(arRH_zph{n2|XM;P+RshYJh8~ z4!2NG`F<cc{;Sq(8em6@|Kh!l(t5LOs-#Ev}1!#LX}Xc0gTtZ;vGgq9z)RdXFcg z2AXRwwfeQFj+;;m*lzhVsP>nw{uXM&d#LfAqHfK5%#3MHx$}8SQ_#vQqZ-ySo1;E3 zI-_=EfW>32ekN)Qm!NiLGisb;=4q?Hh}yCHs0F@4EzCLXZSQfCP|y{Hq8g;KIFsAJ z$!7UHsD%_oZEY3H*F#O*9JP?Ps0jw59^z4`h0a8czY^7dJ$nE9pB)smmHSXDz2vQ6 z>rf3JVN!g788E>a_vy}rdS;5D`qx5D+!6C*Z&bf!_$6*YEg;7)?$#GXKfV7YDQKed zR#6j^5I0l_hCqO86A}V&RK!{Tg#UYN7F%8jqkRxQW5&bIE;nLd?9V z9jW7?pe<^Fx}x@|E9z!(tT_-K_7DAbNDM@_gM_4T?9tKmNu7rVmZiTk0xO)pto=&Jko z3iGf$`M=DZzj}X+dYoPq3Q;i|^WsU=z;7@TD_nEG-#cJ9aXc2opHS@+{^tIYDTqah zJ7Ni3j1BP$R>hpx`6k5vSQ}4ZCZ0bh{D%AV7c(1T1scSnI&4PmjQ>sd?{;FaG;vq# zf~!%lW$-O`#~NTX@c?Xsi?AR*w|w;P?gDFL77rDZC=|dS%v-1qp?|pl)~gEYiiTrP z+=1mW>bCoIcSbF64CcaBsQ%|MJ^J5qFC+?c5D!J|;7ar;af3oi z!B+SjGh)4a?iKgJDB`WCFPC4j9KJ-gEBdFqfToy}cpzrOWvB(5{FD9HiWA&-Kgsf< z7Ss$iz*x+KE3i7AM?XyW!2JNqjERT~VFN6M-Eao#DSv~$==Yb~E)YYBLoooe{>A>M zp^%qE3ao+Z&<6FIbwo`t00VJ2cEgG2kIyg?U!xw*)DPWu`7kwcN!0j_u_E@tnYa}L zv5M!B`$<(F6Ow3Qack7XT~HJCws;U~2S!>v4g-j%Sv=QVVy;H@-+=13&GLIt{XB;) zaT?X|5~jlI*5HNJe?;v}qQ`E#q-Gdur_x|z%z+xXAnF22TfVZzbubC}CdenF$7xSN z_oOc-#c`;G%tQ@1-{NJcj%!g{x7F$oSbPF?g%`|UQ46?b_4h4)Zob6;z5fZG*cG9^ z?ZPb1gc=|_Y9aYh6BI{XVOh&pHyfB=pdQAKs0-PF+R^$ZcJd&O!waZ|Hv7k2NPCPR?uL3;N24bhh4~b6 z;9B#7`2qDo6Z6!4J9=OS;!&vAb1CW*?f`0{@MrFYM4B-eOTGYVq07uQs0&;FjQv+( zn^o*H51S`ZJM#|1Hfys0;WSwV*|){>wZR zG~oBBo!D#@yHE`eS^k817L$>`iW=Yn>WZGBCitJl{{OkpL zw3T&G6ShQMaeGw5fvAonQ4>r+wVP@2Jd7q@j=GSO7>s98{cfS!-^UPqf@=4{l|TQz zaIYvSYQl78R@4A_Q1`qDrof7r9Gh8vce5XA>xZE($b)*yzd|i&C2Hs5QS%(e@p}JH zQP7GSymTjQj#_a$)WluP0p@5_`{}55b5ILiWUfFhWSzyEP`6+ws^1~=q-1{QECsFT z3TmL=%?D~g{M6!Cn3DJ->fuWH%3VNSRJ+2c{-sb0sAN`0Ewm2m_dp}mh4n-4fB)y9 zpnE$JwbEG@FE-bpwsZ^XOX?tM!Xv1K|BR*ZB9_DCuic+!4N&dJpxRA9Eo26&|D4zC ze*}faB((CasDQAhVzW;N72{k|sG#WL*WDLi}sAnVI>QAC}@*HZs>lVL5 z?QEiV?(67@prDmxM_oY?)az0WHBnQujoAe?KyTDec~GzAIMjp-P&={`we=fNw|F;d zf#*@T-~n4>LU{dbs?`JDq4^5G-YGm!41`?I*M{iVPRZLJlogD)$g(TGpGSCS$xCdyQqGTF*UwLJyR)?`gs4thFGjl zd;lBk{SOH8@&1iQ8`KpqKs^h~Q43j%-q#IvrCTk(A9bZiF%mDMF5ngFmIMa73(SIQ zmlHK^Da%(t?|=VO!x}V1-Mfz15{II0#bwmO{y<&vL)1jiE%r<1P8fu$k2dq5CM<&L zU&^d(`8w$R`~OB1bY-oqK^Htj+y}J-WkcM7s$d%8TBwJri^YR2o`srdIqHhnp)O!A z>XsZgFQMkU6TXzI`?VJcZh6TU;9btgj#AMbz4wFK)DdrnZuNg-8AAMc+5E1~XjZ_J8QP~T#ktp1F} zZ?FjYh;a9{s)KrHm*Q%Chmp7x zfraUwB%z=IQlJJ*hiVXs>W~99U_Q$iMYS()`Rb@Ir+TO>ZEy8GP_Jo!RR6K4hjj{S zo(1Sp#Wxi6TC7A(xC1rNKGaiv%<>me?XRI;$3IX5CClh8I1IIe=~1t3ThznZ69?iL z)JOSi)CKrO^8RapphzF@|E@PVs$wu|K_1jXW?@NOgnEy!pxQk}E$B6>-AB~>pE#5I z)}+U1;_RqfP#3kZMyU2}GkM%A?nXkdS#Q*q4YPO(YHR11Yf!gl7iwn?quQTCUD+R~ z_J5-$a5B5&2cSMjlA+o~qIM*=hk^!Nfn9Mu7Q^r?KF%tvi`t3ZdFnxBAAYi94d&^+nC=@leoKPBv#+#RAm8E3heU!5SEt)&0%a7&X8g z)B=~Hu6Tvze=xUO{Xx{u96?>kW#k8q$N7Uo1`@u}?gvg5tVmoPhv8S)787T4Khe6O z>JMNfCd}^R{oC^>)B>8K7SI%=G z9?J8m_V>)ksE6xc)IENUY8MdWwo8ZF`Yh;!`B3!*FrnW6$|_)W)E3n?+n~0pFY4QF zIO^Vhjk>q1P*=Jc)o%}K;M1tr_;-u{MD=@v8ZS79J6~$_=*uKM1r1OL^+{I+bwyoK z6Zb+5JQ}so*{H3akJ^c4s0r7a8&Es36*cZr)Hqi#1|OmNrOe6uua4<*x{1uF6~>^h zun_9?DuVc>$9)p^A9;*L3b0g{*h|kIUuL<{%(7iuq6_?B# zs4c#STJb-a0AHdO{>JJ*q9zE)sD-38v!li@jA~cOtmdJhhoTnhldTSF>${+K zpeJgjLr_;Z3Zrl`Y9Sj?6KzKA)P7XIGpL978tNnX0T#j!sGTX8+g*sK8U;;I3$;~^ zQ1`AYYQmwYD;;A_LhZzK)I#T=7P1i4{u|UnR-<-qyLk{b{!gg(H?g4i{imRTQs!|N zk_NR!nJtdCIFH2zPy>}fO;iVSV{6pIHqBguTF@rc_`6WIU_a`XpT?wm|F2qud#Igw zh#L4g>ed|7YB;tNOu*!f#$)5Gr2N6^7O4TpJmRC&?ZbcY7MD5+{dLJ46)3Og?4}iQ zZ1hm^mO>Je4LB1K*RaOv8RQ^!hloehVUOi!(yk|YeNX&Hc`uf=0Vz6P(ry9gF3Rl~ zgP)8}Kg!y*(!@HRQSZl@GTIX7Xru>4NBOvA)zf-1QKOA|#JQW=QpmTD^QZM7bSg4! zW{Z1bYwAB8(};I*RwS{SsdSXX&a^qf`Gj~k=P9ep`&r*H3Ai)O>D-3~{9toharU=s zJV~w&ol9=ZS#o+udQsMI=WmGX+8lo+ z;K`WJz&fr`v4iqDd_i234hbkH#zveUshdJR2c2thz9-g87KH7v25pXFO5&=VBkB9; z=w;FNZr2}jZHa=aO(KpR9Sy4(D5DZ%F_1$ z<*}U4h#RYdql#|iC??oT$C6ZJq+us2`Vu#!oPe`3`CY_6lK-0WCM-gJJ-Hyxhm^B3 zMlSMv&^r5(pCjHQJOAWosB@k+U*J!i{L#dTp?!C9xAcnpQ5Zq;2Ip1EojC_E@Ru}t z$N+tbcN6Ol$rPP;RBg)_J1^*=Fj9#g?DJLeJ3 zunG;V<1Tr9Qr5!h)Wy-})6w9wwkIv$8FO;#FE=7St50U-Gvr4ymX4pvdAiVeu2oJl zPnd^kc$0Gojhk3*De(s492$W`f0}wtt|Il*FaY@};dJEG@h$#h<=Z%!`ao>Y?sDk* z`92}INU}8NGU7g{V<7o{EN~R(EaKjr)yRdpot?JC&uRM|^-mc14L&B$hkxUK&i=Gn z!C98v8O{on|3_OLgT4PRmfW)Qm}FNP>>wV**~%Ja!<0<;2ZMDdU&|(6M|_-foV6=X z{x$u3le@<`-p2jJb#k=`?$D+&XEF8D^{=Ml^`}p-l-`-fKCL|(@V98Y}`COOS{jdC9xg`bW&#GY9GQI8~l1>h8s^vjGcO zJcsJ-wA1ko?RIf?b3-Sd{7+Wb68YN$rv}dCyi8d~KfJ)0{_c40{U1n!MmEu88dRfU zL+fx@dFsPC`_Sns`BRjOkq_gX&$*Jb3U#%}|3G~U%H`=+&Bj36kcG7*KgKSqBjw*Y zbJBh`Ht`O|JuONiHI3$xs80FQk%e+F14Us^I_Njq1WB$CetxVX_m*~lk{`zT3za>{ zWx;yn?|s(3E%`dk_YHOV+fuQE#HZsSac$zPoF}Z)0}LX*%Rm{eVIFeZC?~-c*1tLq zwmxc>k#eWFa}B~g%c*(5`I2lbX93E4IDe!?c1B%>k@)#Bm!K|Z3F^wJJ>^2UpLi2( z+u>c#f61r70P1w)V`gC6q&~?M3>}A?`$6o$@Jb|10fE(0+>sw4;Mrf;J62HnDOMbiPL8 zj8wEI9%^-iDTh$b&3RT0IZ9Km$9aUB^lubYc4V{e0g%i@hRtKa!r^f4d-v#J-d_aO$W| zyB3u5P`+U8f1_P3t1m$L59+$SZ#l=3`$#Sg)?>gXl&5mO>2b zO^;Zoe{Iqs)NQx=tJKY=&(FjStX(164YsyQ^`yQB^*5|uT+-)pQj+>p?j`)rDt*Ye zCjN93wFRYOu|AZ;sfebWUngz%ksCp-gms8l8~W#xiDj_Kplg~=~zs=LfDPC zE#tiW+$^|H==FCgE7wtA%yT-Pl+~luPf1mtftE)*oj`CB+ zm`1sx53f`r5*MsfGAiP%OwpN5c@+(-*Z`im8_gm-Wf)q=C~BuuJC^e_aZ#ManSlBS z#D7yBPPsDewsEfI)Dcg90{I;H59a{NKAgokKOG;L@e1cf`gA4Pjdn@gdXKY~ilnwA zbvQ?(@-!$$t_){)%1byu9Zku7O)%LKBWZuc7NgWc>Zfz+c*D7X^O(i`Xt$U%fXUn9 z=>!>hc0TJI%S5$_A6bVyamibRwdz9iw`4jp)I@4WbMCfYW2xJ0%d3xH(q;jD(#3|FY2Nm$>y`WbwST9=#Z$ zzPjRnmdi-_6sZ`COA(jmoX<%5E3o2f&vA_U^yGCU#!NVXHc{j|*(wfVesVRbzh$}M z`YZX%1VyZKJ8Vg#R&>n8S%h+zxV|mJx(%V!*IJFC$4=s+O!LZyYHoGUi8phWqW7m` zBW-IF1k$Fe<%6w#RqAfiJ|+3q`aoI3nC zOHoc|V>Y$?Alf#gUn=4|ma9x|D&@lXx8?rBQH-&J{_8FGhJMG1&vS<9%ec8MK)*nB z9H(I}a+Me;gz{Hbf0??0l=pJRQGSV&>9Y)*<12ElILFbiJ#l~XnQ=H{T*3XefK=qu zac&_FPQb_6POB(LvTj`d)?uE+WXclxbN)-$Pe)FM9!<~yzoae_b)=>JO5)*^58@Q! zbkr55?_+WWDPJeonX(_{Kk*^$&T{79)bTs@_i4Z0{R-m86^XA&6vnA;8}C1J)1fJY z_>pT2UXiqm7I=jwAAr~ji&Ot&&s~^t54f= zK~M??X>S83#O^ehW0U8KJIOM)ka=a9 zpD>D{vf@N~9{6ndp_FHlFGpU-x0I{8MdvwgrJj@%$Si9x6LBDk4A%ZHa$RjD_bJ!p z{51i89*L)L$SN99{+W&=EGG2+o0hr^)JCIoETHZrZKhLBZtZ@j9LniST+P}|#Omad z*#fc?N5-FPTRpK)r}(tpvV_F98oD+-KH1lY@&*>poj-qMzG5W`7LNPvKn8_HBlG7g jQMf>S!h@c4@!PI94)Vzr7x=Jk{D_AELGfR|3M}@2Q2;Or diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.po b/django/conf/locale/es_AR/LC_MESSAGES/django.po index 061cd7d1ba5f..ab79df3f5038 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/django.po +++ b/django/conf/locale/es_AR/LC_MESSAGES/django.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-05-11 08:35-0300\n" -"PO-Revision-Date: 2010-05-17 10:52-0300\n" +"POT-Creation-Date: 2010-08-06 16:06-0300\n" +"PO-Revision-Date: 2010-08-06 16:05-0300\n" "Last-Translator: Ramiro \n" "Language-Team: Django-I18N \n" "Language: es_AR\n" @@ -70,7 +70,7 @@ msgid "Spanish" msgstr "español" #: conf/global_settings.py:57 -msgid "Argentinean Spanish" +msgid "Argentinian Spanish" msgstr "español de Argentina" #: conf/global_settings.py:58 @@ -166,98 +166,102 @@ msgid "Macedonian" msgstr "macedonio" #: conf/global_settings.py:81 +msgid "Malayalam" +msgstr "Malayalam" + +#: conf/global_settings.py:82 msgid "Mongolian" msgstr "mongol" -#: conf/global_settings.py:82 +#: conf/global_settings.py:83 msgid "Dutch" msgstr "holandés" -#: conf/global_settings.py:83 +#: conf/global_settings.py:84 msgid "Norwegian" msgstr "noruego" -#: conf/global_settings.py:84 +#: conf/global_settings.py:85 msgid "Norwegian Bokmal" msgstr "bokmål" -#: conf/global_settings.py:85 +#: conf/global_settings.py:86 msgid "Norwegian Nynorsk" msgstr "nynorsk" -#: conf/global_settings.py:86 +#: conf/global_settings.py:87 msgid "Polish" msgstr "polaco" -#: conf/global_settings.py:87 +#: conf/global_settings.py:88 msgid "Portuguese" msgstr "portugués" -#: conf/global_settings.py:88 +#: conf/global_settings.py:89 msgid "Brazilian Portuguese" msgstr "portugués de Brasil" -#: conf/global_settings.py:89 +#: conf/global_settings.py:90 msgid "Romanian" msgstr "rumano" -#: conf/global_settings.py:90 +#: conf/global_settings.py:91 msgid "Russian" msgstr "ruso" -#: conf/global_settings.py:91 +#: conf/global_settings.py:92 msgid "Slovak" msgstr "eslovaco" -#: conf/global_settings.py:92 +#: conf/global_settings.py:93 msgid "Slovenian" msgstr "esloveno" -#: conf/global_settings.py:93 +#: conf/global_settings.py:94 msgid "Albanian" msgstr "albanés" -#: conf/global_settings.py:94 +#: conf/global_settings.py:95 msgid "Serbian" msgstr "serbio" -#: conf/global_settings.py:95 +#: conf/global_settings.py:96 msgid "Serbian Latin" msgstr "Latín de Serbia" -#: conf/global_settings.py:96 +#: conf/global_settings.py:97 msgid "Swedish" msgstr "sueco" -#: conf/global_settings.py:97 +#: conf/global_settings.py:98 msgid "Tamil" msgstr "tamil" -#: conf/global_settings.py:98 +#: conf/global_settings.py:99 msgid "Telugu" msgstr "telugu" -#: conf/global_settings.py:99 +#: conf/global_settings.py:100 msgid "Thai" msgstr "tailandés" -#: conf/global_settings.py:100 +#: conf/global_settings.py:101 msgid "Turkish" msgstr "turco" -#: conf/global_settings.py:101 +#: conf/global_settings.py:102 msgid "Ukrainian" msgstr "ucraniano" -#: conf/global_settings.py:102 +#: conf/global_settings.py:103 msgid "Vietnamese" msgstr "vietnamita" -#: conf/global_settings.py:103 +#: conf/global_settings.py:104 msgid "Simplified Chinese" msgstr "chino simplificado" -#: conf/global_settings.py:104 +#: conf/global_settings.py:105 msgid "Traditional Chinese" msgstr "chino tradicional" @@ -309,15 +313,15 @@ msgstr "Este mes" msgid "This year" msgstr "Este año" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "Yes" msgstr "Sí" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "No" msgstr "No" -#: contrib/admin/filterspecs.py:154 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:154 forms/widgets.py:478 msgid "Unknown" msgstr "Desconocido" @@ -363,7 +367,7 @@ msgid "Changed %s." msgstr "Modifica %s." #: contrib/admin/options.py:559 contrib/admin/options.py:569 -#: contrib/comments/templates/comments/preview.html:16 db/models/base.py:844 +#: contrib/comments/templates/comments/preview.html:16 db/models/base.py:845 #: forms/models.py:568 msgid "and" msgstr "y" @@ -855,13 +859,17 @@ msgstr "Guardar y agregar otro" msgid "Save and continue editing" msgstr "Guardar y continuar editando" -#: contrib/admin/templates/admin/auth/user/add_form.html:5 +#: contrib/admin/templates/admin/auth/user/add_form.html:6 msgid "" "First, enter a username and password. Then, you'll be able to edit more user " "options." msgstr "" -"Primero, introduzca un nombre de usuario y una contraseña. Luego podrá " -"configurar opciones adicionales." +"Primero introduzca un nombre de usuario y una contraseña. Luego podrá " +"configurar opciones adicionales acerca del usuario." + +#: contrib/admin/templates/admin/auth/user/add_form.html:8 +msgid "Enter a username and password." +msgstr "Introduzca un nombre de usuario y una contraseña." #: contrib/admin/templates/admin/auth/user/change_password.html:28 #, python-format @@ -1435,8 +1443,8 @@ msgstr "mensaje" msgid "Logged out" msgstr "Sesión cerrada" -#: contrib/auth/management/commands/createsuperuser.py:23 -#: core/validators.py:120 forms/fields.py:428 +#: contrib/auth/management/commands/createsuperuser.py:24 +#: core/validators.py:120 forms/fields.py:427 msgid "Enter a valid e-mail address." msgstr "Introduzca una dirección de correo electrónico válida" @@ -1504,7 +1512,7 @@ msgid "Email address" msgstr "Dirección de correo electrónico" #: contrib/comments/forms.py:95 contrib/flatpages/admin.py:8 -#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1101 +#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1109 msgid "URL" msgstr "URL" @@ -1553,7 +1561,7 @@ msgstr "comentario" msgid "date/time submitted" msgstr "fecha/hora de envío" -#: contrib/comments/models.py:60 db/models/fields/__init__.py:896 +#: contrib/comments/models.py:60 db/models/fields/__init__.py:904 msgid "IP address" msgstr "Dirección IP" @@ -4503,20 +4511,20 @@ msgstr "sitios" msgid "Enter a valid value." msgstr "Introduzca un valor válido." -#: core/validators.py:87 forms/fields.py:529 +#: core/validators.py:87 forms/fields.py:528 msgid "Enter a valid URL." msgstr "Introduzca una URL válida." -#: core/validators.py:89 forms/fields.py:530 +#: core/validators.py:89 forms/fields.py:529 msgid "This URL appears to be a broken link." msgstr "La URL parece ser un enlace roto." -#: core/validators.py:123 forms/fields.py:873 +#: core/validators.py:123 forms/fields.py:877 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "Introduzca un 'slug' válido consistente de letras, números o guiones." -#: core/validators.py:126 forms/fields.py:866 +#: core/validators.py:126 forms/fields.py:870 msgid "Enter a valid IPv4 address." msgstr "Introduzca una dirección IPv4 válida" @@ -4528,15 +4536,15 @@ msgstr "Introduzca sólo dígitos separados por comas." #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "" -"Asegúrese de que este valor sea %(limit_value)s (actualmente es %(show_value)" -"s)." +"Asegúrese de que este valor sea %(limit_value)s (actualmente es " +"%(show_value)s)." -#: core/validators.py:153 forms/fields.py:205 forms/fields.py:257 +#: core/validators.py:153 forms/fields.py:204 forms/fields.py:256 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Asegúrese de que este valor sea menor o igual a %(limit_value)s." -#: core/validators.py:158 forms/fields.py:206 forms/fields.py:258 +#: core/validators.py:158 forms/fields.py:205 forms/fields.py:257 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Asegúrese de que este valor sea mayor o igual a %(limit_value)s." @@ -4544,8 +4552,8 @@ msgstr "Asegúrese de que este valor sea mayor o igual a %(limit_value)s." #: core/validators.py:164 #, python-format msgid "" -"Ensure this value has at least %(limit_value)d characters (it has %" -"(show_value)d)." +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." msgstr "" "Asegúrese de que este valor tenga al menos %(limit_value)d caracteres (tiene " "%(show_value)d)." @@ -4553,20 +4561,20 @@ msgstr "" #: core/validators.py:170 #, python-format msgid "" -"Ensure this value has at most %(limit_value)d characters (it has %" -"(show_value)d)." +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." msgstr "" "Asegúrese de que este valor tenga como máximo %(limit_value)d caracteres " "(tiene %(show_value)d)." -#: db/models/base.py:822 +#: db/models/base.py:823 #, python-format msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s." msgstr "" "%(field_name)s debe ser único/a para un %(lookup)s %(date_field)s " "determinado." -#: db/models/base.py:837 db/models/base.py:845 +#: db/models/base.py:838 db/models/base.py:846 #, python-format msgid "%(model_name)s with this %(field_label)s already exists." msgstr "Ya existe un/a %(model_name)s con este/a %(field_label)s." @@ -4589,13 +4597,13 @@ msgstr "Este campo no puede estar en blanco." msgid "Field of type: %(field_type)s" msgstr "Campo tipo: %(field_type)s" -#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:852 -#: db/models/fields/__init__.py:961 db/models/fields/__init__.py:972 -#: db/models/fields/__init__.py:999 +#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:860 +#: db/models/fields/__init__.py:969 db/models/fields/__init__.py:980 +#: db/models/fields/__init__.py:1007 msgid "Integer" msgstr "Entero" -#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:850 +#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:858 msgid "This value must be an integer." msgstr "Este valor debe ser un número entero." @@ -4607,7 +4615,7 @@ msgstr "Este valor debe ser True o False." msgid "Boolean (Either True or False)" msgstr "Booleano (Verdadero o Falso)" -#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:982 +#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:990 #, python-format msgid "String (up to %(max_length)s)" msgstr "Cadena (máximo %(max_length)s)" @@ -4651,44 +4659,44 @@ msgstr "Número decimal" msgid "E-mail address" msgstr "Dirección de correo electrónico" -#: db/models/fields/__init__.py:799 db/models/fields/files.py:220 +#: db/models/fields/__init__.py:807 db/models/fields/files.py:220 #: db/models/fields/files.py:331 msgid "File path" msgstr "Ruta de archivo" -#: db/models/fields/__init__.py:822 +#: db/models/fields/__init__.py:830 msgid "This value must be a float." msgstr "Este valor debe ser un valor en representación de punto flotante." -#: db/models/fields/__init__.py:824 +#: db/models/fields/__init__.py:832 msgid "Floating point number" msgstr "Número de punto flotante" -#: db/models/fields/__init__.py:883 +#: db/models/fields/__init__.py:891 msgid "Big (8 byte) integer" msgstr "Entero grande (8 bytes)" -#: db/models/fields/__init__.py:912 +#: db/models/fields/__init__.py:920 msgid "This value must be either None, True or False." msgstr "Este valor debe ser None, True o False." -#: db/models/fields/__init__.py:914 +#: db/models/fields/__init__.py:922 msgid "Boolean (Either True, False or None)" msgstr "Booleano (Verdadero, Falso o Nulo)" -#: db/models/fields/__init__.py:1005 +#: db/models/fields/__init__.py:1013 msgid "Text" msgstr "Texto" -#: db/models/fields/__init__.py:1021 +#: db/models/fields/__init__.py:1029 msgid "Time" msgstr "Hora" -#: db/models/fields/__init__.py:1025 +#: db/models/fields/__init__.py:1033 msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgstr "Introduzca un valor de hora válido en formato HH:MM[:ss[.uuuuuu]]." -#: db/models/fields/__init__.py:1109 +#: db/models/fields/__init__.py:1125 msgid "XML text" msgstr "Texto XML" @@ -4701,22 +4709,22 @@ msgstr "No existe un modelo %(model)s con una clave primaria %(pk)r." msgid "Foreign Key (type determined by related field)" msgstr "Clave foránea (el tipo está determinado por el campo relacionado)" -#: db/models/fields/related.py:918 +#: db/models/fields/related.py:919 msgid "One-to-one relationship" msgstr "Relación uno-a-uno" -#: db/models/fields/related.py:980 +#: db/models/fields/related.py:981 msgid "Many-to-many relationship" msgstr "Relación muchos-a-muchos" -#: db/models/fields/related.py:1000 +#: db/models/fields/related.py:1001 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" "Mantenga presionada \"Control\" (\"Command\" en una Mac) para seleccionar " "más de uno." -#: db/models/fields/related.py:1061 +#: db/models/fields/related.py:1062 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -4732,55 +4740,55 @@ msgstr[1] "" msgid "This field is required." msgstr "Este campo es obligatorio." -#: forms/fields.py:204 +#: forms/fields.py:203 msgid "Enter a whole number." msgstr "Introduzca un número entero." -#: forms/fields.py:235 forms/fields.py:256 +#: forms/fields.py:234 forms/fields.py:255 msgid "Enter a number." msgstr "Introduzca un número." -#: forms/fields.py:259 +#: forms/fields.py:258 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Asegúrese de que no existan en total mas de %s dígitos." -#: forms/fields.py:260 +#: forms/fields.py:259 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Asegúrese de que no existan mas de %s lugares decimales." -#: forms/fields.py:261 +#: forms/fields.py:260 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Asegúrese de que no existan mas de %s dígitos antes del punto decimal." -#: forms/fields.py:323 forms/fields.py:838 +#: forms/fields.py:322 forms/fields.py:837 msgid "Enter a valid date." msgstr "Introduzca una fecha válida." -#: forms/fields.py:351 forms/fields.py:839 +#: forms/fields.py:350 forms/fields.py:838 msgid "Enter a valid time." msgstr "Introduzca un valor de hora válido." -#: forms/fields.py:377 +#: forms/fields.py:376 msgid "Enter a valid date/time." msgstr "Introduzca un valor de fecha/hora válido." -#: forms/fields.py:435 +#: forms/fields.py:434 msgid "No file was submitted. Check the encoding type on the form." msgstr "" "No se envió un archivo. Verifique el tipo de codificación en el formulario." -#: forms/fields.py:436 +#: forms/fields.py:435 msgid "No file was submitted." msgstr "No se envió ningún archivo." -#: forms/fields.py:437 +#: forms/fields.py:436 msgid "The submitted file is empty." msgstr "El archivo enviado está vacío." -#: forms/fields.py:438 +#: forms/fields.py:437 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -4788,7 +4796,7 @@ msgstr "" "Asegúrese de que este nombre de archivo tenga como máximo %(max)d caracteres " "(tiene %(length)d)." -#: forms/fields.py:473 +#: forms/fields.py:472 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -4796,14 +4804,14 @@ msgstr "" "Seleccione una imagen válida. El archivo que ha seleccionado no es una " "imagen o es un un archivo de imagen corrupto." -#: forms/fields.py:596 forms/fields.py:671 +#: forms/fields.py:595 forms/fields.py:670 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "Seleccione una opción válida. %(value)s no es una de las opciones " "disponibles." -#: forms/fields.py:672 forms/fields.py:734 forms/models.py:1002 +#: forms/fields.py:671 forms/fields.py:733 forms/models.py:1002 msgid "Enter a list of values." msgstr "Introduzca una lista de valores." diff --git a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo index f894397efb6533f459739ce64da4ef3092e452b6..db805c50efbff295da63f63ed962e8967bd16277 100644 GIT binary patch delta 630 zcmXZZxk^Jp6oBE$b%|Tt;?gJ%h+>KhEKH>&0knNZvU$KvQg6gEeGrAJYpxkqd<@oHHR_dLBB>F-x{$LFM zP!5iNG&RpTfap41@7<)AMgeDsA3Cexzs~`RZCP8JlJi1 zdO28=ow|RG3@dYUd4;snEbc2Mu@ox>mJt<16(NnhZo%>hdAVw7OU4s{q+VavPRi*`Do%Yp?VQv5R%W#l)DZ zi3~a#5(i_AaUx-Gal*KmXk>EHgb4>DA^sjXj8E?W{@#zf|MNU|w;aEpXnpI8JT}Df zKyNMpuE1{iBj6g0vfh9ZV_GJH z+jTa&p&Cs>4on$xnF@m@o`!0C8w#9*dfy9p5x#_K{2i*#PpFC4;5^)bkKj`ts=>3| zJRfQq^PI^T8zWs?iS9x!bB|#!d;qnRXHbn7g8kQUKkFs93w{drSD+TM3c1W0LmsX} z^_jp#3!8*d@*9VV8hZ?z9l}A@b5IT5!t-zu(uDaH?Ei-P212+k!WdkJ@8Jx5LN@)1 z`u5aU;~>&vk05QXr3thejeY14!q*%_{|BGT97Wpkf#5*9JdCu6I7%Qbuov|q4eeO} zlkL#|%q07L+a2)5yVd>^O7~`pMU$De9M`((+LH9!gJFr6JGju3czI)0?SFq3k&YyWbxWt6G$T>lCJ>^Eo^k?n$3Xos;3= zkysASve*0bQ)QL_1g5y=Kx-`6^=j%=6 zEIG{EUt$l($6Tl4aBN293Z-S`vfS7%Odqtx^?Y#}cEuMQy diff --git a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po index 5f6278533a8b..ddb54e4d1743 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-05-04 22:01-0300\n" -"PO-Revision-Date: 2010-05-04 22:10-0300\n" +"POT-Creation-Date: 2010-08-06 16:09-0300\n" +"PO-Revision-Date: 2010-08-06 15:59-0300\n" "Last-Translator: Ramiro Morales \n" "Language-Team: Django-I18N \n" "Language: es_AR\n" @@ -17,45 +17,17 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Pootle 2.0.1\n" -#: contrib/admin/media/js/SelectFilter2.js:37 -#, perl-format -msgid "Available %s" -msgstr "%s disponibles" - -#: contrib/admin/media/js/SelectFilter2.js:45 -msgid "Choose all" -msgstr "Seleccionar todos" - -#: contrib/admin/media/js/SelectFilter2.js:50 -msgid "Add" -msgstr "Agregar" - -#: contrib/admin/media/js/SelectFilter2.js:52 -msgid "Remove" -msgstr "Eliminar" - -#: contrib/admin/media/js/SelectFilter2.js:57 -#, perl-format -msgid "Chosen %s" -msgstr "%s elegidos" - -#: contrib/admin/media/js/SelectFilter2.js:58 -msgid "Select your choice(s) and click " -msgstr "Seleccione los items a agregar y haga click en " - #: contrib/admin/media/js/SelectFilter2.js:63 msgid "Clear all" msgstr "Eliminar todos" #: contrib/admin/media/js/actions.js:18 -#: contrib/admin/media/js/actions.min.js:1 msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "%(sel)s de %(cnt)s seleccionado/a" msgstr[1] "%(sel)s de %(cnt)s seleccionados/as" #: contrib/admin/media/js/actions.js:109 -#: contrib/admin/media/js/actions.min.js:5 msgid "" "You have unsaved changes on individual editable fields. If you run an " "action, your unsaved changes will be lost." From 486b0dfb21ecc03558f457e680516a1a948682d3 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 02:19:29 +0000 Subject: [PATCH 045/902] [1.2.X] Fixed #14070 -- Updated Serbian translations. Thanks to janos. Backport of r13524 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13525 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/locale/sr/LC_MESSAGES/django.mo | Bin 56881 -> 57173 bytes django/conf/locale/sr/LC_MESSAGES/django.po | 231 ++--- django/conf/locale/sr/LC_MESSAGES/djangojs.mo | Bin 3011 -> 2683 bytes django/conf/locale/sr/LC_MESSAGES/djangojs.po | 53 +- django/conf/locale/sr/formats.py | 12 +- .../conf/locale/sr_Latn/LC_MESSAGES/django.mo | Bin 45424 -> 45656 bytes .../conf/locale/sr_Latn/LC_MESSAGES/django.po | 804 ++++++++---------- django/conf/locale/sr_Latn/formats.py | 12 +- 8 files changed, 506 insertions(+), 606 deletions(-) diff --git a/django/conf/locale/sr/LC_MESSAGES/django.mo b/django/conf/locale/sr/LC_MESSAGES/django.mo index 3b4121580c9aedff414b8abb32fe2c32170928cc..28995bfad7e09cbd74e6295818208fb9a68874d6 100644 GIT binary patch delta 12616 zcmZA62YgT0|HttwvO^FdF(L^;Vnoc?#2!(KTD1~l?}XCe8++BLm{p_pY!Rzg1y!pR zR5gm0pYr3U+NxHK>i_l5Ir`}1e;+-bpU=7H-gE9b=YGFmy6bT6D@Sv?ZU(q7b~xhP z9H%%&1UOEv+>TQrLbZ+)6XQ6Gayd?AEJS{)GUxC*c0->kj#C%=Vln&(YvK{~#y@QS zHx?wXS=Dh0V?5HI%jrWsP?x| z9p1O~zhD6Qb8FsO=00Jl0mq=OYf_8(FHaCpg{F2arr=am$Ip;`=J?choM5bjy09Z^ z#=6^l7;5Urpa$qd&ERa*eHNhZzXmnX?dXpgwV8jd#c?V$vdgF^y@$HNuc#?~it6Ac z>Ozk?=0<@SL|zQbVpY@t-$u=BAN&|6<8rLbtI`h7A=J5? z?_g1UjKwf-eKW8q)RR;~4X6g{Nt_=xCHbd7f0``>?*??CG zdtrOrjBnx#WUx+~hP*O38+jP#3~FE=ab^a>kSeDpY9QlKn{pPGz#|xs*RcskHOje< z%Na(XC)ijfR$MaAfZAa~uOw>}GLrw7|d<(CkJH{~2 zg|RwnY2#4??1|Cz@1zh6!*%G10nN<^ND)*Xg1T`8Y9JL*Ph8#REm7^-qZ@Wb5A1I1 z6RiVn{Rs5p{1|lcu+BsRjr0WSiB6+lqf0iwf&Ay(MEq zHNYaM%~uiimeoPcL_F%P>C=+=*GN;T@WM&h8E2sebj#M?MQ`#)w*DzzEsFqaNfgM&r*ef~o{1csD9ydsM?TR7X=$H=Kj&a4~8V ztwTTDi|RNNHDl*74_-tK;3n$2KT&(bvyE9AAJp663L(&%#Gu~m=BU?k3hIWdQ6Dht zQ3J_9J@FCLK)yn?`vLW2Kid2eY6gEp4d?}G&As0+GZKaj$mNtFPzO<{8`MN~5Qo}i z?NJ{ziKq{f38>fSL-fbps2gXZ2A++2J%2~dfNxtY5vO4@%)l6Yj>EBhJI5)9D^Q<|r?4S=X&I_!e z_rC^PSes-hYR!{T19}&=wv$m)JQp?P3$ZD#N40x^dg8~ZC-&-Sc6&H#MjD|%_C(!( zDCWl$bm=2?8i8)O3fYg&9aKj#e2i&H8lk4RHLAltwtg^bv!>X5ENY;WFc7Ds2DAcm zHY;iXnW&{W{TAzAkl+#(ESGZ^HIV9^%#@~LbMk|zsdnSTK{Hhmb%Uy?0o6k-T~pK( zcR(%SaMUgzk9v^#sF_%X>TgqL=3fo|ph5%j>0(A0g6bdwKf!2JM>kR50e4YP;@;JK zx%i=GqB^R+7OH(i)RVVB4ZNd0-wicW2`(G-L+$dxm@{=)h*| znd|yucXAg-<8{<|zqie%?QETidfh)owR2?=gcJO2E5f-;GxBz*rC5ri@dg&h4n6oP z#Su6im)Ja{r&;U9$SOKLkd5zTpmx7UFS9hAuqgQ`4AT2Qm!KpS8&Cr|joS4OkXd$q zM^?vqqqiB@MvNf;35#Jsg85vCMmF#Mq*1$MP2tf=EErUcpy zt573bhq}>rERXvz6mO$8ms?*mLq4co9%%D$>`z_+Srumy#^Pg~jOCKdKzE?-zYAUJ z@DPCqoV*d!jmCi*-<=iG#XW4=1A5{xC-3 z)&9)CJApI6ye1yj0_aI~h&2K=kn*U3RkC@UwJG{j-x9sC3wmJ!YK8`)9$*Z5<5bl7 zxdWJgHCSvLtVIoIC;H)D)LzI$Z@hvU$W7FBw^8kWMh)NvdZ6z>v*!6x=fhCfRX}wd zgWBXZT?Fc|v9$$iv$R1U?15_7AIsntj&^YJj?5Gt_`Ppc{6v`P)X9 z)0;peOhR2S5H+AN)-=?BW~16Iv3`KM;VRUOeT3?07iwVp&=)^LU4It&z;V7oUH1sR z_5S}#pw00d)zEve89*TFBe(=s!fL3F`l1Gsf|`l3sDY=UAI?P$U^!}u)}jWs+2%V? z*Jq%c-v9jsy1_xz_wpC03%*6&=mu(lcTppMf-(3M)j{MCW3;sz>blyfjvAxx*Bo{K zc=W{{=+c+X00KXpfVyxN>c$JL%TO1rvTngV6KsZhuMDNHX0p3`T)tqMEo21K5`QIW_klZ z!;a`b#wtJg#>Ju~-b)OZerOh70{OiQeR5-a<-*?QnRsON&2@+6WJ}IcFKaZM; z3#b{pX7jsPk^EOIi=pGpCu4JLLEaY|;|?sXcBrKYa=mL_mq^qTSHkhw*yhKuD*4x_ zDfdq0V;2iy4XlM4&iMc191rIzLRXe2=yRakn3E|J_23%14f{G zn)z~x!cd1@i`xDDu^Wy?4J-?f;!juvw@)(vuy6{s#(tAc$1Sl0d3Vg2A=Ch8V*$PY zD+n}&J8XjksHyn^HPQ>HC%ld&(Q}G13bl#iQF|c~b=@#j2V<}p&OqH~9Y*3#Y=haD zkN%yqQ_bdx#j?4WYV^l-EYw`i@1Ab{;?Qsgf9oMXjioSYruoG)3yYE;KrPW_)Dn5j zGBZ#QgUH)i2VgLH8oJ^LmJ`&)?`(tM+2#pius`*2H~_bxUbBEXS~$+326)dp`aQ?# zO`e-dUDp?f;(2V2wda|)WHR<3-#L%lXe9pg*=Sf6HIV7J1W#ZZc3WV6On!$|$Sb~Y z_DXMTM?MY9<2T5Q;t>#w5*_%o`#>y-_B zcnnnpqHYjs^C~v4hwMYA9qI;GQ8RHD^Wjs}bzUnRhi%{##bn%sL0F8Py9=vgF#d>K z=W<>VgiztX(!5R;P`kSkR>gj(rTG9gz#CW?|BKu4Z`9OpUu9nkG8S<8gs(}s0$;p0oFo2(KyspzGw60s2SLfA$SONJ{xmp1TT_5wfUKk z%m>spbf^9a2I4aerhmtOty$}`=uO@hHS#W~hP|*areIghMh&p)I`ibosDY+h=V1}@ zk5C^_hpZQ|Ao)Gi{a>IrZg0FgJ{(I+yZr@6m-LBSOI6EmS8WcgOjL_ z+Phc|-8Pw@Y!y*U)B`o}RIGxlt(P`2|LVwNv)OEwQBRzN+SMPTZm=EI;aSvFXQO8B zA!@+xyu8qz;TF_XmfOe2Gj2xJhwe8&xz?Z$`F#w)-%uTU9xyXg z5G(2ZuSyWci9T2cr(z-8X6rL?7Wr)~j6*&(1Db;Q$v?qBJcdv33TjEt9yEUo&PL^h z4)O0p7=fCJZkUJuokRjP7>>DdBI?GI)d15`Q@IA!(Kc+1$1oh-51St(Wv~u;OH@bG zurnS)%}~K3rhYbVB|nC)js$~`@-Nw{K;>;eGpC${{)9) zl}vto;99JJV~(58hjo}mejj7;;0bg7@d@T%QyY5H?D{R3PTuNsv(`7Q#ZQ^{xf?d( z{A`TFFHxUp`M%&^w6GLT#2c7^-A|hV97WB9`x*1e*&W{`|L_d+Uzgw!6`FzaXUz={ zqSoju)KYwdzIYG)@UhKbVKBMRIn%x*YRSr3V^P;NK|Og})Dq9YK%C_JPM2G{ck{^Dd~!#ILJC1wIo|n9UMV*d>%va4r*=Pu9~+bFP0;(jM@|3 zQ5}!QA~*pH;!+I4ZRkz^&ItmI{5*!^AE*n1zcqjJ2}7-Aw6!{F3F@MruqCR4@zy2S zl6)ts-E-7Hy|Rq~7)&05E}f`BpeKw&b==O{#o8M+GXqfVW}mYIA*#y8e#! zA*$UIR6n_{ncoq4Q1z9sG5>x9_3epPs3#qcdQHZlE_~NI4b{N{tc1(30G_s9!yxj9 zsCMq(nd|eQ?i++*SOUGV!FSBRo~Q*Cva_`xmL?yK8psMPimR~*9z=EYwXJ`MT0*bu zb`PMIraNjN^D$?}P}i?VZNj52d*W->+AEzuVmXhWRI%ycj`!K~#q|Q5__r zHt`?~!Bo`1mtzZDiJF1CsDb>1>d*Du1|By}gZ!u)1z{ms8%-`>*2_HQEP1=VUY>FUCuJTP^Le8pFvo?Vg-# zPy9W_hdh$<28GXJYpzf)Jjwjs3mMQ3(NF2$T>a?1OEhtrpJ5t}9*qgYVxgh87%ngWh zQ5R$DYSKo>3Cb8sZqDhbVe9qc>Nrd8fmJ!5rV5UGTJvF?SU}09;!S*?;!DxjjE-8w zA5(OEg*z!LDU&E`D0=fVDEjCcNYOEi`dW1KHAZ6tN;PxJ*-Kq(;)6Iam))d!srZ6Y zno1ovaTLB!DQ4@o6Hle+h^3U{raC^PRHw~{ltGlH~#x?b%fKtfW1a}O-fny zub&{_(m0lr@%V~dM`hxkl)s2SpnOVP9yg*d>O;C4r2@G=@n0X+2@X@fq5cr16lFH$ zC3P{VqpsC;oFvL7>xjG9_%U$>%Il+o9r-QJ^XtSJf%>qnO??4keSp8jx%Pry#5&4z ze!7ht60fDMgU#R7`sX3JPRYZ`_M99}IYs^}KCul6|2gJU*Mw{dMsZCC;*+)=Uko|_ zLKIB>>!S|AB}y=DI#O;EU$!U5Y5g~lys#BN*c)9TFG&25vccBro?jBzw|Nz77wSjb zxFMFM;U`#v`UG2_N?e@!u9%-XH{yrHIiGn_$3ELQnD`teH+c*VTjCn>bjm8?CY0X9 zh4Ct-FtI=F>SG~{rmjClN5h;y`6i5C{Coa)Q(2E2 zjKo!xBpPm|1W=b^FX&EQmwXZV+r&>PvxqNH>QleRwp&PjN#YQkM$yrZawaF@Q_$9h z==(~?UdkV~Qh77tYYI5Zryr>hmWuGcP?k5kU%?Qd7N+Nkrd>30$dJ$j3{TN8mk*2qH0!7Ci%J}qz z=pa{7qNCIfv1g`O)#@+e-1vaJ#-Y(dEdGt!ZLoZG{C)3OxW;#VWlJc3`>bh4oglNlGG<9 zsc%O2Y9({$8kpgV{lzCerD@@eesLAOy!#FxmXtg&VOYk37F*rCh9rzj7?LnFBewNh z;l4F1#?%O_T&s5Vs_9FHl=vsD7Mp%_$Qj@Ggkk+hC-hIM9hNbD=-1w%F=1Ipv-bSc zYu16R{aHJ+c3n*4&m&n!GrEqM8Ju2uNrm)X+X6H0%*&rQ=m7P5xadgM-mHCUb#YSG f!K?!rk5>eE6gW)9p{$)3ml?ATXS`ffz25%;fd0JE delta 12419 zcmZA73w)2||HturX0wx-9qeRmjG1i?+nAh(9CAL+vB)v!%rS@WoJEK^L=q)MInMdG zh>+xvlp=?Sk`f6M|JQrh<@fOT-;X|?pU-uluKT*~!$xQKxo_Iz?z$LQaG}FetAOJa z$AW>5Vme~ z_S2~2-9WC@3FM@nj*Fd0bb{i@g*&n6hvo7&pmXR;T@!V{#@5~#Ks^a{rP-+cm!UVV zz>c`qu6M6$w)aDwFA9rtey0pcIHsWownk6P!14Gp&cdItF!oC`Lpc%|3}+@5#C@p! z4qA_)PIL-`@dwm}-Nq7_hpt$X2qsJ$))RhcG9~^1xiKy*op-z}>+m~V> z^(yOj)Nzhs5S~ZvcN-J%el^BlLtE+v#~F?lP&<5x+*eKxhT%2Tj!#f??pfW`VW^>x zMs+YAHG-+A<5Wi-|0PsMJD@*yLrvYl>Wsg7Hl7Au={(c{-bD@RO4JEfqjubYI?z50 z#X}f}7f>Das9}b-Fs`Rg#3lG6w!#rSDqMqe7<;4Rs%7rlKBy}jhnnL#7=g<$61Sr| zb_UgfA5a~-gu2o{F$SMv6vosx$EkzrSaak)blPJ>oQ2#@j_Wi@V-oK=j?)I)U~Bvc z>7?^7vWb&g*KxQACll4N4X6<~hWzJT;SY7Bbh^1IQ?MCzUu=L|Q2RebT~L*J`S*&; zX-!g&hF++tn2l`XtVGTIRn!$d#>yB`-)z?mb+dNI5FCNJnP;J%^NpyR@jRBrKTuCg zFul=~HpZrU{#%m_qTxL(h`ukH4FXUn4o96R4Rwz+Lrp;k)X;Xr_ShS>|86XXxu_{U zhw9)>tbk8(Fjin5JUPGf9*H}yQUiX1I`9TmPq(8+CfC;Aq9^qQEP%hD2mWf?|FGV( z?Rlu4vKNA^$nU_@goILd~r^#dr+DLO2iAu_dURb{*=m z+Kn24c179-RhoTN# z5P?vW663EEXW0#AU?z38-LOzob7lUhD=UsZn1tb2 z4b_1*s447-x`3{zx$ccAxB!#z2x`0gsE#~xk$981@vLYFeNi_}IQn7&>V#>i5vhlM z*a+2ucBuUZqwa-qs3~!w?*44lRIElletS`m-$T^#Tp`WPizEWok$BXVrl2~~0JUKU z)RlFybuZM&y@r1HI%=*bphjd7szb|AFRJ%Z`>#hGe>-wdxtt>;dZB!eCGallaS3c; zb}WZFa2l%TEm4o-0MrO%+4iNV3)qGlxm@dUR7bzZ!gvYQ;oF#`=Rc2xC&YI`kL% z==py@q9JzY7T1t_;fol7+ORw7iu<9icsxeqV$_K2K!5xeb%LuHfWM*Mpn0g{1-H{1 zk00Am=UI&|&B+cD4e{rw6aIi|zl^$Df3x*%R7d~CV0?_~P*Glg`8O-718JzKsEt9` z7#U=zE2<-(qP{72+B5!*NGf$OLp>TbR3D-aum;tk9Ms(HMqTky)Er*JK>QokVGrg- zBjJxaVH9fpKvYLus19bMj=wa6@!vqQiUysiT}SiI?~2;+4UET$sFC>8wr@afzYTTe z`%oP}X4juYjnp~oPpF&xGUksQhENxEbuvARv(`WjO-s~V%|Y#W9=qZ{SOME~HtQ#1 zCF-xOPp}wu#V%&Mh8ROV!q$tiA@yO@RJaOr)Da|Yu{a*ZGI$-QphEBttek*JQ;M!nFwBZJ^_29PlN&KIbj74L4o<2_La zoPi~973#_lVmbU9HOG-X%v|Ry?sDSLp0UbtJf_=~y`GIHfRw^E-(o zC9xxV;%L+mxlnicOj|F;zSJKeqwo0i;!(qXI0;vvJC^QkP7sG0iOQ%8s&4DLsOP^4 zy0k+V67~2MOu#`Hg-cNP#CEKY-=a<&*2jEIMq)bkW~_zxP;;M5FEvHYQCB_^^^}aY zPDfAb?7ob@lBG1LBP&ro`^eVYt-H~m_Wh_UI)R1o9BPDqMqR)y)cQwQ2;KXc^}eX} zVWMhgJJf+YqBiV}>cH#hfmv7tr`h$3Q2Tv=I`L}M6s|{| zaHn-2YQKZ1d*FpefvUmfvLy_0aiGooh6oJ|<2E8%S)>Tm_PPe{jZDQ9qM{m}5 zL|xFUs0$im+g)QxbbzVWH&ItG8@+HL>dKef_I0QZZABe$530jQu>hX1^(pIjsN?^L z`BR17)VGW-=RS#gMl8Q8}MV;t8s>i>fM&dTA=l4+ua35d}Tm*G5grPcC!q%lx`^Td? zl7!m766&*D3$=X<^wRU+mP9@5irS$+>YY9ab%N#ARo1nrE8B=V(N5F>_o7a40DbTb z>SJ>eeeo`8zo)3@_eD% z;TY7EoUndpy@+~)UPm3rbENsaHbBRXPGKw3E~8AxKf?^_{i7Iv4SDEjGXfE)p(Ww%b8{-D7j}Nd6)*NG|;8oO9l8L&~H*g%zv9<3T=J$ed3pvl%nSe$wd7DLxz zlFB3(Fb#`LFjvqBu&$&3j}R z)~B9?ac)c@`s3{>yj^MkkNKL;`JGwQ_%#WGrkf$#h!v>6#RzntVWy}AYKmH+Mqmbp z;=9%y45R)E)A1LqiDln3+x0|Uz*y{yZ=t?F&OH)6UfpJDLinDedYC-Rn2X)1TfAj< z+<=3qL)oYiPQ@hr8oOa0sw17|@Ryf33>V=k%))+i&CiZE=W@Da8fv_4Zj!;+iaHw; z@E7EUcLL^_r(zbSQXfE_=wB?4?dO{j%EFS=o3S3AMs+YG+k9QSVN>cU$P??F%;s_f zNSZ7#KSFiJrPLR&5RP7GZmukhp`MHCz$VntUq{^oPprON<{IiSRQmy}j^AJ$`Ybjh zm5j})ySqqskgUf*?E8*6@f)bScM__7HtK*YP;Kj_gi8NwU$EdpJeO0 zwr+~tc8=?15*^?gY9#KVzJeYMgLVu-rrRlpnYa~0G5%df0n<_2-$gy&?%c^C7=xuT z4O6fSYD(s!I(iz5>2|w7@)-^HP(!}*J#(dNP#xNadX;{O+=tEyWU!ph%T4>=sF8Vy z9MJK6-|QEKb*L+#E@UujyNR}*gZ{eRSCT}rVk2sU6Q~pah^O$Dt@o`kFQjjZdOT_<7hBh15cNUS z3C^LW;v$yC`xu5%ADRwU#j4abFcyblXco8e2_i8gW zFQ9s!fyp@5x*qkOxQx2V3V&>_I30CUjz&%G4Aco%qeglwYUGZ5%=oLvmuS$7;So;4 zhM$wOS3YBks3)&8Z@`)B%}4Gg`mnyi z26L~qbdjiMT~T-M09#MOlGO9?CER4&AEQRXccXb_SHvFFQ*3=5HIg-WHgz*+phln< zR>DchtHQ~#x|-4bvNT-70QAc-QxJvfX=7A-TP%dVPzN4}>i8Jc6wE-~Y+JAd{*Kzu zXNy@MiM6O3VHQqBMv~wEw(<)i4O37n@~|@p)! z71g2IsP#?J9Xq40{AIg-2x=t9q0TcM>*F$v;rz}el2TY;w>e=M)QNgv1}?^t_@`~} zvxi?+sFz_oY_yjjELEfGgnh;=)V*>RQ}H2|!3z7$RJB8w?)F(EL(o5$Um9=%CSse< z?Ozh%N$NwGhKmlE^(Rn6n};iK>Onr;SoRAu*So9_P*YX)ka>#wU^?}hLyUhA$?r5U zy3SLafIAPHySmz!rUOe+BXJ4qV6`LW2Z+&FllmBH1PUEBZ^%Wcn{_Q}DmJ60;2`?q z30t2(>N2m)YcyzwC#X5~JZ20+?HG-^@&wcz_e9NQUz~yCP&cW^SLWsnLw*Qys$nD^ z#&LKB)uC688;80`iqbF<+hR6W!e7x7!@f3mZAmOaT@`hJ_IMZj;0o+~!u;fvhn&M{ z_Kmrqu#@%zP)|hy`e8cyqpJl;L6UCPS5ZCgi>+}oHpjEr085|ZpJlKgZox~aduQQk z-U;Y&#(d?5VFl{rsN>|J4>teSym~t#9dbECNz{{R7>^6F0Pe?RJb;>-f2{wZuEgW4 zd38r(0Cg2Ci4CoNP*XAub^Ik5il1N<=Aw_D|BEDgOm1R%Ec~6hC#s=N+#18N69!=> z>Oj-c3*Sd|{1c49)2RLKVPSlXno6H@#-gYx2*YH~@020Y2|8MbVH4`NP#b=S8oI02 z+Zaau(AI(9n=6b$ojB2&YORSHnFgql=!H6NCc2_YvPg7utwbFl*LnoC;Ym~nE?_L) zuN(bz~Gq;8+aDMX2*^JkR)R#St1bhgY#Y7XH!PeAQ4r z9)S5nhB`nNhTu}$z7f^Yy{L{IK@IgOTVFyC>YJ#M`V)1&VAoIP1hr9jaYNMInStv0 z2yBdFP$O^v)sds96MkpCY}emH9p^6UrY?BF+$-g<0(Cm-_(L%gU2{nEn5;!z>0T_0 zhpe|Sm^$d9xw3NTM!lG5M`(FOgD?J`U-8QbhVl$w)P{C>52L6x>;+iYf;`WS`S+$F zj?hcy1fk_qLw*lSvGEl){^hxY`I5iIHt9tCbL}sYdk~9>4MYG@ka&^pBJnP+BD5SM zVhS+-+{_LanLj+`*+|Rh*jEWBUkC}*nM4(GPugM$Ex!??X%Wj;3yi%NK z+jbV;w|NO`Ef)u>#ztS!Fcq}~($8Ffw)cWW-q{Xj^YlD5zp;J>3nEw-nPyt@{NSA-6v+H%U{cLmXm(03-)JKRf zsTUES5!b0_<5j$gTBezQ>oSG@?90kD;yy8th_ojhWjC2W|i6O4QxPEAa6^1SMq}7dj7TOBUhXFk9NHkwWN}3`J5O* zJhgSQwUAb_9H!QLr6R}Er$ozDrTtm)7Qwg2X^yiA{X(L5iIytlYY8pK@H1iwF`igP zc+tLz&^x3bp=B!VRXEW}ERQva6tl|NMB7W`+pxF(?oW%peqRtVG-^4Iuj5=I)V8f9 zpG;_}N|fcGT9y)(+2$Q$648MABchVs?`?7|QEXq>?xQ-5D68|gBEDtg6jnCIN7P!9 z$U74c$=@Nil9$I1&?ilDQA0rf%Ru-9EioSs;-JWiTnZ1vO9Dk*AmD2A8lTfdxFDv4ePzs7-uCXnCI~W%Jgke^vSa%QhN(SXTnK5MkthV`SaiFyt7 zrRUndBp++*fo#)^{LTC$@R&b8{u^n0!CvJu@*Zrsn((JB)9%obx*GL->J0L`gqGt( zb=o)QSNw`cdklFvPPE&!q&{rdSF~;6&pyvNtoYkDYMXlG=j_ID+5aVmx?ZC?PU~LW zf;EU<ygJ2H|@H8GA@9IF@%=O#MtbW6+&I%WV>kXXVJ6` zi4t^W4?bH0Y!_A6C2A;PnZZ7Xh(6^1UXhYDCWY)JFx}dgq)?`^!7$m-N^|IYCW6E|Bv@(>5_aRgx;D zMJHFPo|>8+J*b3FN>Xx4baG1dlozra4?3DteDE2soFb#9hvl4^8&o*w>9PQioN^x| G)%rgHqJl91 diff --git a/django/conf/locale/sr/LC_MESSAGES/django.po b/django/conf/locale/sr/LC_MESSAGES/django.po index 51e9ab54dd88..23c68cbfe110 100644 --- a/django/conf/locale/sr/LC_MESSAGES/django.po +++ b/django/conf/locale/sr/LC_MESSAGES/django.po @@ -4,17 +4,18 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-05-07 20:44+0200\n" -"PO-Revision-Date: 2010-03-23 23:39+0100\n" +"POT-Creation-Date: 2010-08-06 19:53+0200\n" +"PO-Revision-Date: 2010-08-06 19:47+0100\n" "Last-Translator: Janos Guljas \n" "Language-Team: Branko Vukelic & Janos Guljas " " & Nesh & Petar \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" -"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #: conf/global_settings.py:44 msgid "Arabic" @@ -69,7 +70,7 @@ msgid "Spanish" msgstr "шпански" #: conf/global_settings.py:57 -msgid "Argentinean Spanish" +msgid "Argentinian Spanish" msgstr "аргентински шпански" #: conf/global_settings.py:58 @@ -121,138 +122,146 @@ msgid "Hungarian" msgstr "мађарски" #: conf/global_settings.py:70 +msgid "Indonesian" +msgstr "индонежански" + +#: conf/global_settings.py:71 msgid "Icelandic" msgstr "исландски" -#: conf/global_settings.py:71 +#: conf/global_settings.py:72 msgid "Italian" msgstr "италијански" -#: conf/global_settings.py:72 +#: conf/global_settings.py:73 msgid "Japanese" msgstr "јапански" -#: conf/global_settings.py:73 +#: conf/global_settings.py:74 msgid "Georgian" msgstr "грузијски" -#: conf/global_settings.py:74 +#: conf/global_settings.py:75 msgid "Khmer" msgstr "камбодијски" -#: conf/global_settings.py:75 +#: conf/global_settings.py:76 msgid "Kannada" msgstr "канада" -#: conf/global_settings.py:76 +#: conf/global_settings.py:77 msgid "Korean" msgstr "корејски" -#: conf/global_settings.py:77 +#: conf/global_settings.py:78 msgid "Lithuanian" msgstr "литвански" -#: conf/global_settings.py:78 +#: conf/global_settings.py:79 msgid "Latvian" msgstr "латвијски" -#: conf/global_settings.py:79 +#: conf/global_settings.py:80 msgid "Macedonian" msgstr "македонски" -#: conf/global_settings.py:80 +#: conf/global_settings.py:81 +msgid "Malayalam" +msgstr "малајаламски" + +#: conf/global_settings.py:82 msgid "Mongolian" msgstr "монголски" -#: conf/global_settings.py:81 +#: conf/global_settings.py:83 msgid "Dutch" msgstr "холандски" -#: conf/global_settings.py:82 +#: conf/global_settings.py:84 msgid "Norwegian" msgstr "норвешки" -#: conf/global_settings.py:83 +#: conf/global_settings.py:85 msgid "Norwegian Bokmal" msgstr "норвешки кнјжевни" -#: conf/global_settings.py:84 +#: conf/global_settings.py:86 msgid "Norwegian Nynorsk" msgstr "норвешки нови" -#: conf/global_settings.py:85 +#: conf/global_settings.py:87 msgid "Polish" msgstr "пољски" -#: conf/global_settings.py:86 +#: conf/global_settings.py:88 msgid "Portuguese" msgstr "португалски" -#: conf/global_settings.py:87 +#: conf/global_settings.py:89 msgid "Brazilian Portuguese" msgstr "бразилски португалски" -#: conf/global_settings.py:88 +#: conf/global_settings.py:90 msgid "Romanian" msgstr "румунски" -#: conf/global_settings.py:89 +#: conf/global_settings.py:91 msgid "Russian" msgstr "руски" -#: conf/global_settings.py:90 +#: conf/global_settings.py:92 msgid "Slovak" msgstr "словачки" -#: conf/global_settings.py:91 +#: conf/global_settings.py:93 msgid "Slovenian" msgstr "словеначки" -#: conf/global_settings.py:92 +#: conf/global_settings.py:94 msgid "Albanian" msgstr "албански" -#: conf/global_settings.py:93 +#: conf/global_settings.py:95 msgid "Serbian" msgstr "српски" -#: conf/global_settings.py:94 +#: conf/global_settings.py:96 msgid "Serbian Latin" msgstr "српски (латиница)" -#: conf/global_settings.py:95 +#: conf/global_settings.py:97 msgid "Swedish" msgstr "шведски" -#: conf/global_settings.py:96 +#: conf/global_settings.py:98 msgid "Tamil" msgstr "тамилски" -#: conf/global_settings.py:97 +#: conf/global_settings.py:99 msgid "Telugu" msgstr "телугу" -#: conf/global_settings.py:98 +#: conf/global_settings.py:100 msgid "Thai" msgstr "тајландски" -#: conf/global_settings.py:99 +#: conf/global_settings.py:101 msgid "Turkish" msgstr "турски" -#: conf/global_settings.py:100 +#: conf/global_settings.py:102 msgid "Ukrainian" msgstr "украјински" -#: conf/global_settings.py:101 +#: conf/global_settings.py:103 msgid "Vietnamese" msgstr "вијетнамски" -#: conf/global_settings.py:102 +#: conf/global_settings.py:104 msgid "Simplified Chinese" msgstr "новокинески" -#: conf/global_settings.py:103 +#: conf/global_settings.py:105 msgid "Traditional Chinese" msgstr "старокинески" @@ -304,15 +313,15 @@ msgstr "Овај месец" msgid "This year" msgstr "Ова година" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "Yes" msgstr "Да" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "No" msgstr "Не" -#: contrib/admin/filterspecs.py:154 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:154 forms/widgets.py:478 msgid "Unknown" msgstr "Непознато" @@ -358,7 +367,7 @@ msgid "Changed %s." msgstr "Измењена поља %s" #: contrib/admin/options.py:559 contrib/admin/options.py:569 -#: contrib/comments/templates/comments/preview.html:16 db/models/base.py:844 +#: contrib/comments/templates/comments/preview.html:16 db/models/base.py:845 #: forms/models.py:568 msgid "and" msgstr "и" @@ -457,9 +466,9 @@ msgstr[1] "%(total_count)s изабрано" msgstr[2] "%(total_count)s изабраних" #: contrib/admin/options.py:1071 -#, fuzzy, python-format +#, python-format msgid "0 of %(cnt)s selected" -msgstr "0 од %(cnt)d изабрано" +msgstr "0 од %(cnt)s изабрано" #: contrib/admin/options.py:1118 #, python-format @@ -687,7 +696,7 @@ msgid "Filter" msgstr "Филтер" #: contrib/admin/templates/admin/delete_confirmation.html:10 -#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:302 +#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:300 msgid "Delete" msgstr "Обриши" @@ -849,7 +858,7 @@ msgstr "Сачувај и додај следећи" msgid "Save and continue editing" msgstr "Сачувај и настави са изменама" -#: contrib/admin/templates/admin/auth/user/add_form.html:5 +#: contrib/admin/templates/admin/auth/user/add_form.html:6 msgid "" "First, enter a username and password. Then, you'll be able to edit more user " "options." @@ -857,6 +866,10 @@ msgstr "" "Прво унесите корисничко име и лозинку. Потом ћете моћи да мењате још " "корисничких подешавања." +#: contrib/admin/templates/admin/auth/user/add_form.html:8 +msgid "Enter a username and password." +msgstr "Унесите корисничко име и лозинку" + #: contrib/admin/templates/admin/auth/user/change_password.html:28 #, python-format msgid "Enter a new password for the user %(username)s." @@ -1050,7 +1063,7 @@ msgstr "Имејл адреса:" msgid "Reset my password" msgstr "Ресетуј моју лозинку" -#: contrib/admin/templatetags/admin_list.py:239 +#: contrib/admin/templatetags/admin_list.py:257 msgid "All dates" msgstr "Сви датуми" @@ -1424,8 +1437,8 @@ msgstr "порука" msgid "Logged out" msgstr "Одјављен" -#: contrib/auth/management/commands/createsuperuser.py:23 -#: core/validators.py:120 forms/fields.py:428 +#: contrib/auth/management/commands/createsuperuser.py:24 +#: core/validators.py:120 forms/fields.py:427 msgid "Enter a valid e-mail address." msgstr "Унесите важећу имејл адресу." @@ -1497,7 +1510,7 @@ msgid "Email address" msgstr "Имејл адреса" #: contrib/comments/forms.py:95 contrib/flatpages/admin.py:8 -#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1101 +#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1109 msgid "URL" msgstr "URL" @@ -1547,7 +1560,7 @@ msgstr "коментар" msgid "date/time submitted" msgstr "датум/време постављања" -#: contrib/comments/models.py:60 db/models/fields/__init__.py:896 +#: contrib/comments/models.py:60 db/models/fields/__init__.py:904 msgid "IP address" msgstr "IP адреса" @@ -4471,22 +4484,22 @@ msgstr "сајтови" msgid "Enter a valid value." msgstr "Унесите исправну вредност." -#: core/validators.py:87 forms/fields.py:529 +#: core/validators.py:87 forms/fields.py:528 msgid "Enter a valid URL." msgstr "Унесите исправан URL." -#: core/validators.py:89 forms/fields.py:530 +#: core/validators.py:89 forms/fields.py:529 msgid "This URL appears to be a broken link." msgstr "Овај URL изгледа не води никуда." -#: core/validators.py:123 forms/fields.py:873 +#: core/validators.py:123 forms/fields.py:877 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Унесите исрпаван „слаг“, који се састоји од слова, бројки, доњих црта или " "циртица." -#: core/validators.py:126 forms/fields.py:866 +#: core/validators.py:126 forms/fields.py:870 msgid "Enter a valid IPv4 address." msgstr "Унесите исправну IPv4 адресу." @@ -4499,12 +4512,12 @@ msgstr "Унесите само бројке раздвојене запетам msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "Ово поље мора да буде %(limit_value)s (тренутно има %(show_value)s)." -#: core/validators.py:153 forms/fields.py:205 forms/fields.py:257 +#: core/validators.py:153 forms/fields.py:204 forms/fields.py:256 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Ова вредност мора да буде мања од %(limit_value)s. или тачно толико." -#: core/validators.py:158 forms/fields.py:206 forms/fields.py:258 +#: core/validators.py:158 forms/fields.py:205 forms/fields.py:257 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Ова вредност мора бити већа од %(limit_value)s или тачно толико." @@ -4512,27 +4525,27 @@ msgstr "Ова вредност мора бити већа од %(limit_value)s #: core/validators.py:164 #, python-format msgid "" -"Ensure this value has at least %(limit_value)d characters (it has %" -"(show_value)d)." +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." msgstr "" -"Ово поље мора да садржи најмање %(limit_value)d словних места (тренутно има %" -"(show_value)d)." +"Ово поље мора да садржи најмање %(limit_value)d словних места (тренутно има " +"%(show_value)d)." #: core/validators.py:170 #, python-format msgid "" -"Ensure this value has at most %(limit_value)d characters (it has %" -"(show_value)d)." +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." msgstr "" -"Ово поље мора да садржи највише %(limit_value)d словних места (тренутно има %" -"(show_value)d)." +"Ово поље мора да садржи највише %(limit_value)d словних места (тренутно има " +"%(show_value)d)." -#: db/models/base.py:822 +#: db/models/base.py:823 #, python-format msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s." msgstr "%(field_name)s мора да буде јединствен за %(date_field)s %(lookup)s." -#: db/models/base.py:837 db/models/base.py:845 +#: db/models/base.py:838 db/models/base.py:846 #, python-format msgid "%(model_name)s with this %(field_label)s already exists." msgstr "%(model_name)s са овом вредношћу %(field_label)s већ постоји." @@ -4555,13 +4568,13 @@ msgstr "Ово поље не може да остане празно." msgid "Field of type: %(field_type)s" msgstr "Поње типа: %(field_type)s" -#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:852 -#: db/models/fields/__init__.py:961 db/models/fields/__init__.py:972 -#: db/models/fields/__init__.py:999 +#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:860 +#: db/models/fields/__init__.py:969 db/models/fields/__init__.py:980 +#: db/models/fields/__init__.py:1007 msgid "Integer" msgstr "Цео број" -#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:850 +#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:858 msgid "This value must be an integer." msgstr "Ова вредност мора бити целобројна." @@ -4573,7 +4586,7 @@ msgstr "Ова вредност мора бити True или False." msgid "Boolean (Either True or False)" msgstr "Булова вредност (True или False)" -#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:982 +#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:990 #, python-format msgid "String (up to %(max_length)s)" msgstr "Стринг (највише %(max_length)s знакова)" @@ -4615,44 +4628,44 @@ msgstr "Децимални број" msgid "E-mail address" msgstr "Имејл адреса" -#: db/models/fields/__init__.py:799 db/models/fields/files.py:220 +#: db/models/fields/__init__.py:807 db/models/fields/files.py:220 #: db/models/fields/files.py:331 msgid "File path" msgstr "Путања фајла" -#: db/models/fields/__init__.py:822 +#: db/models/fields/__init__.py:830 msgid "This value must be a float." msgstr "Ова вредност мора бити број са клизећом запетом" -#: db/models/fields/__init__.py:824 +#: db/models/fields/__init__.py:832 msgid "Floating point number" msgstr "Број са покреном запетом" -#: db/models/fields/__init__.py:883 +#: db/models/fields/__init__.py:891 msgid "Big (8 byte) integer" msgstr "Велики цео број" -#: db/models/fields/__init__.py:912 +#: db/models/fields/__init__.py:920 msgid "This value must be either None, True or False." msgstr "Ова вредност мора бити или None, или True, или False." -#: db/models/fields/__init__.py:914 +#: db/models/fields/__init__.py:922 msgid "Boolean (Either True, False or None)" msgstr "Булова вредност (True, False или None)" -#: db/models/fields/__init__.py:1005 +#: db/models/fields/__init__.py:1013 msgid "Text" msgstr "Текст" -#: db/models/fields/__init__.py:1021 +#: db/models/fields/__init__.py:1029 msgid "Time" msgstr "Време" -#: db/models/fields/__init__.py:1025 +#: db/models/fields/__init__.py:1033 msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgstr "Унесите исправно време у формату ЧЧ:ММ[:сс[.уууууу]]." -#: db/models/fields/__init__.py:1109 +#: db/models/fields/__init__.py:1125 msgid "XML text" msgstr "XML текст" @@ -4665,22 +4678,22 @@ msgstr "Објекат класе %(model)s са примарним кључем msgid "Foreign Key (type determined by related field)" msgstr "Страни кључ (тип одређује референтно поље)" -#: db/models/fields/related.py:918 +#: db/models/fields/related.py:919 msgid "One-to-one relationship" msgstr "Релација један на један" -#: db/models/fields/related.py:980 +#: db/models/fields/related.py:981 msgid "Many-to-many relationship" msgstr "Релација више на више" -#: db/models/fields/related.py:1000 +#: db/models/fields/related.py:1001 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" "Држите „Control“, или „Command“ на Mac-у да бисте обележили више од једне " "ставке." -#: db/models/fields/related.py:1061 +#: db/models/fields/related.py:1062 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -4693,62 +4706,62 @@ msgstr[2] "Унесите исправан %(self)s IDs. Вредности %(va msgid "This field is required." msgstr "Ово поље се мора попунити." -#: forms/fields.py:204 +#: forms/fields.py:203 msgid "Enter a whole number." msgstr "Унесите цео број." -#: forms/fields.py:235 forms/fields.py:256 +#: forms/fields.py:234 forms/fields.py:255 msgid "Enter a number." msgstr "Унесите број." -#: forms/fields.py:259 +#: forms/fields.py:258 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Не сме бити укупно више од %s цифара. Проверите." -#: forms/fields.py:260 +#: forms/fields.py:259 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Не сме бити укупно више од %s децималних места. Проверите." -#: forms/fields.py:261 +#: forms/fields.py:260 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Не сме бити укупно више од %s цифара пре запете. Проверите." -#: forms/fields.py:323 forms/fields.py:838 +#: forms/fields.py:322 forms/fields.py:837 msgid "Enter a valid date." msgstr "Унесите исправан датум." -#: forms/fields.py:351 forms/fields.py:839 +#: forms/fields.py:350 forms/fields.py:838 msgid "Enter a valid time." msgstr "Унесите исправно време" -#: forms/fields.py:377 +#: forms/fields.py:376 msgid "Enter a valid date/time." msgstr "Унесите исправан датум/време." -#: forms/fields.py:435 +#: forms/fields.py:434 msgid "No file was submitted. Check the encoding type on the form." msgstr "Фајл није пребачен. Проверите тип енкодирања формулара." -#: forms/fields.py:436 +#: forms/fields.py:435 msgid "No file was submitted." msgstr "Фајл није пребачен." -#: forms/fields.py:437 +#: forms/fields.py:436 msgid "The submitted file is empty." msgstr "Пребачен фајл је празан." -#: forms/fields.py:438 +#: forms/fields.py:437 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." msgstr "" -"Назив фајла мора да садржи бар %(max)d словних места (тренутно има %(length)" -"d)." +"Назив фајла мора да садржи бар %(max)d словних места (тренутно има " +"%(length)d)." -#: forms/fields.py:473 +#: forms/fields.py:472 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -4756,17 +4769,17 @@ msgstr "" "Пребаците исправан фајл. Фајл који је пребачен или није слика, или је " "оштећен." -#: forms/fields.py:596 forms/fields.py:671 +#: forms/fields.py:595 forms/fields.py:670 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "" "%(value)s није међу понуђеним вредностима. Одаберите једну од понуђених." -#: forms/fields.py:672 forms/fields.py:734 forms/models.py:1002 +#: forms/fields.py:671 forms/fields.py:733 forms/models.py:1002 msgid "Enter a list of values." msgstr "Унесите листу вредности." -#: forms/formsets.py:298 forms/formsets.py:300 +#: forms/formsets.py:296 forms/formsets.py:298 msgid "Order" msgstr "Редослед" @@ -5103,23 +5116,23 @@ msgstr "%(number)d %(type)s" msgid ", %(number)d %(type)s" msgstr ", %(number)d %(type)s" -#: utils/translation/trans_real.py:518 +#: utils/translation/trans_real.py:519 msgid "DATE_FORMAT" msgstr "j. F Y." -#: utils/translation/trans_real.py:519 +#: utils/translation/trans_real.py:520 msgid "DATETIME_FORMAT" msgstr "j. F Y. H:i Т" -#: utils/translation/trans_real.py:520 +#: utils/translation/trans_real.py:521 msgid "TIME_FORMAT" msgstr "G:i" -#: utils/translation/trans_real.py:541 +#: utils/translation/trans_real.py:542 msgid "YEAR_MONTH_FORMAT" msgstr "F Y." -#: utils/translation/trans_real.py:542 +#: utils/translation/trans_real.py:543 msgid "MONTH_DAY_FORMAT" msgstr "j. F" diff --git a/django/conf/locale/sr/LC_MESSAGES/djangojs.mo b/django/conf/locale/sr/LC_MESSAGES/djangojs.mo index 6de25a218c3219cdcb717b433e3e7ce9e1c611aa..b74e78cc4b8b119ac483560bc5822646050f335a 100644 GIT binary patch delta 611 zcmXZZJuE{}6u|M@=c`J6wHT=0g&>hs>Z28dK}ZA(!bD4GBH|;`#L%RR#4L|RBGNRe z#r9&5STKk%Jd4EA$x7M;i^cyuPIBJw-q(BYIq&wa`NyBmH`o zxsnI;;1bovb(D=s^y4AQ`W(j4LRsghOEHMNQr#HGFvjp0_wgHD)K`sU14;8v_;^a^ zQba=j8VUId2{}}v>JO)RF2N}(HzrNhlJ%qn-{IUa^_&|}?VY=;8(q74YfYOeqtCoC zg65Zz^3LcfJ*(%*F+F4DN=6M&q$f0>d!s}BVe_-B=$VXfCX(^Q>X5eH%Ew*ignQV$ ZaDVttwSA`TthO`SK6l0$>%H>D^$Q5%Oa1@= delta 918 zcmYk)Ur19?9Ki9j+jLX=W6oM-!68~`P?@xh9t0_b9(-v<5JIl)+D2FBwvr-hYJZ-C z8D&sGQ4#f^PH7}2QBOUc`w~6$=c$*1DDZ6|eSfzR9rin)bI(2JcmCW}n%{&ApPT$Q z6=icZeq)6A=%bOPGu9i_nEgb`-T&Uau4qu7XtaW$U67EI$>yoNIFR`vNA%7zzEZs;w_6}u>T zPqQmAhb^qHF42&Ilh}-pu>qx%1+tQ&_CX=^y+;P;w6s;lY z@3k&RkJy8m5nIJ{uRf}e6FoYnVzy%^&gu)9b2*(zWzq?|HQ%P=LrI-*(up&=^uao0 z)kk->cj}Jz1AF%sx7YkCZLRI`ZJ98y%&eJmZ!1I79dpOsD`wFwn6fN55a`M@*&xVKQrI{xkUBw&0@SeN8Ui1ddn6f)==CmoB7iOAeMrmqq Pc$u0B-s{q%;CtU6h32_L diff --git a/django/conf/locale/sr/LC_MESSAGES/djangojs.po b/django/conf/locale/sr/LC_MESSAGES/djangojs.po index 56a550b3759d..aecb4267dddd 100644 --- a/django/conf/locale/sr/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/sr/LC_MESSAGES/djangojs.po @@ -4,50 +4,24 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-05-07 20:46+0200\n" +"POT-Creation-Date: 2010-08-06 19:53+0200\n" "PO-Revision-Date: 2009-03-30 14:04+0200\n" "Last-Translator: Janos Guljas \n" "Language-Team: Branko Vukelic & Janos Guljas " " & Nesh & Petar \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" -"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" - -#: contrib/admin/media/js/SelectFilter2.js:37 -#, perl-format -msgid "Available %s" -msgstr "Доступни %s" - -#: contrib/admin/media/js/SelectFilter2.js:45 -msgid "Choose all" -msgstr "Додај све" - -#: contrib/admin/media/js/SelectFilter2.js:50 -msgid "Add" -msgstr "Додај" - -#: contrib/admin/media/js/SelectFilter2.js:52 -msgid "Remove" -msgstr "Уклони" - -#: contrib/admin/media/js/SelectFilter2.js:57 -#, perl-format -msgid "Chosen %s" -msgstr "Одабрани %s" - -#: contrib/admin/media/js/SelectFilter2.js:58 -msgid "Select your choice(s) and click " -msgstr "Направите избор и кликните " +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #: contrib/admin/media/js/SelectFilter2.js:63 msgid "Clear all" msgstr "Врати све" #: contrib/admin/media/js/actions.js:18 -#: contrib/admin/media/js/actions.min.js:1 msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "%(sel)s од %(cnt)s изабран" @@ -55,7 +29,6 @@ msgstr[1] "%(sel)s од %(cnt)s изабрано" msgstr[2] "%(sel)s од %(cnt)s изабраних" #: contrib/admin/media/js/actions.js:109 -#: contrib/admin/media/js/actions.min.js:5 msgid "" "You have unsaved changes on individual editable fields. If you run an " "action, your unsaved changes will be lost." @@ -151,3 +124,21 @@ msgstr "Јуче" #: contrib/admin/media/js/admin/DateTimeShortcuts.js:184 msgid "Tomorrow" msgstr "Сутра" + +#~ msgid "Available %s" +#~ msgstr "Доступни %s" + +#~ msgid "Choose all" +#~ msgstr "Додај све" + +#~ msgid "Add" +#~ msgstr "Додај" + +#~ msgid "Remove" +#~ msgstr "Уклони" + +#~ msgid "Chosen %s" +#~ msgstr "Одабрани %s" + +#~ msgid "Select your choice(s) and click " +#~ msgstr "Направите избор и кликните " diff --git a/django/conf/locale/sr/formats.py b/django/conf/locale/sr/formats.py index 63a20f4574a2..cb0478ed0f59 100644 --- a/django/conf/locale/sr/formats.py +++ b/django/conf/locale/sr/formats.py @@ -11,9 +11,9 @@ SHORT_DATETIME_FORMAT = 'j.m.Y. H:i' FIRST_DAY_OF_WEEK = 1 DATE_INPUT_FORMATS = ( - '%Y-%m-%d', # '2006-10-25' '%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.' '%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.' + '%Y-%m-%d', # '2006-10-25' # '%d. %b %y.', '%d. %B %y.', # '25. Oct 06.', '25. October 06.' # '%d. %b \'%y.', '%d. %B \'%y.', # '25. Oct '06.', '25. October '06.' # '%d. %b %Y.', '%d. %B %Y.', # '25. Oct 2006.', '25. October 2006.' @@ -23,9 +23,6 @@ '%H:%M', # '14:30' ) DATETIME_INPUT_FORMATS = ( - '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' - '%Y-%m-%d %H:%M', # '2006-10-25 14:30' - '%Y-%m-%d', # '2006-10-25' '%d.%m.%Y. %H:%M:%S', # '25.10.2006. 14:30:59' '%d.%m.%Y. %H:%M', # '25.10.2006. 14:30' '%d.%m.%Y.', # '25.10.2006.' @@ -38,7 +35,10 @@ '%d. %m. %y. %H:%M:%S', # '25. 10. 06. 14:30:59' '%d. %m. %y. %H:%M', # '25. 10. 06. 14:30' '%d. %m. %y.', # '25. 10. 06.' + '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' + '%Y-%m-%d %H:%M', # '2006-10-25 14:30' + '%Y-%m-%d', # '2006-10-25' ) -DECIMAL_SEPARATOR = '.' -THOUSAND_SEPARATOR = ',' +DECIMAL_SEPARATOR = ',' +THOUSAND_SEPARATOR = '.' NUMBER_GROUPING = 3 diff --git a/django/conf/locale/sr_Latn/LC_MESSAGES/django.mo b/django/conf/locale/sr_Latn/LC_MESSAGES/django.mo index ad6193b38b3eda3c716b4b3bcf409c6a56949c81..ad04e754de45bb584a10fd148a7d3333fdedd7b0 100644 GIT binary patch delta 12584 zcmZA62YeO9+Q;!Fq?3deN~i}2A++QmfdC1R&_Q}H0Zd3j4@uwn2u#}6E?sj7>M_5 z`7su!+@P-Gl*IN(e;#KLNj)m2V>LWry@b^%`^Py>0J^OWtVvju`ZlNm^+gSEo^>7e zqMV1CSRp#m`DoOAo1hQW1sBZ(|Y4U!!Jx z9d-R348nWZ8=d;*d>>T%Gz_4BC!0hwnT!#bi+;ErHL%?{75CvHd@9~?Lh&6ej-Mcl z<$R027|#0Zx(KTaHL+?Kjc97(cP)bEx*0 zQ5{~l^|!GYsmBq{cDo6r$S3R0dsIRs^dSAedYu;a-7nbfV!|d zYQ<7)ISaM)<52_jpjL1p>OMQie0ro?!>>%8OGjR>p;Z^C3Cy}{1J&aIF7_(EzPr^jygXF)$tNkN4rpaB_H)DPNJ6hH1@!6&==jT zb4jd+dbI6P0~~-c^zY=5WZ@R{$6~F`2P6VjE|0o#Bx)eFP&2M)%WYBZx}p#EK|f5f z^=a0jwth4QaDF^`n5;96L?eA4HKP-#*XXn@Uqt@r{J=ljU@ZU8GoFA`aXzZOe_M0o zqNo8zpf+D^)LWK-T8Z|kw`NdV)?Xu?Oho|Bz+SijHK5D3{u%~SzGdt0qxQrj?1oL+ znfA+3_jv(z-wmjh*@iCMWzT<#`W~EZ$NC48T&6+|Z=ybpcTqRehS2#y%*NuVcJoj( zTY{R|OQ?3cF$mwp2t0}!$OSBjS5OnVhB5eyhomk^1>TL?*cH`qI;x}Ds2eUqb+`(( ziMC)N+>h!wAGKniUjW>sDXTjYWFQ_X5ZWLEz}C$MGfd7>X`?2G%Mml2IO&~NYp`f)D0S-I!H!s zvaYBPO&aP$G8Oguyo6!+2I|K7sDYnHy`I0LRv@^OsgFcWAPKc{?Y#2*dy;6RX&8#b zP$QmzwQ&x%!aQ{21001lJ3CGdd=d3!JdR1|*Trn&Ryd3DdgPQ-na}Dx%tn4joQGIn z?|*%^ur|p^)HBaU4QLYT+0H~QaV~1fmt#xZifVTQHRC&|83%MXyS)->MVet44nW<1 zBo@IO^yrg1heS7AkL*Y1DykzlpD{g>W~e288r9(-TR$ANS#xZ80&1W$FdXNi2J|9& zH!ErY`KU*6q6g2vILT=$cwEjk)IjR>G)p=eTT?!ST52CY4qB<&s2kKp4X82d(X~X) zxEtybjzaD7DX57&hgyl%sQ$M1V*S1O z$E6TzCF-H-8=~4Lp=RC&HSq5Cd|%W`rFv|ViQ47E(Yth5g7S-~f$g;(Lv6P6s7F=0 zuemNAQz&~d1}~t_7wTs=Z7=J5)a(8hs-5Q)NhOlUwxSYuX+^m+>QTIa<+u)L!sO(yBYz{1ka=&PCK_jUU7W>ECHd zqRp@#HL@+J8|}iHcmONn71ZYPNjEDLgxcldwpSX)lEw!|>%+hQR0#sEx3tC$L!u6wTic*EO9u?X{-}n7F$%|`+OI)%v>vrmTT$(HVh|p%<@Zn>pR|5% zJ!8*%&XEMs;0kI+H&G-0)7JY9GdCz~EsmN&DGbDlsF_FG`gqiUnxgL43N_$v=!3m& zxu4PFJVT-pW}q$@iW<;(>vYtB7NXicZ(WPJ;d<1HZ9;Xl7d5a07>w_tuKyVM;5c8Q zuDgYSdjEeV(dKx7Y8W`&3?Ll!39f*#7>DX89W{^~)JjZ14SYHl!d%n<)}S8IX4Jr5 zvE|*U>+{e@@Bcv(-QW=Fclkrq1z)3XbP+YcYp9X`hHku%>L7ZAF~%B)x~>tbqvojl zwMN~)JqBZc^ytTC2uUHFin?$C>c-2ht5FxMx4w!Yl=qi#}i?8Jds9}Dr5R}7nB9qfTx`gy3` zy%5#WGSo_~LH#;zK>fTPMlJOjbmLXjeM*cs{k29-xW9))&nOeM#KTZC9B0e#p+2=I zQ5|2$;&|KE`#x(1P#HC#Xk3kMoR1%&Cen3+`MV+m+fbf_+7q7lNp#~AsAu`P^*m~~ ze}~!|chQB9F$!H1%?w&&3Cf*O9c9}3iKq$9MBO*nmR~^iw;4<6_y1KAZNhxi%)UZ( zd;@jE2dJ5aPBK3-B~bN!kp1S2Kz)GDV^#bST^KgmbR3I%6b(^J-p<<1d!FZ?LZS{b zQLkB+El)v>bT%?lXAx?ETdW6BD{~Un?i^}KFQGd64mGfwsQW)e-KXFbv&YI}klz2U zB-$h?_%`O^W^6mv{ONQP+f!~l&HN>_5Vho2uq^(88hAK2i@?gLJ(Gx9`o0)~>DDo* z_S4W4NwS!v67IDP&Y))SEhgY~OvI|w%`Q&E&XiZ;K>Xg?a)$YvaT_+H{wHjOF*D7- z9UF(GDX+sMJTQ~zuaVrRA`bm#nSnIIUX)X?1Mb5n=rh~QEE%-|1F;s4LoMxQY>kId zE8sK7{B7vMc*;$&BtDB}aLF8IrVqobR8+&aQ8)Y!wJD3tHGip8#b%T{qE==ZcEhze z3U8q=4wz@2^&nIS!?6O+MNM=Y`eUB;kcT9lihK;fsQG61#-K*p3AJRI7=s=hjC)Ye zw&ViSu?zhuS4Gv=M0HdTD`KLpPr*RSnfAPAlugE?ZZH*taF#9S+VToap?)3e1`lj~ z!G)%MsI?S!;d~`*f!U~;zloaYaa*pr$a^1;(~v~3RWsCk-xIa;nOF`dV-;MD8sK3p zghw$IKSM3Od$FA{YJiDY5L@EY*cQ9uDqH^}`m_I>n24V1Xro(Uyp`Q@j8)Nhsac^KsO#&X2GSJuXxm~K_D2nP1Zt(nEM@&wF^LK-sRwm~ zd8idxWgG5B4d?*sZFmdS{xs^h;2Ts2KclXDg!(duFEfwGjT%T2YEN}XogcG|_17me zmkP~vJL-Ksh8p2jEQ&v)I(&?pS-^5L(4y9g7)yO^?1=r*jk|CxUPNu;&MV9ZD#v=* zLz2k}-y3+KjVM16pWZj(WChZTV#^LU|AB!}YGM z_j$prSTJgUp7JE|Bpq$VT-1dJQ5~GHNQA_*-mc+a0ecfI% z_2IaHdKYToJ1`paP%C)>tKn7DN`$PVUhjWV5?v61dM_*6aujN*Vo@`xk9yA&QLkNV z)Bs1KCNKlFLMyQ%9>h9$4qIW+dQ;ySpP@V&^04d}vcSP+k+`uSKFb|5*6 z>ZrZ)}=lI^@uv41~d${X-A=6>&Y01i;)+@ z@vI_AA#uNIzF@gHmhutQ4ct4-4C5k6Tc`5j!vh-$V`Q80y3GDHg!*u?+rz z8lcZ^V*qMJLNHA4e`yjeNj20Gxvj~l8FsbjQ!tcr2C7{)>ev_pnI(QQ0)()M<-5@Xazn)jrg4Pg7q@$lX(qw!#_|1(7zg2eE@1jB2d@Yu*RZy zpwl{=Ei%`#YDQZb~S`VO>HXqgQGt~8G zQ1?BLTH%Yf{`PCEzh-og3i-Fy|8?_7!cYT=!Z56kTDk_Pj#}CJZm7SI(y%H{Ks}oE zsDT_tO*9{M{R!(=9(&>{YNS7*F8CF_1GDviqh{c{*ZkZDqB<;(YF`)i2peEwOhyg7 z3+ip^j=FvUY9P7jUD=g3S!)|?MBQjB>RIhZ%{(8qyHBAyxNiLu^_B#^VFpqhwV6v} zIJ!|Q)e_ZVSJdmdhUlU9U&r6n=$-t33+W$Jykvxcak;o&$i9n(ijVt1PeA(XRQ>@C( z|AXHWvBWh($1&OtL0`_lfU%UHCG>|$Fm=&{j$4}l6i)0VPLaQWI^M;F_%#t}>(1jA zn^(3bbEAf|`JD1x)KQYUUX**1Hzp1dttn^YlVb^GefG`zlPsb_$87w@<|?!AoB&(a zZ@MnHL)ni`9^-$A->Iu$+mL#X?Y4Z5(q!^|L}5GYNW5jvd%Pcx?`&gTS)Y?fDStwI zOnEi2hqyucIs5^CMji7E|NL_}=Msnq#44hq?Qnu^G1I!0bLm7KP3L`*e;=(W=#_eM z=-+HMAbzs#UcnbRxdC_J@3!6t^=5?PSzFfui`iV;KFAK}B6&RV(8WK>5H3!%;o?%{ zIxZNT&sL0yD(;y^DVj6&Xf+Mn5KocoZ>49*1IcUHEA&@DQ=$NMZd=!YHagxX#uEiO zr=z~D*Ox}e$CUMw*5&+kRdD>EXP(80rNntEy5KS*m}u*5!WJaoL+JPnUnkZPGl-3Z zz9@M_Br%lGv4Hx9bo3?0U{gY$DjoZ&dm0bn&;oXo>cjaVQI$#^mvAgDBg)#kUF5R~ z9q~jBZmQ!Yq8@EtB8CwyDQ_p@>~+h@byT8#QG1Qb4Tx&$zYFm-jpI4l9{;ASqYn81 z;xBUjLG>1SO?(-Hu{zP0s6|;H>?cP(lEcJT)W1zsAr=yksB@!^M62g0h3dAjg}k@T z?~vCbo*cF8$S-sLD=J5$euf%RUz9wC{1N8b3kH(wsLA=cHcukoOkFoyo}}j=Lg4}t z!pW|j97P*^WZ(_?&S$k7I&gMy2jfSsb1?p36{bcfT)c3(6 z)cKI#B=>&vq>cl&acT0CL_x}K8n(ralvfbz$y*T5ke9@BL`m{6+BLxv7(?A)LPwJK zpZpS0f5PS;a&9kqC&H6Y;V`j@XhytB=-5O=*}Mmq;s*c!c$11S&Q-(%L^<+%SPhR5 z*~A}|-@q^Nzl4td`Um%0U4Z$IT>J_DU=f_ucX|M z{64XO{BxoS_4{nQ<siOLM+9ei?x+aao)OeES9i@D|_Vld@DF@l&%e)q}O!$n0SD(Y~9PtcdLjvt9< ziFU+F>I!kb3i&VOskoDPa%7RLBR;Sd@7a3{!QZHRh1g0|;U4+;wD-J+-*l3mL}Oxw zx6=H#vz9iet@nuF!Zfnl#AV_*F`RgtSYzAHAzwxGx8<_rPmYNs#kq%$5;`B8INFu> zd+ZS35^mS1Ojp(DL0LJ~vt8L4BQgf%WTfY%#8oa>U}&Bv{&vucoR%f?GLvfu1g4M5 z%E%s?nw7V-%?_V{5vdbXN2HF-i+{RDrQim&-Su5{8a9fnyW;r~75=Xnzv7({9|gBh z%^Ex|b#O)_SKi!_G5+c^{hvOEPD)K1o0>H$FE9IQ*op%4BJ!5ZJQWZ+G)vtbdnYw3 Yd-%}2{tK`81dXJ>VctFya_dF@4|`WTN&o-= delta 12419 zcmZA72YgT0|Htv0M0Ny`C4z(yBO#FxVy}ulVuV_0?Y*hdYm_3bmMEoa?^>0XqE)R{ zOH~K0Qd*-`TDv-o@_)T^PJR!M|9$*EeSSXYo_p^(=Z-&AFblWX{a!Uq{~*-$N?|ds@02GgfobTC9nc56;#BN~Id~QeF0o<>dV2A0ML=*E#mal_QH0jgtjEP_2yGZ~5jINsLNQT=mJ7tFQo z%P@p`oplfDJV&uOoV3Z|mYla4xnOVmKSU@-PX-MXRatiMJ!l?Kgp0qO*Apq6ws>H-^32ktuuKh*L2F&qz~ zZs{r10IypO3fc;PxV;3m|Gcc4bP2emQ>ZG94bsL!D%{(|24n{B^t zy=&Vapw8#f+}sL()Ii&!s~KgI==JG|ss|$faYplx4BUshw_X%eu{iqT0@T2kq8{39 zsMl&gY6Xs=o{>wa0X{$-=h4D^qWxR2{u)U(4cgHk{ct#{eFExPn1Y#jz_y39H2ot` zCoY3NSRP|A!R~K^djC6P0A{254Mlwk$FyYqb>V5Y!yFt#oohS#wlXsdM$If5127Rw zU@g=DI-+i27HR@LQ1`kYrr;t>#LrRv{zeVtzDwdy;=#M3B@9G8G$k+)tDr8JhFX!v z7=+DH1L%Z0ZaC^$n1Z?`F6!yeMcs-GsMqfU)a!Q-bv`$&jrov7p$1Y3HPaN-K$@aD zc0tYTXim0sFgctJ%$?S4_Fw_qXv8f6ZQT-AmI&hDz$T* zBpit&a5YxOV(rZrur4;Fo{U}beVmD*9n81=b$pTfD)MvSjN)@z6StyP5IpoZK&&PKv(zVJrXVPN2m*)Mzvo+J*~gn`UYyC|6nLSL=C7IpTGQv6*Yh~)UBwG z#W4d}WTyveAa9|5Q*L)={hN~{bumjl5w%onQ771p8c-hU-t9-t_z3DA{)!=Z2Q^@C z?u%9;7nJfU!zv)l=Up?A-{n6D~Dm!#oTUYWbxKIsHJI#x>xg22cE$m_!m~ij@`}v z>6k?QrS%bpQ&)T1^m__psmIxR2|h)A7@1X8+$!v2k2cvG$Sk$vH5p@fuqi)S?>r1F-VIg|y{og>Md%4BB z3w6Lg)W{E_PJ9XV(ENri@HXnWI?tG2$0o?#aWYX4>o#PD&VJNDLZ3AQD}g#sS**hK zodl9Hn1wz#5w%1v>M5UR>m@jVdJVGrPC#E?H5`aDa20xCxqjvX@u-zZMolQ))(uhb ze+zVVz|$leac``G!>|-CMLiRHunC?-T{yD8`7s%fji`5FJ-my$_thDtZc!W5%*UhN zlF8N=(T6&B0PC-084Vi9YShTq+j@_6KL*qOA!-$!5c8ff+h zqV`9k22=qxpeh4de?1IoH27gF)Id6+PMn46*bg;;QRt1+un5k!`xm2*TZ6jr2GlLw zj=JDp>jBhphfvSJ37142&tV0;f;yncAakKm)CxtR`o*F@CfK?r>cWkz&8#i#{x;~( z{w&mloK^ryaNbe+FRG$LoP=@?`!iaKE^YRMu{7m7y>ECBVGHU)XHX;l9kmiSP$T~vbpo#;=EOx%&q5?>V5Mzc4t0Db z)IbtZ$0wnFmg}MVw?#j_|CuBjVGq;+gHhk~VWOy-_C;R|)fsZi& zPoREme!@Wf6Ls8U)Or1e^8PCcCeZ<*)-o7GT^Y4hX;_?*HNx#4eE)}=pVdMm&0n_> zxRHLXuqpnD`fk)3#cjdPs0%N)^-9!RcYYM>A477NhC~b-&A+Q-6V!)gKBnW>s1How z7;{TnSUXs|VKMe+V`UtJ`gvV}Z{a&w6Z?!c17Cv4)LX`~{#x?0H0Y`R8FkMuqgLhy zY9@D4KdZ&~sVs^WF$rs<1e&xD&n5<}7n zE8sZP43=XUuE!AEZ`+TfW^@X5;)}MvhMLGf7=e#a0}Fr7Osop(!VOU8YllqCb)F$< zLc?IYV>|MkIUk`uJXI%|KS1hX4E3|93%`K674uO8UTNKA_rHx=x&5fO>yWL#K@IdY zM(F)NPoky&*BUh0EKLlmV*+YPQ&1PGg&J5R)CJn3W}JmJaU^Ph8&S{5+qfSu;u>5r z#r(z8Xeu8oz5feIy5i5MB~N?a{N>XIHS$4N0w-W8T!6a34h+J**2Ad&Com2#U@Ypt zWKh3~s0q}-I#?gwx+IfG^c3&G_IMS0WBN4XGHgbD9~)!C>E=(hnHW!f6eIBtHpIXg zW+1IGmAX4>g0Em3{0-}4jhU>!W;AxDnfXFYpx%U9((kZ2UPtw-@q+nVFcZ_LM`Ji{ z!6^I)^&L2i8rTig`NC(Jhp`D(q|U;|IDHl~)slQdLneNSBQSQhx#tT|_jobt0xPgI z?nN#259ouJt=F+H^`!$IlN#b17v8J`5wIz07ejCJi_kR?LM*N)ZFxPf? z6?tWyWws7~*}V5LsDW0)08B+atPN~?SJZ%e*!_L5A@vZ{Q@<4};2&6s>pLOy&50sV zBQ1-;n1Df82Mc2cYKC1<7kI|*?~hvQF{plX&=VJ<-kPPT@5&xjzb~;iUPQM%N%Sjb zX&R$W&;m7(uBb0#HU{H3)XZN%E%97iFGOGJC8*<9qE=)Zs^6!m0Ubv@{okPa|NaW= zuOEXuH0T1}ubKlxu_Sc@7QqbEK(bH|(@?vAF6zs;9yQYsQLpDY)P*0Sz6ah5%mpJ* z6DxxnXr%?LzmhZ>s$ny1i{mf}Kf%%X7wYL9xX^r%Ub3FV{KZK7d8352Lcs4roETdzPJ z_Z8{_=WTrj^#QwwdhbJ**!O=Fi5`mMs3p6E5qJ&to)&uDveR0$bB5iva)Gerp zTKdLV472V2F&Ifb1cVlT3sk{a ztcM}k8+D=4sJA8u^~}7E9dRpag&$)$2EL*5a{tScXh#Ch#agJ9_!ukV7pSGYi}Cmn zwGwg5O?xF&|76r_nQrU)r~x-eO{6XAd(jE?*7e5XdjDsUXa-ABOSBcs;8&Q8H}NTq zebclL#6Hw>FdeUAcZ^$MY8Pu$@52&!7j?nlmFDe9MNOmwx>}m&NE+cWoT84a%z-(m zj_Yv}eu*)dvDz&4AnZy#1E0n-*a>T{VKA6u-H9cszeBC)ZLEM%YgvCSNu#yqe-3Mn z?WlKQUG!XMM&1DRwD-fZxEO2TUet+hq9zi)-YoGT^rjw*dfzA6`UTW2nP=+->s|A( zETN$gJJ#6_o2@%-$2}Os{twU#e?krXBDTTns0-EEU>?q9*nzq$>I1eJwL*JQ&q~-v zV`-N}2gYLxCSx@ALk(yK>fwA5^GfP5m9}_>9eFCEB3g zicIvyKIo6`KoWHvjh;9GHR4H_fb*<-umbfj*cb!1@RN*fu_^9F-LglhFJ7Iu%sn58 zRjFS>AKZ`n;(m+_$aTIZ(TL8WK0Lpo2mXgq_!u?7sIA5_7)Tw5`prl}tw;mZ3T0Tk zp+EItyMHX|!c*=39Q4-vznny`-x|~ho zfiJPXi8}vU)UDmL-M;^aXwcFgM=kAX+wn5$1b?7Td>ggIf7y1w9cD(s)^KZS)U#9p zHIVukjE%4`wnts3*ACWSJBH8@hLf;7zKW%AH)_NuQ7ds8b%OKO8@Bx+YM@>_O@DvX zQU{~j!%-86M*Z5xpsrWLCD8?1q3&UOEP~xoBOio%ora=Luo^Xx^{5kXweGU}-$R|} zeblY`6g9!qsE7Ls>iiyW8{J|gdQDqx+K&{Gf+#_6LrDCsMm7|(TUJ@p9Z~> zf8*el*I``68WvdE#V& z_~v~MH6B+o@{`*99_N5t5+?{KBfOIz!?oTwI^zMx?iY73z) zle#mxKB?~!O{n!j$M^dy!h(H2mJ?s<#tfw$}ZBp148nVJG08qPR_C)Ds6$ zI%{oxl6(UBP9o4A6obFp{YBaTv(0r}b@m;g{+#%Xn!h)lt;A*Om+=x_MQyXqzYv*1 z{~W;1G~#b!0a4OkaDweJ-MWf>eTbyo4)Mj^r)brqTd+lOR4VZsJ+!Su{+M*$z)g7D zwm%}*yWmg#jcsdz!8U)$K5sjM)3l`!{}ACsH0{mVU)&@A_2LDi(Dp_CkfxHw(Jh&7 zldvgKl{}O79^{3{E2%qMb)r7;AMMrDz?Mp`?IU6w@z~bYt-jjHc9>dkWi`&HpAv1C zln!RcOT_myw857NA0i{a3lB2+7DC%m+)6AZrV`5ueM5ND&A;@h6Wc7>YjB~ju`<>n zQp_&rZQ5Fr@5X)}b}0H%enQ03sO=1n!dHk0+qQ*#CZSD#_E+Gf+VsawGJRerW)MxO z*Aq$hxL3)wm7;%PdyMKdqJpm9o;XS86n19deQIrqQ($t9y{5ye(Ffce42n@+w5ZR?&|99Q#kuI25C?re;N+ zNd7P8*aMy>*A~zIA8lTjd=+i&Z2g?>zaPa9gdaOQuyZ)^G4*x4Wjm7oZ_A;r3DrU@ z&oLdz588e|lFuVTX)oB)NxmXN>C>M0nfzfurUm;f8Hl6bb zdAhBuTD#Id%I0;k939`nQndH9?UTqOY0ttS+8z;C$p7~$DBoo}k0k$`cuXX4P&3;3 z7b9l@v5dSS(VIL3PZ0Wd!~pu$!(!N!I{%O+GjO47}z~ zuK!LNYui~aC(owi1|pcYG4_Bg>RQwbsk@T@NoYGpq|?4Dzv92@r#+Uu1Wvbo+EE|2 z`>WZu5(PicdF;4j8`Y;V`6=5uKKH+b2zL+Qyo5nUgXE|T?{3(O({ylbA+}F z#N^!7RU_OIWcz3xX!p2Q^}0vyg_nu$#Cc)}ZNFhZqBKK!9}Bjkc8IDQ5_OcY&Ec3& ziT>n&VHh!m{AR(|BbtWVG$e9@Bj`|T`-K=!v>;xk&6oXU$^RhlgPVzhZ5YWL#K*Sb zeS40+c#F0T#2TUu=lBqt=kIs97D-2S7A%XabI7AF4_7bnz zzAupH65VVaMP9H?Bq_oghHfBQhnB3c`QF&)l z?|XXm%lj_vmVe$ajpBUsinrM4nfFPnjKR-F{{{WmF8Ik}1 diff --git a/django/conf/locale/sr_Latn/LC_MESSAGES/django.po b/django/conf/locale/sr_Latn/LC_MESSAGES/django.po index 7947647f8ce2..7a289c3d2325 100644 --- a/django/conf/locale/sr_Latn/LC_MESSAGES/django.po +++ b/django/conf/locale/sr_Latn/LC_MESSAGES/django.po @@ -4,17 +4,15 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-05-07 20:46+0200\n" -"PO-Revision-Date: 2010-03-23 23:39+0100\n" +"POT-Creation-Date: 2010-08-06 19:43+0200\n" +"PO-Revision-Date: 2010-08-06 19:47+0100\n" "Last-Translator: Janos Guljas \n" -"Language-Team: Branko Vukelic & Janos Guljas " -" & Nesh & Petar \n" +"Language-Team: Branko Vukelic & Janos Guljas & Nesh & Petar \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" -"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #: conf/global_settings.py:44 msgid "Arabic" @@ -69,7 +67,7 @@ msgid "Spanish" msgstr "španski" #: conf/global_settings.py:57 -msgid "Argentinean Spanish" +msgid "Argentinian Spanish" msgstr "argentinski španski" #: conf/global_settings.py:58 @@ -121,138 +119,146 @@ msgid "Hungarian" msgstr "mađarski" #: conf/global_settings.py:70 +msgid "Indonesian" +msgstr "indonežanski" + +#: conf/global_settings.py:71 msgid "Icelandic" msgstr "islandski" -#: conf/global_settings.py:71 +#: conf/global_settings.py:72 msgid "Italian" msgstr "italijanski" -#: conf/global_settings.py:72 +#: conf/global_settings.py:73 msgid "Japanese" msgstr "japanski" -#: conf/global_settings.py:73 +#: conf/global_settings.py:74 msgid "Georgian" msgstr "gruzijski" -#: conf/global_settings.py:74 +#: conf/global_settings.py:75 msgid "Khmer" msgstr "kambodijski" -#: conf/global_settings.py:75 +#: conf/global_settings.py:76 msgid "Kannada" msgstr "kanada" -#: conf/global_settings.py:76 +#: conf/global_settings.py:77 msgid "Korean" msgstr "korejski" -#: conf/global_settings.py:77 +#: conf/global_settings.py:78 msgid "Lithuanian" msgstr "litvanski" -#: conf/global_settings.py:78 +#: conf/global_settings.py:79 msgid "Latvian" msgstr "latvijski" -#: conf/global_settings.py:79 +#: conf/global_settings.py:80 msgid "Macedonian" msgstr "makedonski" -#: conf/global_settings.py:80 +#: conf/global_settings.py:81 +msgid "Malayalam" +msgstr "malajalamski" + +#: conf/global_settings.py:82 msgid "Mongolian" msgstr "mongolski" -#: conf/global_settings.py:81 +#: conf/global_settings.py:83 msgid "Dutch" msgstr "holandski" -#: conf/global_settings.py:82 +#: conf/global_settings.py:84 msgid "Norwegian" msgstr "norveški" -#: conf/global_settings.py:83 +#: conf/global_settings.py:85 msgid "Norwegian Bokmal" msgstr "norveški knjževni" -#: conf/global_settings.py:84 +#: conf/global_settings.py:86 msgid "Norwegian Nynorsk" msgstr "norveški novi" -#: conf/global_settings.py:85 +#: conf/global_settings.py:87 msgid "Polish" msgstr "poljski" -#: conf/global_settings.py:86 +#: conf/global_settings.py:88 msgid "Portuguese" msgstr "portugalski" -#: conf/global_settings.py:87 +#: conf/global_settings.py:89 msgid "Brazilian Portuguese" msgstr "brazilski portugalski" -#: conf/global_settings.py:88 +#: conf/global_settings.py:90 msgid "Romanian" msgstr "rumunski" -#: conf/global_settings.py:89 +#: conf/global_settings.py:91 msgid "Russian" msgstr "ruski" -#: conf/global_settings.py:90 +#: conf/global_settings.py:92 msgid "Slovak" msgstr "slovački" -#: conf/global_settings.py:91 +#: conf/global_settings.py:93 msgid "Slovenian" msgstr "slovenački" -#: conf/global_settings.py:92 +#: conf/global_settings.py:94 msgid "Albanian" msgstr "albanski" -#: conf/global_settings.py:93 +#: conf/global_settings.py:95 msgid "Serbian" msgstr "srpski" -#: conf/global_settings.py:94 +#: conf/global_settings.py:96 msgid "Serbian Latin" msgstr "srpski (latinica)" -#: conf/global_settings.py:95 +#: conf/global_settings.py:97 msgid "Swedish" msgstr "švedski" -#: conf/global_settings.py:96 +#: conf/global_settings.py:98 msgid "Tamil" msgstr "tamilski" -#: conf/global_settings.py:97 +#: conf/global_settings.py:99 msgid "Telugu" msgstr "telugu" -#: conf/global_settings.py:98 +#: conf/global_settings.py:100 msgid "Thai" msgstr "tajlandski" -#: conf/global_settings.py:99 +#: conf/global_settings.py:101 msgid "Turkish" msgstr "turski" -#: conf/global_settings.py:100 +#: conf/global_settings.py:102 msgid "Ukrainian" msgstr "ukrajinski" -#: conf/global_settings.py:101 +#: conf/global_settings.py:103 msgid "Vietnamese" msgstr "vijetnamski" -#: conf/global_settings.py:102 +#: conf/global_settings.py:104 msgid "Simplified Chinese" msgstr "novokineski" -#: conf/global_settings.py:103 +#: conf/global_settings.py:105 msgid "Traditional Chinese" msgstr "starokineski" @@ -261,7 +267,8 @@ msgstr "starokineski" msgid "Successfully deleted %(count)d %(items)s." msgstr "Uspešno obrisano: %(count)d %(items)s." -#: contrib/admin/actions.py:55 contrib/admin/options.py:1125 +#: contrib/admin/actions.py:55 +#: contrib/admin/options.py:1125 msgid "Are you sure?" msgstr "Da li ste sigurni?" @@ -279,8 +286,10 @@ msgstr "" "

      %s:

      \n" "
        \n" -#: contrib/admin/filterspecs.py:75 contrib/admin/filterspecs.py:92 -#: contrib/admin/filterspecs.py:147 contrib/admin/filterspecs.py:173 +#: contrib/admin/filterspecs.py:75 +#: contrib/admin/filterspecs.py:92 +#: contrib/admin/filterspecs.py:147 +#: contrib/admin/filterspecs.py:173 msgid "All" msgstr "Svi" @@ -304,15 +313,18 @@ msgstr "Ovaj mesec" msgid "This year" msgstr "Ova godina" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 +#: forms/widgets.py:478 msgid "Yes" msgstr "Da" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 +#: forms/widgets.py:478 msgid "No" msgstr "Ne" -#: contrib/admin/filterspecs.py:154 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:154 +#: forms/widgets.py:478 msgid "Unknown" msgstr "Nepoznato" @@ -348,7 +360,8 @@ msgstr "zapis u logovima" msgid "log entries" msgstr "zapisi u logovima" -#: contrib/admin/options.py:138 contrib/admin/options.py:153 +#: contrib/admin/options.py:138 +#: contrib/admin/options.py:153 msgid "None" msgstr "Ništa" @@ -357,8 +370,10 @@ msgstr "Ništa" msgid "Changed %s." msgstr "Izmenjena polja %s" -#: contrib/admin/options.py:559 contrib/admin/options.py:569 -#: contrib/comments/templates/comments/preview.html:16 db/models/base.py:844 +#: contrib/admin/options.py:559 +#: contrib/admin/options.py:569 +#: contrib/comments/templates/comments/preview.html:16 +#: db/models/base.py:845 #: forms/models.py:568 msgid "and" msgstr "i" @@ -387,11 +402,13 @@ msgstr "Bez izmena u poljima." msgid "The %(name)s \"%(obj)s\" was added successfully." msgstr "Objekat „%(obj)s“ klase %(name)s sačuvan je uspešno." -#: contrib/admin/options.py:647 contrib/admin/options.py:680 +#: contrib/admin/options.py:647 +#: contrib/admin/options.py:680 msgid "You may edit it again below." msgstr "Dole možete ponovo da unosite izmene." -#: contrib/admin/options.py:657 contrib/admin/options.py:690 +#: contrib/admin/options.py:657 +#: contrib/admin/options.py:690 #, python-format msgid "You may add another %s below." msgstr "Dole možete da dodate novi objekat klase %s" @@ -403,19 +420,13 @@ msgstr "Objekat „%(obj)s“ klase %(name)s izmenjen je uspešno." #: contrib/admin/options.py:686 #, python-format -msgid "" -"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." -msgstr "" -"Objekat „%(obj)s“ klase %(name)s dodat je uspešno. Dole možete uneti dodatne " -"izmene." +msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "Objekat „%(obj)s“ klase %(name)s dodat je uspešno. Dole možete uneti dodatne izmene." -#: contrib/admin/options.py:740 contrib/admin/options.py:997 -msgid "" -"Items must be selected in order to perform actions on them. No items have " -"been changed." -msgstr "" -"Potrebno je izabrati objekte da bi se izvršila akcija nad njima. Nijedan " -"objekat nije promenjen." +#: contrib/admin/options.py:740 +#: contrib/admin/options.py:997 +msgid "Items must be selected in order to perform actions on them. No items have been changed." +msgstr "Potrebno je izabrati objekte da bi se izvršila akcija nad njima. Nijedan objekat nije promenjen." #: contrib/admin/options.py:759 msgid "No action selected." @@ -426,7 +437,8 @@ msgstr "Nije izabrana nijedna akcija." msgid "Add %s" msgstr "Dodaj objekat klase %s" -#: contrib/admin/options.py:866 contrib/admin/options.py:1105 +#: contrib/admin/options.py:866 +#: contrib/admin/options.py:1105 #, python-format msgid "%(name)s object with primary key %(key)r does not exist." msgstr "Objekat klase %(name)s sa primarnim ključem %(key)r ne postoji." @@ -457,9 +469,9 @@ msgstr[1] "%(total_count)s izabrano" msgstr[2] "%(total_count)s izabranih" #: contrib/admin/options.py:1071 -#, fuzzy, python-format +#, python-format msgid "0 of %(cnt)s selected" -msgstr "0 od %(cnt)d izabrano" +msgstr "0 od %(cnt)s izabrano" #: contrib/admin/options.py:1118 #, python-format @@ -471,33 +483,30 @@ msgstr "Objekat „%(obj)s“ klase %(name)s uspešno je obrisan." msgid "Change history: %s" msgstr "Istorijat izmena: %s" -#: contrib/admin/sites.py:18 contrib/admin/views/decorators.py:14 +#: contrib/admin/sites.py:18 +#: contrib/admin/views/decorators.py:14 #: contrib/auth/forms.py:81 -msgid "" -"Please enter a correct username and password. Note that both fields are case-" -"sensitive." -msgstr "" -"Unesite tačno korisničko ime i lozinku. Pazite na razliku između malih i " -"velikih slova u oba polja." +msgid "Please enter a correct username and password. Note that both fields are case-sensitive." +msgstr "Unesite tačno korisničko ime i lozinku. Pazite na razliku između malih i velikih slova u oba polja." -#: contrib/admin/sites.py:307 contrib/admin/views/decorators.py:40 +#: contrib/admin/sites.py:307 +#: contrib/admin/views/decorators.py:40 msgid "Please log in again, because your session has expired." msgstr "Prijavite se ponovo pošto je vaša sesija istekla." -#: contrib/admin/sites.py:314 contrib/admin/views/decorators.py:47 -msgid "" -"Looks like your browser isn't configured to accept cookies. Please enable " -"cookies, reload this page, and try again." -msgstr "" -"Izgleda da vaš brauzer nije podešen da prima kolačiće. Uključite kolačiće, " -"osvežite ovu stranicu i probajte ponovo." +#: contrib/admin/sites.py:314 +#: contrib/admin/views/decorators.py:47 +msgid "Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again." +msgstr "Izgleda da vaš brauzer nije podešen da prima kolačiće. Uključite kolačiće, osvežite ovu stranicu i probajte ponovo." -#: contrib/admin/sites.py:330 contrib/admin/sites.py:336 +#: contrib/admin/sites.py:330 +#: contrib/admin/sites.py:336 #: contrib/admin/views/decorators.py:66 msgid "Usernames cannot contain the '@' character." msgstr "Korisnička imena ne smeju da sadrže znak „@“." -#: contrib/admin/sites.py:333 contrib/admin/views/decorators.py:62 +#: contrib/admin/sites.py:333 +#: contrib/admin/views/decorators.py:62 #, python-format msgid "Your e-mail address is not your username. Try '%s' instead." msgstr "Vaša imejl adresa nije vaše korisničko ime. Probajte sa „%s“." @@ -506,7 +515,8 @@ msgstr "Vaša imejl adresa nije vaše korisničko ime. Probajte sa „%s“." msgid "Site administration" msgstr "Administracija sistema" -#: contrib/admin/sites.py:403 contrib/admin/templates/admin/login.html:26 +#: contrib/admin/sites.py:403 +#: contrib/admin/templates/admin/login.html:26 #: contrib/admin/templates/registration/password_reset_complete.html:14 #: contrib/admin/views/decorators.py:20 msgid "Log in" @@ -584,12 +594,8 @@ msgid "Server Error (500)" msgstr "Greška na serveru (500)" #: contrib/admin/templates/admin/500.html:10 -msgid "" -"There's been an error. It's been reported to the site administrators via e-" -"mail and should be fixed shortly. Thanks for your patience." -msgstr "" -"Došlo je do greške. Administrator sajta je obavešten imejlom i greška će " -"biti uskoro otklonjena. Hvala na strpljenju." +msgid "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." +msgstr "Došlo je do greške. Administrator sajta je obavešten imejlom i greška će biti uskoro otklonjena. Hvala na strpljenju." #: contrib/admin/templates/admin/actions.html:4 msgid "Run the selected action" @@ -687,29 +693,20 @@ msgid "Filter" msgstr "Filter" #: contrib/admin/templates/admin/delete_confirmation.html:10 -#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:302 +#: contrib/admin/templates/admin/submit_line.html:4 +#: forms/formsets.py:300 msgid "Delete" msgstr "Obriši" #: contrib/admin/templates/admin/delete_confirmation.html:16 #, python-format -msgid "" -"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " -"related objects, but your account doesn't have permission to delete the " -"following types of objects:" -msgstr "" -"Uklanjanje %(object_name)s „%(escaped_object)s“ povlači uklanjanje svih " -"objekata koji su povezani sa ovim objektom, ali vaš nalog nema dozvole za " -"brisanje sledećih tipova objekata:" +msgid "Deleting the %(object_name)s '%(escaped_object)s' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" +msgstr "Uklanjanje %(object_name)s „%(escaped_object)s“ povlači uklanjanje svih objekata koji su povezani sa ovim objektom, ali vaš nalog nema dozvole za brisanje sledećih tipova objekata:" #: contrib/admin/templates/admin/delete_confirmation.html:23 #, python-format -msgid "" -"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " -"All of the following related items will be deleted:" -msgstr "" -"Da sigurni da želite da obrišete %(object_name)s „%(escaped_object)s“? " -"Sledeći objekti koji su u vezi sa ovim objektom će takođe biti obrisani:" +msgid "Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? All of the following related items will be deleted:" +msgstr "Da sigurni da želite da obrišete %(object_name)s „%(escaped_object)s“? Sledeći objekti koji su u vezi sa ovim objektom će takođe biti obrisani:" #: contrib/admin/templates/admin/delete_confirmation.html:28 #: contrib/admin/templates/admin/delete_selected_confirmation.html:33 @@ -722,23 +719,13 @@ msgstr "Brisanje više objekata" #: contrib/admin/templates/admin/delete_selected_confirmation.html:15 #, python-format -msgid "" -"Deleting the %(object_name)s would result in deleting related objects, but " -"your account doesn't have permission to delete the following types of " -"objects:" -msgstr "" -"Uklanjanje %(object_name)s povlači uklanjanje svih objekata koji su povezani " -"sa ovim objektom, ali vaš nalog nema dozvole za brisanje sledećih tipova " -"objekata:" +msgid "Deleting the %(object_name)s would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" +msgstr "Uklanjanje %(object_name)s povlači uklanjanje svih objekata koji su povezani sa ovim objektom, ali vaš nalog nema dozvole za brisanje sledećih tipova objekata:" #: contrib/admin/templates/admin/delete_selected_confirmation.html:22 #, python-format -msgid "" -"Are you sure you want to delete the selected %(object_name)s objects? All of " -"the following objects and their related items will be deleted:" -msgstr "" -"Da sigurni da želite da obrišete odabrane %(object_name)s? Sledeći objekti " -"koji su u vezi sa ovim objektom će takođe biti obrisani:" +msgid "Are you sure you want to delete the selected %(object_name)s objects? All of the following objects and their related items will be deleted:" +msgstr "Da sigurni da želite da obrišete odabrane %(object_name)s? Sledeći objekti koji su u vezi sa ovim objektom će takođe biti obrisani:" #: contrib/admin/templates/admin/filter.html:2 #, python-format @@ -775,13 +762,8 @@ msgid "Unknown content" msgstr "Nepoznat sadržaj" #: contrib/admin/templates/admin/invalid_setup.html:7 -msgid "" -"Something's wrong with your database installation. Make sure the appropriate " -"database tables have been created, and make sure the database is readable by " -"the appropriate user." -msgstr "" -"Nešto nije uredu sa vašom bazom podataka. Proverite da li postoje " -"odgovarajuće tabele i da li odgovarajući korisnik ima pristup bazi." +msgid "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." +msgstr "Nešto nije uredu sa vašom bazom podataka. Proverite da li postoje odgovarajuće tabele i da li odgovarajući korisnik ima pristup bazi." #: contrib/admin/templates/admin/login.html:19 msgid "Username:" @@ -804,12 +786,8 @@ msgid "Action" msgstr "Radnja" #: contrib/admin/templates/admin/object_history.html:38 -msgid "" -"This object doesn't have a change history. It probably wasn't added via this " -"admin site." -msgstr "" -"Ovaj objekat nema zabeležen istorijat izmena. Verovatno nije dodat kroz ovaj " -"sajt za administraciju." +msgid "This object doesn't have a change history. It probably wasn't added via this admin site." +msgstr "Ovaj objekat nema zabeležen istorijat izmena. Verovatno nije dodat kroz ovaj sajt za administraciju." #: contrib/admin/templates/admin/pagination.html:10 msgid "Show all" @@ -849,13 +827,13 @@ msgstr "Sačuvaj i dodaj sledeći" msgid "Save and continue editing" msgstr "Sačuvaj i nastavi sa izmenama" -#: contrib/admin/templates/admin/auth/user/add_form.html:5 -msgid "" -"First, enter a username and password. Then, you'll be able to edit more user " -"options." -msgstr "" -"Prvo unesite korisničko ime i lozinku. Potom ćete moći da menjate još " -"korisničkih podešavanja." +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "First, enter a username and password. Then, you'll be able to edit more user options." +msgstr "Prvo unesite korisničko ime i lozinku. Potom ćete moći da menjate još korisničkih podešavanja." + +#: contrib/admin/templates/admin/auth/user/add_form.html:8 +msgid "Enter a username and password." +msgstr "Unesite korisničko ime i lozinku" #: contrib/admin/templates/admin/auth/user/change_password.html:28 #, python-format @@ -863,7 +841,9 @@ msgid "Enter a new password for the user %(username)s." msgstr "Unesite novu lozinku za korisnika %(username)s." #: contrib/admin/templates/admin/auth/user/change_password.html:35 -#: contrib/auth/forms.py:17 contrib/auth/forms.py:61 contrib/auth/forms.py:186 +#: contrib/auth/forms.py:17 +#: contrib/auth/forms.py:61 +#: contrib/auth/forms.py:186 msgid "Password" msgstr "Lozinka" @@ -919,12 +899,8 @@ msgid "Your password was changed." msgstr "Vaša lozinka je izmenjena." #: contrib/admin/templates/registration/password_change_form.html:21 -msgid "" -"Please enter your old password, for security's sake, and then enter your new " -"password twice so we can verify you typed it in correctly." -msgstr "" -"Iz bezbednosnih razloga prvo unesite svoju staru lozinku, a novu zatim " -"unesite dva puta da bismo mogli da proverimo da li ste je pravilno uneli." +msgid "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." +msgstr "Iz bezbednosnih razloga prvo unesite svoju staru lozinku, a novu zatim unesite dva puta da bismo mogli da proverimo da li ste je pravilno uneli." #: contrib/admin/templates/registration/password_change_form.html:27 #: contrib/auth/forms.py:170 @@ -968,12 +944,8 @@ msgid "Enter new password" msgstr "Unesite novu lozinku" #: contrib/admin/templates/registration/password_reset_confirm.html:14 -msgid "" -"Please enter your new password twice so we can verify you typed it in " -"correctly." -msgstr "" -"Unesite novu lozinku dva puta kako bismo mogli da proverimo da li ste je " -"pravilno uneli." +msgid "Please enter your new password twice so we can verify you typed it in correctly." +msgstr "Unesite novu lozinku dva puta kako bismo mogli da proverimo da li ste je pravilno uneli." #: contrib/admin/templates/registration/password_reset_confirm.html:18 msgid "New password:" @@ -988,12 +960,8 @@ msgid "Password reset unsuccessful" msgstr "Resetovanje lozinke neuspešno" #: contrib/admin/templates/registration/password_reset_confirm.html:28 -msgid "" -"The password reset link was invalid, possibly because it has already been " -"used. Please request a new password reset." -msgstr "" -"Link za resetovanje lozinke nije važeći, verovatno zato što je već " -"iskorišćen. Ponovo zatražite resetovanje lozinke." +msgid "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." +msgstr "Link za resetovanje lozinke nije važeći, verovatno zato što je već iskorišćen. Ponovo zatražite resetovanje lozinke." #: contrib/admin/templates/registration/password_reset_done.html:6 #: contrib/admin/templates/registration/password_reset_done.html:10 @@ -1001,12 +969,8 @@ msgid "Password reset successful" msgstr "Resetovanje lozinke uspešno." #: contrib/admin/templates/registration/password_reset_done.html:12 -msgid "" -"We've e-mailed you instructions for setting your password to the e-mail " -"address you submitted. You should be receiving it shortly." -msgstr "" -"Poslali smo uputstva za postavljanje nove lozinke na imejl adresu koju ste " -"nam dali. Uputstva ćete dobiti uskoro." +msgid "We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly." +msgstr "Poslali smo uputstva za postavljanje nove lozinke na imejl adresu koju ste nam dali. Uputstva ćete dobiti uskoro." #: contrib/admin/templates/registration/password_reset_email.html:2 msgid "You're receiving this e-mail because you requested a password reset" @@ -1035,12 +999,8 @@ msgid "The %(site_name)s team" msgstr "Ekipa sajta %(site_name)s" #: contrib/admin/templates/registration/password_reset_form.html:12 -msgid "" -"Forgotten your password? Enter your e-mail address below, and we'll e-mail " -"instructions for setting a new one." -msgstr "" -"Zaboravili ste lozinku? Unesite svoju imejl adresu dole i poslaćemo vam " -"uputstva za postavljanje nove." +msgid "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." +msgstr "Zaboravili ste lozinku? Unesite svoju imejl adresu dole i poslaćemo vam uputstva za postavljanje nove." #: contrib/admin/templates/registration/password_reset_form.html:16 msgid "E-mail address:" @@ -1050,7 +1010,7 @@ msgstr "Imejl adresa:" msgid "Reset my password" msgstr "Resetuj moju lozinku" -#: contrib/admin/templatetags/admin_list.py:239 +#: contrib/admin/templatetags/admin_list.py:257 msgid "All dates" msgstr "Svi datumi" @@ -1064,7 +1024,8 @@ msgstr "Odaberi objekat klase %s" msgid "Select %s to change" msgstr "Odaberi objekat klase %s za izmenu" -#: contrib/admin/views/template.py:38 contrib/sites/models.py:38 +#: contrib/admin/views/template.py:38 +#: contrib/sites/models.py:38 msgid "site" msgstr "sajt" @@ -1072,17 +1033,20 @@ msgstr "sajt" msgid "template" msgstr "templejt" -#: contrib/admindocs/views.py:61 contrib/admindocs/views.py:63 +#: contrib/admindocs/views.py:61 +#: contrib/admindocs/views.py:63 #: contrib/admindocs/views.py:65 msgid "tag:" msgstr "tag:" -#: contrib/admindocs/views.py:94 contrib/admindocs/views.py:96 +#: contrib/admindocs/views.py:94 +#: contrib/admindocs/views.py:96 #: contrib/admindocs/views.py:98 msgid "filter:" msgstr "filter:" -#: contrib/admindocs/views.py:158 contrib/admindocs/views.py:160 +#: contrib/admindocs/views.py:158 +#: contrib/admindocs/views.py:160 #: contrib/admindocs/views.py:162 msgid "view:" msgstr "vju:" @@ -1102,28 +1066,34 @@ msgstr "Model %(model_name)r nije pronađen u aplikaciji %(app_label)r" msgid "the related `%(app_label)s.%(data_type)s` object" msgstr "povezani objekti klase `%(app_label)s.%(data_type)s`" -#: contrib/admindocs/views.py:209 contrib/admindocs/views.py:228 -#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:247 -#: contrib/admindocs/views.py:261 contrib/admindocs/views.py:266 +#: contrib/admindocs/views.py:209 +#: contrib/admindocs/views.py:228 +#: contrib/admindocs/views.py:233 +#: contrib/admindocs/views.py:247 +#: contrib/admindocs/views.py:261 +#: contrib/admindocs/views.py:266 msgid "model:" msgstr "model:" # WARN: possible breakage in future # This string is interpolated in strings below, which can cause breakage in # future releases. -#: contrib/admindocs/views.py:224 contrib/admindocs/views.py:256 +#: contrib/admindocs/views.py:224 +#: contrib/admindocs/views.py:256 #, python-format msgid "related `%(app_label)s.%(object_name)s` objects" msgstr "klase `%(app_label)s.%(object_name)s`" # WARN: possible breakage in future -#: contrib/admindocs/views.py:228 contrib/admindocs/views.py:261 +#: contrib/admindocs/views.py:228 +#: contrib/admindocs/views.py:261 #, python-format msgid "all %s" msgstr "svi povezani objekti %s" # WARN: possible breakage in future -#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:266 +#: contrib/admindocs/views.py:233 +#: contrib/admindocs/views.py:266 #, python-format msgid "number of %s" msgstr "broj povezanih objekata %s" @@ -1170,24 +1140,16 @@ msgid "Documentation for this page" msgstr "Dokumentacija za ovu stranicu" #: contrib/admindocs/templates/admin_doc/bookmarklets.html:19 -msgid "" -"Jumps you from any page to the documentation for the view that generates " -"that page." -msgstr "" -"Vodi od bilo koje stranice do dokumentaicje pogleda koji je generisao tu " -"stranicu." +msgid "Jumps you from any page to the documentation for the view that generates that page." +msgstr "Vodi od bilo koje stranice do dokumentaicje pogleda koji je generisao tu stranicu." #: contrib/admindocs/templates/admin_doc/bookmarklets.html:21 msgid "Show object ID" msgstr "Prikaži ID objekta" #: contrib/admindocs/templates/admin_doc/bookmarklets.html:22 -msgid "" -"Shows the content-type and unique ID for pages that represent a single " -"object." -msgstr "" -"Prikazuje content-type i jedinstveni ID za stranicu koja prestavlja jedan " -"objekat." +msgid "Shows the content-type and unique ID for pages that represent a single object." +msgstr "Prikazuje content-type i jedinstveni ID za stranicu koja prestavlja jedan objekat." #: contrib/admindocs/templates/admin_doc/bookmarklets.html:24 msgid "Edit this object (current window)" @@ -1195,8 +1157,7 @@ msgstr "Izmeni ovaj objekat (u ovom prozoru)" #: contrib/admindocs/templates/admin_doc/bookmarklets.html:25 msgid "Jumps to the admin page for pages that represent a single object." -msgstr "" -"Vodi u administracioni stranicu za stranice koje prestavljaju jedan objekat" +msgstr "Vodi u administracioni stranicu za stranice koje prestavljaju jedan objekat" #: contrib/admindocs/templates/admin_doc/bookmarklets.html:27 msgid "Edit this object (new window)" @@ -1204,8 +1165,7 @@ msgstr "Izmeni ovaj objekat (novi prozor)" #: contrib/admindocs/templates/admin_doc/bookmarklets.html:28 msgid "As above, but opens the admin page in a new window." -msgstr "" -"Isto kao prethodni, ali otvara administracionu stranicu u novom prozoru." +msgstr "Isto kao prethodni, ali otvara administracionu stranicu u novom prozoru." #: contrib/auth/admin.py:29 msgid "Personal info" @@ -1232,17 +1192,19 @@ msgstr "Lozinka uspešno izmenjena." msgid "Change password: %s" msgstr "Izmeni lozinku: %s" -#: contrib/auth/forms.py:14 contrib/auth/forms.py:48 contrib/auth/forms.py:60 +#: contrib/auth/forms.py:14 +#: contrib/auth/forms.py:48 +#: contrib/auth/forms.py:60 msgid "Username" msgstr "Korisnik" -#: contrib/auth/forms.py:15 contrib/auth/forms.py:49 +#: contrib/auth/forms.py:15 +#: contrib/auth/forms.py:49 msgid "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only." -msgstr "" -"Neophodno. Najviše 30 slovnih mesta. Samo alfanumerički znaci (slova, brojke " -"i @/./+/-/_)." +msgstr "Neophodno. Najviše 30 slovnih mesta. Samo alfanumerički znaci (slova, brojke i @/./+/-/_)." -#: contrib/auth/forms.py:16 contrib/auth/forms.py:50 +#: contrib/auth/forms.py:16 +#: contrib/auth/forms.py:50 msgid "This value may contain only letters, numbers and @/./+/-/_ characters." msgstr "Ova vrednost može sadržati samo slova, brojke i @/./+/-/_." @@ -1254,7 +1216,8 @@ msgstr "Potvrda lozinke" msgid "A user with that username already exists." msgstr "Korisnik sa tim korisničkim imenom već postoji." -#: contrib/auth/forms.py:37 contrib/auth/forms.py:156 +#: contrib/auth/forms.py:37 +#: contrib/auth/forms.py:156 #: contrib/auth/forms.py:198 msgid "The two password fields didn't match." msgstr "Dva polja za lozinke se nisu poklopila." @@ -1264,24 +1227,16 @@ msgid "This account is inactive." msgstr "Ovaj nalog je neaktivan." #: contrib/auth/forms.py:88 -msgid "" -"Your Web browser doesn't appear to have cookies enabled. Cookies are " -"required for logging in." -msgstr "" -"Izgleda da su kolačići isključeni u vašem brauzeru. Oni moraju biti " -"uključeni da bi ste se prijavili." +msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in." +msgstr "Izgleda da su kolačići isključeni u vašem brauzeru. Oni moraju biti uključeni da bi ste se prijavili." #: contrib/auth/forms.py:101 msgid "E-mail" msgstr "Imejl adresa" #: contrib/auth/forms.py:110 -msgid "" -"That e-mail address doesn't have an associated user account. Are you sure " -"you've registered?" -msgstr "" -"Ta imejl adresa nije u vezi ni sa jednim nalogom. Da li ste sigurni da ste " -"se već registrovali?" +msgid "That e-mail address doesn't have an associated user account. Are you sure you've registered?" +msgstr "Ta imejl adresa nije u vezi ni sa jednim nalogom. Da li ste sigurni da ste se već registrovali?" #: contrib/auth/forms.py:136 #, python-format @@ -1296,7 +1251,8 @@ msgstr "Potvrda nove lozinke" msgid "Your old password was entered incorrectly. Please enter it again." msgstr "Vaša stara loznka nije pravilno unesena. Unesite je ponovo." -#: contrib/auth/models.py:66 contrib/auth/models.py:94 +#: contrib/auth/models.py:66 +#: contrib/auth/models.py:94 msgid "name" msgstr "ime" @@ -1308,7 +1264,8 @@ msgstr "šifra dozvole" msgid "permission" msgstr "dozvola" -#: contrib/auth/models.py:73 contrib/auth/models.py:95 +#: contrib/auth/models.py:73 +#: contrib/auth/models.py:95 msgid "permissions" msgstr "dozvole" @@ -1316,7 +1273,8 @@ msgstr "dozvole" msgid "group" msgstr "grupa" -#: contrib/auth/models.py:99 contrib/auth/models.py:206 +#: contrib/auth/models.py:99 +#: contrib/auth/models.py:206 msgid "groups" msgstr "grupe" @@ -1325,11 +1283,8 @@ msgid "username" msgstr "korisničko ime" #: contrib/auth/models.py:196 -msgid "" -"Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters" -msgstr "" -"Neophodno. Najviše 30 slovnih mesta. Samo alfanumerički znaci (slova, brojke " -"i @/./+/-/_)." +msgid "Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters" +msgstr "Neophodno. Najviše 30 slovnih mesta. Samo alfanumerički znaci (slova, brojke i @/./+/-/_)." #: contrib/auth/models.py:197 msgid "first name" @@ -1348,12 +1303,8 @@ msgid "password" msgstr "lozinka" #: contrib/auth/models.py:200 -msgid "" -"Use '[algo]$[salt]$[hexdigest]' or use the change " -"password form." -msgstr "" -"Koristite '[algo]$[salt]$[hexdigest]' ili formular za " -"unos lozinke." +msgid "Use '[algo]$[salt]$[hexdigest]' or use the change password form." +msgstr "Koristite '[algo]$[salt]$[hexdigest]' ili formular za unos lozinke." #: contrib/auth/models.py:201 msgid "staff status" @@ -1361,32 +1312,23 @@ msgstr "status člana posade" #: contrib/auth/models.py:201 msgid "Designates whether the user can log into this admin site." -msgstr "" -"Označava da li korisnik može da se prijavi na ovaj sajt za administraciju." +msgstr "Označava da li korisnik može da se prijavi na ovaj sajt za administraciju." #: contrib/auth/models.py:202 msgid "active" msgstr "aktivan" #: contrib/auth/models.py:202 -msgid "" -"Designates whether this user should be treated as active. Unselect this " -"instead of deleting accounts." -msgstr "" -"Označava da li se korisnik smatra aktivnim. Deselektujte ovo umesto da " -"brišete nalog." +msgid "Designates whether this user should be treated as active. Unselect this instead of deleting accounts." +msgstr "Označava da li se korisnik smatra aktivnim. Deselektujte ovo umesto da brišete nalog." #: contrib/auth/models.py:203 msgid "superuser status" msgstr "status administratora" #: contrib/auth/models.py:203 -msgid "" -"Designates that this user has all permissions without explicitly assigning " -"them." -msgstr "" -"Označava da li korisnik ima sve dozvole bez dodeljivanja pojedinačnih " -"dozvola." +msgid "Designates that this user has all permissions without explicitly assigning them." +msgstr "Označava da li korisnik ima sve dozvole bez dodeljivanja pojedinačnih dozvola." #: contrib/auth/models.py:204 msgid "last login" @@ -1397,18 +1339,15 @@ msgid "date joined" msgstr "datum registracije" #: contrib/auth/models.py:207 -msgid "" -"In addition to the permissions manually assigned, this user will also get " -"all permissions granted to each group he/she is in." -msgstr "" -"Pored ručno dodeljenih dozvola, ovaj korisnik će imati i dozvole dodeljene " -"gurpama kojima pripada." +msgid "In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in." +msgstr "Pored ručno dodeljenih dozvola, ovaj korisnik će imati i dozvole dodeljene gurpama kojima pripada." #: contrib/auth/models.py:208 msgid "user permissions" msgstr "korisničke dozvole" -#: contrib/auth/models.py:212 contrib/comments/models.py:50 +#: contrib/auth/models.py:212 +#: contrib/comments/models.py:50 #: contrib/comments/models.py:168 msgid "user" msgstr "korisnik" @@ -1425,8 +1364,9 @@ msgstr "poruka" msgid "Logged out" msgstr "Odjavljen" -#: contrib/auth/management/commands/createsuperuser.py:23 -#: core/validators.py:120 forms/fields.py:428 +#: contrib/auth/management/commands/createsuperuser.py:24 +#: core/validators.py:120 +#: forms/fields.py:427 msgid "Enter a valid e-mail address." msgstr "Unesite važeću imejl adresu." @@ -1497,8 +1437,10 @@ msgstr "Ime" msgid "Email address" msgstr "Imejl adresa" -#: contrib/comments/forms.py:95 contrib/flatpages/admin.py:8 -#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1101 +#: contrib/comments/forms.py:95 +#: contrib/flatpages/admin.py:8 +#: contrib/flatpages/models.py:7 +#: db/models/fields/__init__.py:1109 msgid "URL" msgstr "URL" @@ -1515,11 +1457,11 @@ msgstr[1] "Pazi na jezik! Reči „%s“ ovde nisu dozvoljene." msgstr[2] "Pazi na jezik! Reči „%s“ ovde nisu dozvoljene." #: contrib/comments/forms.py:182 -msgid "" -"If you enter anything in this field your comment will be treated as spam" +msgid "If you enter anything in this field your comment will be treated as spam" msgstr "Ako išta unesete u ovo polje, Vaš komentar će se smatrati spamom." -#: contrib/comments/models.py:22 contrib/contenttypes/models.py:81 +#: contrib/comments/models.py:22 +#: contrib/contenttypes/models.py:81 msgid "content type" msgstr "tip sadržaja" @@ -1539,7 +1481,8 @@ msgstr "korisnikova imejl adresa" msgid "user's URL" msgstr "korisnikov URL" -#: contrib/comments/models.py:56 contrib/comments/models.py:76 +#: contrib/comments/models.py:56 +#: contrib/comments/models.py:76 #: contrib/comments/models.py:169 msgid "comment" msgstr "komentar" @@ -1548,7 +1491,8 @@ msgstr "komentar" msgid "date/time submitted" msgstr "datum/vreme postavljanja" -#: contrib/comments/models.py:60 db/models/fields/__init__.py:896 +#: contrib/comments/models.py:60 +#: db/models/fields/__init__.py:904 msgid "IP address" msgstr "IP adresa" @@ -1557,42 +1501,28 @@ msgid "is public" msgstr "javno" #: contrib/comments/models.py:62 -msgid "" -"Uncheck this box to make the comment effectively disappear from the site." -msgstr "" -"Deselektujte ovo polje ako želite da poruka faktički nestane sa ovog sajta." +msgid "Uncheck this box to make the comment effectively disappear from the site." +msgstr "Deselektujte ovo polje ako želite da poruka faktički nestane sa ovog sajta." #: contrib/comments/models.py:64 msgid "is removed" msgstr "uklonjen" #: contrib/comments/models.py:65 -msgid "" -"Check this box if the comment is inappropriate. A \"This comment has been " -"removed\" message will be displayed instead." -msgstr "" -"Obeležite ovu kućicu ako je komentar neprikladan. Poruka o uklanjanju će " -"biti prikazana umesto komentara." +msgid "Check this box if the comment is inappropriate. A \"This comment has been removed\" message will be displayed instead." +msgstr "Obeležite ovu kućicu ako je komentar neprikladan. Poruka o uklanjanju će biti prikazana umesto komentara." #: contrib/comments/models.py:77 msgid "comments" msgstr "komentari" #: contrib/comments/models.py:119 -msgid "" -"This comment was posted by an authenticated user and thus the name is read-" -"only." -msgstr "" -"Ovaj komentar je postavio prijavljen korisnik i zato je polje sa imenom " -"zaključano." +msgid "This comment was posted by an authenticated user and thus the name is read-only." +msgstr "Ovaj komentar je postavio prijavljen korisnik i zato je polje sa imenom zaključano." #: contrib/comments/models.py:128 -msgid "" -"This comment was posted by an authenticated user and thus the email is read-" -"only." -msgstr "" -"Ovaj komentar je postavio prijavljen korisnik i zato je polje sa imejl " -"adresom zaključano." +msgid "This comment was posted by an authenticated user and thus the email is read-only." +msgstr "Ovaj komentar je postavio prijavljen korisnik i zato je polje sa imejl adresom zaključano." #: contrib/comments/models.py:153 #, python-format @@ -1644,8 +1574,7 @@ msgstr "Hvala na odobrenju!" #: contrib/comments/templates/comments/approved.html:7 #: contrib/comments/templates/comments/deleted.html:7 #: contrib/comments/templates/comments/flagged.html:7 -msgid "" -"Thanks for taking the time to improve the quality of discussion on our site" +msgid "Thanks for taking the time to improve the quality of discussion on our site" msgstr "Hvala na učešću u unapređenju kvaliteta diskusija na našem sajtu." #: contrib/comments/templates/comments/delete.html:4 @@ -1723,19 +1652,12 @@ msgid "content types" msgstr "tipovi sadržaja" #: contrib/flatpages/admin.py:9 -msgid "" -"Example: '/about/contact/'. Make sure to have leading and trailing slashes." -msgstr "" -"Primer: '/about/contact/'. Pazite na to da postoje i početne i završne kose " -"crte." +msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "Primer: '/about/contact/'. Pazite na to da postoje i početne i završne kose crte." #: contrib/flatpages/admin.py:11 -msgid "" -"This value must contain only letters, numbers, underscores, dashes or " -"slashes." -msgstr "" -"Ova vrednost može sadržati samo slova, brojke, donje crte, crtice ili kose " -"crte." +msgid "This value must contain only letters, numbers, underscores, dashes or slashes." +msgstr "Ova vrednost može sadržati samo slova, brojke, donje crte, crtice ili kose crte." #: contrib/flatpages/admin.py:22 msgid "Advanced options" @@ -1758,12 +1680,8 @@ msgid "template name" msgstr "naziv templejta" #: contrib/flatpages/models.py:12 -msgid "" -"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " -"will use 'flatpages/default.html'." -msgstr "" -"Primer: 'flatpages/contact_page.html'. Ako ovo ostavite praznim, sistem će " -"koristiti 'flatpages/default.html'." +msgid "Example: 'flatpages/contact_page.html'. If this isn't provided, the system will use 'flatpages/default.html'." +msgstr "Primer: 'flatpages/contact_page.html'. Ako ovo ostavite praznim, sistem će koristiti 'flatpages/default.html'." #: contrib/flatpages/models.py:13 msgid "registration required" @@ -1771,9 +1689,7 @@ msgstr "potrebna registracija" #: contrib/flatpages/models.py:13 msgid "If this is checked, only logged-in users will be able to view the page." -msgstr "" -"Ako je ovo obeleženo, samo će prijavljeni korisnici moći da vide ovu " -"stranicu." +msgstr "Ako je ovo obeleženo, samo će prijavljeni korisnici moći da vide ovu stranicu." #: contrib/flatpages/models.py:18 msgid "flat page" @@ -1784,17 +1700,12 @@ msgid "flat pages" msgstr "flet stranice" #: contrib/formtools/wizard.py:140 -msgid "" -"We apologize, but your form has expired. Please continue filling out the " -"form from this page." -msgstr "" -"Žao nam je, ali Vaša sesija je istekla. Popunjavanje formulara nastavite na " -"ovoj stranici." +msgid "We apologize, but your form has expired. Please continue filling out the form from this page." +msgstr "Žao nam je, ali Vaša sesija je istekla. Popunjavanje formulara nastavite na ovoj stranici." #: contrib/gis/db/models/fields.py:50 msgid "The base GIS field -- maps to the OpenGIS Specification Geometry type." -msgstr "" -"Osnovno „GIS“ polje koje mapira tip geometrije po „OpenGIS“ specifikaciji." +msgstr "Osnovno „GIS“ polje koje mapira tip geometrije po „OpenGIS“ specifikaciji." #: contrib/gis/db/models/fields.py:270 msgid "Point" @@ -1837,9 +1748,7 @@ msgid "Invalid geometry type." msgstr "Nepostojeći tip geometrije." #: contrib/gis/forms/fields.py:20 -msgid "" -"An error occurred when transforming the geometry to the SRID of the geometry " -"form field." +msgid "An error occurred when transforming the geometry to the SRID of the geometry form field." msgstr "Greška se desila tokom transformacije geometrije na „SRID“ tip polja." #: contrib/humanize/templatetags/humanize.py:19 @@ -1934,8 +1843,10 @@ msgstr "juče" msgid "Enter a postal code in the format NNNN or ANNNNAAA." msgstr "Unesite poštanski broj u formatu NNNN ili ANNNNAAA." -#: contrib/localflavor/ar/forms.py:50 contrib/localflavor/br/forms.py:92 -#: contrib/localflavor/br/forms.py:131 contrib/localflavor/pe/forms.py:24 +#: contrib/localflavor/ar/forms.py:50 +#: contrib/localflavor/br/forms.py:92 +#: contrib/localflavor/br/forms.py:131 +#: contrib/localflavor/pe/forms.py:24 #: contrib/localflavor/pe/forms.py:52 msgid "This field requires only numbers." msgstr "Ovo polje mora da sadrži samo brojke." @@ -1988,15 +1899,15 @@ msgstr "Voralber" msgid "Vienna" msgstr "Beč" -#: contrib/localflavor/at/forms.py:20 contrib/localflavor/ch/forms.py:17 +#: contrib/localflavor/at/forms.py:20 +#: contrib/localflavor/ch/forms.py:17 #: contrib/localflavor/no/forms.py:13 msgid "Enter a zip code in the format XXXX." msgstr "Unesite poštanski broj u formatu XXXX." #: contrib/localflavor/at/forms.py:48 msgid "Enter a valid Austrian Social Security Number in XXXX XXXXXX format." -msgstr "" -"Unesite važeći austrijski broj socijalnog osiguranja u formatu XXXX XXXXXX." +msgstr "Unesite važeći austrijski broj socijalnog osiguranja u formatu XXXX XXXXXX." #: contrib/localflavor/au/forms.py:17 msgid "Enter a 4 digit post code." @@ -2011,9 +1922,7 @@ msgid "Phone numbers must be in XX-XXXX-XXXX format." msgstr "Broj telefona mora biti u formatu XX-XXXX-XXXX." #: contrib/localflavor/br/forms.py:54 -msgid "" -"Select a valid brazilian state. That state is not one of the available " -"states." +msgid "Select a valid brazilian state. That state is not one of the available states." msgstr "Odaberite postojeću brazilsku državu. Ta država nije među ponuđenima." #: contrib/localflavor/br/forms.py:90 @@ -2145,9 +2054,7 @@ msgid "Zurich" msgstr "" #: contrib/localflavor/ch/forms.py:65 -msgid "" -"Enter a valid Swiss identity or passport card number in X1234567<0 or " -"1234567890 format." +msgid "Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format." msgstr "" #: contrib/localflavor/cl/forms.py:30 @@ -2218,7 +2125,8 @@ msgstr "" msgid "Moravian-Silesian Region" msgstr "" -#: contrib/localflavor/cz/forms.py:28 contrib/localflavor/sk/forms.py:30 +#: contrib/localflavor/cz/forms.py:28 +#: contrib/localflavor/sk/forms.py:30 msgid "Enter a postal code in the format XXXXX or XXX XX." msgstr "" @@ -2302,15 +2210,14 @@ msgstr "" msgid "Thuringia" msgstr "" -#: contrib/localflavor/de/forms.py:15 contrib/localflavor/fi/forms.py:13 +#: contrib/localflavor/de/forms.py:15 +#: contrib/localflavor/fi/forms.py:13 #: contrib/localflavor/fr/forms.py:16 msgid "Enter a zip code in the format XXXXX." msgstr "" #: contrib/localflavor/de/forms.py:42 -msgid "" -"Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X " -"format." +msgid "Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format." msgstr "" #: contrib/localflavor/es/es_provinces.py:5 @@ -2585,9 +2492,7 @@ msgid "Enter a valid postal code in the range and format 01XXX - 52XXX." msgstr "" #: contrib/localflavor/es/forms.py:40 -msgid "" -"Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or " -"9XXXXXXXX." +msgid "Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or 9XXXXXXXX." msgstr "" #: contrib/localflavor/es/forms.py:67 @@ -2611,8 +2516,7 @@ msgid "Invalid checksum for CIF." msgstr "" #: contrib/localflavor/es/forms.py:143 -msgid "" -"Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX." +msgid "Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX." msgstr "" #: contrib/localflavor/es/forms.py:144 @@ -2631,7 +2535,8 @@ msgstr "" msgid "Enter a valid post code" msgstr "" -#: contrib/localflavor/id/forms.py:68 contrib/localflavor/nl/forms.py:53 +#: contrib/localflavor/id/forms.py:68 +#: contrib/localflavor/nl/forms.py:53 msgid "Enter a valid phone number" msgstr "" @@ -3060,8 +2965,7 @@ msgid "Enter a zip code in the format XXXXXXX." msgstr "" #: contrib/localflavor/is_/forms.py:18 -msgid "" -"Enter a valid Icelandic identification number. The format is XXXXXX-XXXX." +msgid "Enter a valid Icelandic identification number. The format is XXXXXX-XXXX." msgstr "" #: contrib/localflavor/is_/forms.py:19 @@ -3481,8 +3385,7 @@ msgid "Wrong checksum for the National Identification Number." msgstr "" #: contrib/localflavor/pl/forms.py:71 -msgid "" -"Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX." +msgid "Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX." msgstr "" #: contrib/localflavor/pl/forms.py:72 @@ -4410,24 +4313,16 @@ msgid "redirect from" msgstr "preusmeren sa" #: contrib/redirects/models.py:8 -msgid "" -"This should be an absolute path, excluding the domain name. Example: '/" -"events/search/'." -msgstr "" -"Ovo mora biti apsolutna putanja bez imena domena. Na primer: '/events/" -"search/'." +msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'." +msgstr "Ovo mora biti apsolutna putanja bez imena domena. Na primer: '/events/search/'." #: contrib/redirects/models.py:9 msgid "redirect to" msgstr "preusmeri ka" #: contrib/redirects/models.py:10 -msgid "" -"This can be either an absolute path (as above) or a full URL starting with " -"'http://'." -msgstr "" -"Ovo može biti ili apsolutna putanja (kao gore) ili pun URL koji počinje sa " -"'http://'." +msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'." +msgstr "Ovo može biti ili apsolutna putanja (kao gore) ili pun URL koji počinje sa 'http://'." #: contrib/redirects/models.py:13 msgid "redirect" @@ -4469,30 +4364,33 @@ msgstr "prikazano ime" msgid "sites" msgstr "sajtovi" -#: core/validators.py:20 forms/fields.py:66 +#: core/validators.py:20 +#: forms/fields.py:66 msgid "Enter a valid value." msgstr "Unesite ispravnu vrednost." -#: core/validators.py:87 forms/fields.py:529 +#: core/validators.py:87 +#: forms/fields.py:528 msgid "Enter a valid URL." msgstr "Unesite ispravan URL." -#: core/validators.py:89 forms/fields.py:530 +#: core/validators.py:89 +#: forms/fields.py:529 msgid "This URL appears to be a broken link." msgstr "Ovaj URL izgleda ne vodi nikuda." -#: core/validators.py:123 forms/fields.py:873 -msgid "" -"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." -msgstr "" -"Unesite isrpavan „slag“, koji se sastoji od slova, brojki, donjih crta ili " -"cirtica." +#: core/validators.py:123 +#: forms/fields.py:877 +msgid "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." +msgstr "Unesite isrpavan „slag“, koji se sastoji od slova, brojki, donjih crta ili cirtica." -#: core/validators.py:126 forms/fields.py:866 +#: core/validators.py:126 +#: forms/fields.py:870 msgid "Enter a valid IPv4 address." msgstr "Unesite ispravnu IPv4 adresu." -#: core/validators.py:129 db/models/fields/__init__.py:572 +#: core/validators.py:129 +#: db/models/fields/__init__.py:572 msgid "Enter only digits separated by commas." msgstr "Unesite samo brojke razdvojene zapetama." @@ -4501,40 +4399,37 @@ msgstr "Unesite samo brojke razdvojene zapetama." msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." msgstr "Ovo polje mora da bude %(limit_value)s (trenutno ima %(show_value)s)." -#: core/validators.py:153 forms/fields.py:205 forms/fields.py:257 +#: core/validators.py:153 +#: forms/fields.py:204 +#: forms/fields.py:256 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "Ova vrednost mora da bude manja od %(limit_value)s. ili tačno toliko." -#: core/validators.py:158 forms/fields.py:206 forms/fields.py:258 +#: core/validators.py:158 +#: forms/fields.py:205 +#: forms/fields.py:257 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "Ova vrednost mora biti veća od %(limit_value)s ili tačno toliko." #: core/validators.py:164 #, python-format -msgid "" -"Ensure this value has at least %(limit_value)d characters (it has %" -"(show_value)d)." -msgstr "" -"Ovo polje mora da sadrži najmanje %(limit_value)d slovnih mesta (trenutno " -"ima %(show_value)d)." +msgid "Ensure this value has at least %(limit_value)d characters (it has %(show_value)d)." +msgstr "Ovo polje mora da sadrži najmanje %(limit_value)d slovnih mesta (trenutno ima %(show_value)d)." #: core/validators.py:170 #, python-format -msgid "" -"Ensure this value has at most %(limit_value)d characters (it has %" -"(show_value)d)." -msgstr "" -"Ovo polje mora da sadrži najviše %(limit_value)d slovnih mesta (trenutno ima " -"%(show_value)d)." +msgid "Ensure this value has at most %(limit_value)d characters (it has %(show_value)d)." +msgstr "Ovo polje mora da sadrži najviše %(limit_value)d slovnih mesta (trenutno ima %(show_value)d)." -#: db/models/base.py:822 +#: db/models/base.py:823 #, python-format msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s." msgstr "%(field_name)s mora da bude jedinstven za %(date_field)s %(lookup)s." -#: db/models/base.py:837 db/models/base.py:845 +#: db/models/base.py:838 +#: db/models/base.py:846 #, python-format msgid "%(model_name)s with this %(field_label)s already exists." msgstr "%(model_name)s sa ovom vrednošću %(field_label)s već postoji." @@ -4557,13 +4452,16 @@ msgstr "Ovo polje ne može da ostane prazno." msgid "Field of type: %(field_type)s" msgstr "Ponje tipa: %(field_type)s" -#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:852 -#: db/models/fields/__init__.py:961 db/models/fields/__init__.py:972 -#: db/models/fields/__init__.py:999 +#: db/models/fields/__init__.py:451 +#: db/models/fields/__init__.py:860 +#: db/models/fields/__init__.py:969 +#: db/models/fields/__init__.py:980 +#: db/models/fields/__init__.py:1007 msgid "Integer" msgstr "Ceo broj" -#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:850 +#: db/models/fields/__init__.py:455 +#: db/models/fields/__init__.py:858 msgid "This value must be an integer." msgstr "Ova vrednost mora biti celobrojna." @@ -4575,7 +4473,8 @@ msgstr "Ova vrednost mora biti True ili False." msgid "Boolean (Either True or False)" msgstr "Bulova vrednost (True ili False)" -#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:982 +#: db/models/fields/__init__.py:539 +#: db/models/fields/__init__.py:990 #, python-format msgid "String (up to %(max_length)s)" msgstr "String (najviše %(max_length)s znakova)" @@ -4617,44 +4516,45 @@ msgstr "Decimalni broj" msgid "E-mail address" msgstr "Imejl adresa" -#: db/models/fields/__init__.py:799 db/models/fields/files.py:220 +#: db/models/fields/__init__.py:807 +#: db/models/fields/files.py:220 #: db/models/fields/files.py:331 msgid "File path" msgstr "Putanja fajla" -#: db/models/fields/__init__.py:822 +#: db/models/fields/__init__.py:830 msgid "This value must be a float." msgstr "Ova vrednost mora biti broj sa klizećom zapetom" -#: db/models/fields/__init__.py:824 +#: db/models/fields/__init__.py:832 msgid "Floating point number" msgstr "Broj sa pokrenom zapetom" -#: db/models/fields/__init__.py:883 +#: db/models/fields/__init__.py:891 msgid "Big (8 byte) integer" msgstr "Veliki ceo broj" -#: db/models/fields/__init__.py:912 +#: db/models/fields/__init__.py:920 msgid "This value must be either None, True or False." msgstr "Ova vrednost mora biti ili None, ili True, ili False." -#: db/models/fields/__init__.py:914 +#: db/models/fields/__init__.py:922 msgid "Boolean (Either True, False or None)" msgstr "Bulova vrednost (True, False ili None)" -#: db/models/fields/__init__.py:1005 +#: db/models/fields/__init__.py:1013 msgid "Text" msgstr "Tekst" -#: db/models/fields/__init__.py:1021 +#: db/models/fields/__init__.py:1029 msgid "Time" msgstr "Vreme" -#: db/models/fields/__init__.py:1025 +#: db/models/fields/__init__.py:1033 msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgstr "Unesite ispravno vreme u formatu ČČ:MM[:ss[.uuuuuu]]." -#: db/models/fields/__init__.py:1109 +#: db/models/fields/__init__.py:1125 msgid "XML text" msgstr "XML tekst" @@ -4667,26 +4567,22 @@ msgstr "Objekat klase %(model)s sa primarnim ključem %(pk)r ne postoji." msgid "Foreign Key (type determined by related field)" msgstr "Strani ključ (tip određuje referentno polje)" -#: db/models/fields/related.py:918 +#: db/models/fields/related.py:919 msgid "One-to-one relationship" msgstr "Relacija jedan na jedan" -#: db/models/fields/related.py:980 +#: db/models/fields/related.py:981 msgid "Many-to-many relationship" msgstr "Relacija više na više" -#: db/models/fields/related.py:1000 -msgid "" -"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." -msgstr "" -"Držite „Control“, ili „Command“ na Mac-u da biste obeležili više od jedne " -"stavke." +#: db/models/fields/related.py:1001 +msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "Držite „Control“, ili „Command“ na Mac-u da biste obeležili više od jedne stavke." -#: db/models/fields/related.py:1061 +#: db/models/fields/related.py:1062 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." -msgid_plural "" -"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." msgstr[0] "Unesite ispravan %(self)s IDs. Vrednost %(value)r je neispravna." msgstr[1] "Unesite ispravan %(self)s IDs. Vrednosti %(value)r su neispravne." msgstr[2] "Unesite ispravan %(self)s IDs. Vrednosti %(value)r su neispravne." @@ -4695,80 +4591,79 @@ msgstr[2] "Unesite ispravan %(self)s IDs. Vrednosti %(value)r su neispravne." msgid "This field is required." msgstr "Ovo polje se mora popuniti." -#: forms/fields.py:204 +#: forms/fields.py:203 msgid "Enter a whole number." msgstr "Unesite ceo broj." -#: forms/fields.py:235 forms/fields.py:256 +#: forms/fields.py:234 +#: forms/fields.py:255 msgid "Enter a number." msgstr "Unesite broj." -#: forms/fields.py:259 +#: forms/fields.py:258 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Ne sme biti ukupno više od %s cifara. Proverite." -#: forms/fields.py:260 +#: forms/fields.py:259 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Ne sme biti ukupno više od %s decimalnih mesta. Proverite." -#: forms/fields.py:261 +#: forms/fields.py:260 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Ne sme biti ukupno više od %s cifara pre zapete. Proverite." -#: forms/fields.py:323 forms/fields.py:838 +#: forms/fields.py:322 +#: forms/fields.py:837 msgid "Enter a valid date." msgstr "Unesite ispravan datum." -#: forms/fields.py:351 forms/fields.py:839 +#: forms/fields.py:350 +#: forms/fields.py:838 msgid "Enter a valid time." msgstr "Unesite ispravno vreme" -#: forms/fields.py:377 +#: forms/fields.py:376 msgid "Enter a valid date/time." msgstr "Unesite ispravan datum/vreme." -#: forms/fields.py:435 +#: forms/fields.py:434 msgid "No file was submitted. Check the encoding type on the form." msgstr "Fajl nije prebačen. Proverite tip enkodiranja formulara." -#: forms/fields.py:436 +#: forms/fields.py:435 msgid "No file was submitted." msgstr "Fajl nije prebačen." -#: forms/fields.py:437 +#: forms/fields.py:436 msgid "The submitted file is empty." msgstr "Prebačen fajl je prazan." -#: forms/fields.py:438 +#: forms/fields.py:437 #, python-format -msgid "" -"Ensure this filename has at most %(max)d characters (it has %(length)d)." -msgstr "" -"Naziv fajla mora da sadrži bar %(max)d slovnih mesta (trenutno ima %(length)" -"d)." +msgid "Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr "Naziv fajla mora da sadrži bar %(max)d slovnih mesta (trenutno ima %(length)d)." -#: forms/fields.py:473 -msgid "" -"Upload a valid image. The file you uploaded was either not an image or a " -"corrupted image." -msgstr "" -"Prebacite ispravan fajl. Fajl koji je prebačen ili nije slika, ili je " -"oštećen." +#: forms/fields.py:472 +msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." +msgstr "Prebacite ispravan fajl. Fajl koji je prebačen ili nije slika, ili je oštećen." -#: forms/fields.py:596 forms/fields.py:671 +#: forms/fields.py:595 +#: forms/fields.py:670 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "" -"%(value)s nije među ponuđenim vrednostima. Odaberite jednu od ponuđenih." +msgstr "%(value)s nije među ponuđenim vrednostima. Odaberite jednu od ponuđenih." -#: forms/fields.py:672 forms/fields.py:734 forms/models.py:1002 +#: forms/fields.py:671 +#: forms/fields.py:733 +#: forms/models.py:1002 msgid "Enter a list of values." msgstr "Unesite listu vrednosti." -#: forms/formsets.py:298 forms/formsets.py:300 +#: forms/formsets.py:296 +#: forms/formsets.py:298 msgid "Order" msgstr "Redosled" @@ -4780,17 +4675,12 @@ msgstr "Ispravite dupliran sadržaj za polja: %(field)s." #: forms/models.py:566 #, python-format msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "" -"Ispravite dupliran sadržaj za polja: %(field)s, koji mora da bude jedinstven." +msgstr "Ispravite dupliran sadržaj za polja: %(field)s, koji mora da bude jedinstven." #: forms/models.py:572 #, python-format -msgid "" -"Please correct the duplicate data for %(field_name)s which must be unique " -"for the %(lookup)s in %(date_field)s." -msgstr "" -"Ispravite dupliran sadržaj za polja: %(field_name)s, koji mora da bude " -"jedinstven za %(lookup)s u %(date_field)s." +msgid "Please correct the duplicate data for %(field_name)s which must be unique for the %(lookup)s in %(date_field)s." +msgstr "Ispravite dupliran sadržaj za polja: %(field_name)s, koji mora da bude jedinstven za %(lookup)s u %(date_field)s." #: forms/models.py:580 msgid "Please correct the duplicate values below." @@ -4929,23 +4819,28 @@ msgstr "januar" msgid "February" msgstr "februar" -#: utils/dates.py:18 utils/dates.py:31 +#: utils/dates.py:18 +#: utils/dates.py:31 msgid "March" msgstr "mart" -#: utils/dates.py:18 utils/dates.py:31 +#: utils/dates.py:18 +#: utils/dates.py:31 msgid "April" msgstr "april" -#: utils/dates.py:18 utils/dates.py:31 +#: utils/dates.py:18 +#: utils/dates.py:31 msgid "May" msgstr "maj" -#: utils/dates.py:18 utils/dates.py:31 +#: utils/dates.py:18 +#: utils/dates.py:31 msgid "June" msgstr "jun" -#: utils/dates.py:19 utils/dates.py:31 +#: utils/dates.py:19 +#: utils/dates.py:31 msgid "July" msgstr "jul" @@ -5105,23 +5000,23 @@ msgstr "%(number)d %(type)s" msgid ", %(number)d %(type)s" msgstr ", %(number)d %(type)s" -#: utils/translation/trans_real.py:518 +#: utils/translation/trans_real.py:519 msgid "DATE_FORMAT" msgstr "j. F Y." -#: utils/translation/trans_real.py:519 +#: utils/translation/trans_real.py:520 msgid "DATETIME_FORMAT" msgstr "j. F Y. H:i T" -#: utils/translation/trans_real.py:520 +#: utils/translation/trans_real.py:521 msgid "TIME_FORMAT" msgstr "G:i" -#: utils/translation/trans_real.py:541 +#: utils/translation/trans_real.py:542 msgid "YEAR_MONTH_FORMAT" msgstr "F Y." -#: utils/translation/trans_real.py:542 +#: utils/translation/trans_real.py:543 msgid "MONTH_DAY_FORMAT" msgstr "j. F" @@ -5139,3 +5034,4 @@ msgstr "%(verbose_name)s je uspešno ažuriran." #, python-format msgid "The %(verbose_name)s was deleted." msgstr "%(verbose_name)s je obrisan." + diff --git a/django/conf/locale/sr_Latn/formats.py b/django/conf/locale/sr_Latn/formats.py index 63a20f4574a2..cb0478ed0f59 100644 --- a/django/conf/locale/sr_Latn/formats.py +++ b/django/conf/locale/sr_Latn/formats.py @@ -11,9 +11,9 @@ SHORT_DATETIME_FORMAT = 'j.m.Y. H:i' FIRST_DAY_OF_WEEK = 1 DATE_INPUT_FORMATS = ( - '%Y-%m-%d', # '2006-10-25' '%d.%m.%Y.', '%d.%m.%y.', # '25.10.2006.', '25.10.06.' '%d. %m. %Y.', '%d. %m. %y.', # '25. 10. 2006.', '25. 10. 06.' + '%Y-%m-%d', # '2006-10-25' # '%d. %b %y.', '%d. %B %y.', # '25. Oct 06.', '25. October 06.' # '%d. %b \'%y.', '%d. %B \'%y.', # '25. Oct '06.', '25. October '06.' # '%d. %b %Y.', '%d. %B %Y.', # '25. Oct 2006.', '25. October 2006.' @@ -23,9 +23,6 @@ '%H:%M', # '14:30' ) DATETIME_INPUT_FORMATS = ( - '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' - '%Y-%m-%d %H:%M', # '2006-10-25 14:30' - '%Y-%m-%d', # '2006-10-25' '%d.%m.%Y. %H:%M:%S', # '25.10.2006. 14:30:59' '%d.%m.%Y. %H:%M', # '25.10.2006. 14:30' '%d.%m.%Y.', # '25.10.2006.' @@ -38,7 +35,10 @@ '%d. %m. %y. %H:%M:%S', # '25. 10. 06. 14:30:59' '%d. %m. %y. %H:%M', # '25. 10. 06. 14:30' '%d. %m. %y.', # '25. 10. 06.' + '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' + '%Y-%m-%d %H:%M', # '2006-10-25 14:30' + '%Y-%m-%d', # '2006-10-25' ) -DECIMAL_SEPARATOR = '.' -THOUSAND_SEPARATOR = ',' +DECIMAL_SEPARATOR = ',' +THOUSAND_SEPARATOR = '.' NUMBER_GROUPING = 3 From 849c6079b46576670917ab65ce4f19ec786ae09d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 02:46:50 +0000 Subject: [PATCH 046/902] [1.2.X] Fixed #13594 -- Corrected typo in email docs. Thanks to mostrovsky for the report. Backport of r13526 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13532 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/email.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 74e153de61eb..8ea64dac1fbb 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -501,7 +501,7 @@ convenience that can be used during development. Defining a custom e-mail backend -------------------------------- -If you need to change how e-mails are send you can write your own e-mail +If you need to change how e-mails are sent you can write your own e-mail backend. The ``EMAIL_BACKEND`` setting in your settings file is then the Python import path for your backend class. From b1c621bea479aaa5a143d6b864fac7afdb5f71d4 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 02:47:10 +0000 Subject: [PATCH 047/902] [1.2.X] Fixed #13727 -- Corrected alphabetical sorting in settings docs. Thanks to adamv. Backport of r13527 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13533 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 144 +++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 53fbfb2ffeec..f3c5656a9ab3 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -152,18 +152,6 @@ Default: ``600`` The default number of seconds to cache a page when the caching middleware or ``cache_page()`` decorator is used. -.. setting:: CSRF_COOKIE_NAME - -CSRF_COOKIE_NAME ----------------- - -.. versionadded:: 1.2 - -Default: ``'csrftoken'`` - -The name of the cookie to use for the CSRF authentication token. This can be whatever you -want. See :ref:`ref-contrib-csrf`. - .. setting:: CSRF_COOKIE_DOMAIN CSRF_COOKIE_DOMAIN @@ -179,6 +167,18 @@ request forgery protection. It should be set to a string such as ``".lawrence.com"`` to allow a POST request from a form on one subdomain to be accepted by accepted by a view served from another subdomain. +.. setting:: CSRF_COOKIE_NAME + +CSRF_COOKIE_NAME +---------------- + +.. versionadded:: 1.2 + +Default: ``'csrftoken'`` + +The name of the cookie to use for the CSRF authentication token. This can be whatever you +want. See :ref:`ref-contrib-csrf`. + .. setting:: CSRF_FAILURE_VIEW CSRF_FAILURE_VIEW @@ -573,29 +573,29 @@ Default: ``'webmaster@localhost'`` Default e-mail address to use for various automated correspondence from the site manager(s). -.. setting:: DEFAULT_TABLESPACE +.. setting:: DEFAULT_INDEX_TABLESPACE -DEFAULT_TABLESPACE ------------------- +DEFAULT_INDEX_TABLESPACE +------------------------ .. versionadded:: 1.0 Default: ``''`` (Empty string) -Default tablespace to use for models that don't specify one, if the -backend supports it. +Default tablespace to use for indexes on fields that don't specify +one, if the backend supports it. -.. setting:: DEFAULT_INDEX_TABLESPACE +.. setting:: DEFAULT_TABLESPACE -DEFAULT_INDEX_TABLESPACE ------------------------- +DEFAULT_TABLESPACE +------------------ .. versionadded:: 1.0 Default: ``''`` (Empty string) -Default tablespace to use for indexes on fields that don't specify -one, if the backend supports it. +Default tablespace to use for models that don't specify one, if the +backend supports it. .. setting:: DISALLOWED_USER_AGENTS @@ -738,21 +738,6 @@ Default: ``2621440`` (i.e. 2.5 MB). The maximum size (in bytes) that an upload will be before it gets streamed to the file system. See :ref:`topics-files` for details. -.. setting:: FILE_UPLOAD_TEMP_DIR - -FILE_UPLOAD_TEMP_DIR --------------------- - -.. versionadded:: 1.0 - -Default: ``None`` - -The directory to store data temporarily while uploading files. If ``None``, -Django will use the standard temporary directory for the operating system. For -example, this will default to '/tmp' on \*nix-style operating systems. - -See :ref:`topics-files` for details. - .. setting:: FILE_UPLOAD_PERMISSIONS FILE_UPLOAD_PERMISSIONS @@ -781,6 +766,21 @@ system's standard umask. .. _documentation for os.chmod: http://docs.python.org/library/os.html#os.chmod +.. setting:: FILE_UPLOAD_TEMP_DIR + +FILE_UPLOAD_TEMP_DIR +-------------------- + +.. versionadded:: 1.0 + +Default: ``None`` + +The directory to store data temporarily while uploading files. If ``None``, +Django will use the standard temporary directory for the operating system. For +example, this will default to '/tmp' on \*nix-style operating systems. + +See :ref:`topics-files` for details. + .. setting:: FIRST_DAY_OF_WEEK FIRST_DAY_OF_WEEK @@ -1227,27 +1227,6 @@ Default: ``'root@localhost'`` The e-mail address that error messages come from, such as those sent to ``ADMINS`` and ``MANAGERS``. -.. setting:: SESSION_ENGINE - -SESSION_ENGINE --------------- - -.. versionadded:: 1.0 - -.. versionchanged:: 1.1 - The ``cached_db`` backend was added - -Default: ``django.contrib.sessions.backends.db`` - -Controls where Django stores session data. Valid values are: - - * ``'django.contrib.sessions.backends.db'`` - * ``'django.contrib.sessions.backends.file'`` - * ``'django.contrib.sessions.backends.cache'`` - * ``'django.contrib.sessions.backends.cached_db'`` - -See :ref:`topics-http-sessions`. - .. setting:: SESSION_COOKIE_AGE SESSION_COOKIE_AGE @@ -1306,6 +1285,27 @@ Whether to use a secure cookie for the session cookie. If this is set to ensure that the cookie is only sent under an HTTPS connection. See the :ref:`topics-http-sessions`. +.. setting:: SESSION_ENGINE + +SESSION_ENGINE +-------------- + +.. versionadded:: 1.0 + +.. versionchanged:: 1.1 + The ``cached_db`` backend was added + +Default: ``django.contrib.sessions.backends.db`` + +Controls where Django stores session data. Valid values are: + + * ``'django.contrib.sessions.backends.db'`` + * ``'django.contrib.sessions.backends.file'`` + * ``'django.contrib.sessions.backends.cache'`` + * ``'django.contrib.sessions.backends.cached_db'`` + +See :ref:`topics-http-sessions`. + .. setting:: SESSION_EXPIRE_AT_BROWSER_CLOSE SESSION_EXPIRE_AT_BROWSER_CLOSE @@ -1601,6 +1601,20 @@ A boolean that specifies whether to output the "Etag" header. This saves bandwidth but slows down performance. This is only used if ``CommonMiddleware`` is installed (see :ref:`topics-http-middleware`). +.. setting:: USE_I18N + +USE_I18N +-------- + +Default: ``True`` + +A boolean that specifies whether Django's internationalization system should be +enabled. This provides an easy way to turn it off, for performance. If this is +set to ``False``, Django will make some optimizations so as not to load the +internationalization machinery. + +See also ``USE_L10N`` + .. setting:: USE_L10N USE_L10N @@ -1616,20 +1630,6 @@ format of the current locale. See also ``USE_I18N`` and ``LANGUAGE_CODE`` -.. setting:: USE_I18N - -USE_I18N --------- - -Default: ``True`` - -A boolean that specifies whether Django's internationalization system should be -enabled. This provides an easy way to turn it off, for performance. If this is -set to ``False``, Django will make some optimizations so as not to load the -internationalization machinery. - -See also ``USE_L10N`` - .. setting:: USE_THOUSAND_SEPARATOR USE_THOUSAND_SEPARATOR From 490bd7f9bc323362dc98c01a88f9e8549dec7d42 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 02:47:31 +0000 Subject: [PATCH 048/902] [1.2.X] Fixed #13718 -- Corrected typo in model docs. Thanks to gvkalra for the report. Backport of r13528 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13534 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/models.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 6d7a7a437492..78c578d61a7b 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -353,7 +353,7 @@ For example, if a ``Pizza`` has multiple ``Topping`` objects -- that is, a As with :class:`~django.db.models.ForeignKey`, you can also create :ref:`recursive relationships ` (an object with a -many-to-one relationship to itself) and :ref:`relationships to models not yet +many-to-many relationship to itself) and :ref:`relationships to models not yet defined `; see :ref:`the model field reference ` for details. From 049a246cc4829bce63b2860e4b2e3babf44e7df9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 02:47:51 +0000 Subject: [PATCH 049/902] [1.2.X] Fixed #13687 -- Corrected request/response docs that mistakenly suggested __getitem__ and __delitem__ were case sensitive. Thanks to master for the report. Backport of r13529 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13535 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/request-response.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 2c874a1a83a1..d111e4c127c2 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -498,11 +498,11 @@ Methods .. method:: HttpResponse.__delitem__(header) Deletes the header with the given name. Fails silently if the header - doesn't exist. Case-sensitive. + doesn't exist. Case-insensitive. .. method:: HttpResponse.__getitem__(header) - Returns the value for the given header name. Case-sensitive. + Returns the value for the given header name. Case-insensitive. .. method:: HttpResponse.has_header(header) From e7b009ce053cee1d65d5cd77200310a0de72789d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 02:48:11 +0000 Subject: [PATCH 050/902] [1.2.X] Fixed #13661 -- Corrected example in the serialization docs. Thanks to jabapyth for the report. Backport of r13530 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13536 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/serialization.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index c5155107f026..b99a3b925e95 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -338,7 +338,7 @@ example, ``(first name, last name)``. Then, when you call ``serializers.serialize()``, you provide a ``use_natural_keys=True`` argument:: - >>> serializers.serialize([book1, book2], format='json', indent=2, use_natural_keys=True) + >>> serializers.serialize('json', [book1, book2], indent=2, use_natural_keys=True) When ``use_natural_keys=True`` is specified, Django will use the ``natural_key()`` method to serialize any reference to objects of the From 8b70c75ed6a11b3f0e55996200addab3f116c145 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 02:48:31 +0000 Subject: [PATCH 051/902] [1.2.X] Fixed #13658 -- Added missing command in JavaScript example. Thanks to erikr for the report. Backport of r13531 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13537 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/internationalization.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 7ae8d187912c..35e76c3d62ac 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -569,7 +569,7 @@ function supports both positional and named interpolation: object or associative array. For example:: d = { - count: 10 + count: 10, total: 50 }; From e990a2cbc43e5eaa3f245cd1b6a4c6850e31f3e9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 07:39:19 +0000 Subject: [PATCH 052/902] [1.2.X] Fixed #13516 -- Added an internal type definition for AutoFields, so that subclassed fields are handled correctly by the SQL generator. Thanks to manfre for the report and patch. Backport of r13540 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13541 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 4640eb323899..3c5887303562 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -459,6 +459,9 @@ def __init__(self, *args, **kwargs): kwargs['blank'] = True Field.__init__(self, *args, **kwargs) + def get_internal_type(self): + return "AutoField" + def to_python(self, value): if value is None: return value From 3309c394382dd4147f3c8c9acb8afee7f974f19b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 07:58:38 +0000 Subject: [PATCH 053/902] [1.2.X] Fixed #7284 -- Provided an example for the use of the search_fields clause on ModelAdmin. Thanks to rbell01824@earthlink.net for the suggestion, and Simon Meers for the initial patch. Backport of r13542 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13544 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/admin/index.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 4d84dfafbc9e..d527b30ced77 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -600,6 +600,11 @@ the lookup API "follow" notation:: search_fields = ['foreign_key__related_fieldname'] +For example, if you have a blog entry with an author, the following definition +would enable search blog entries by the email address of the author:: + + search_fields = ['user__email'] + When somebody does a search in the admin search box, Django splits the search query into words and returns all objects that contain each of the words, case insensitive, where each word must be in at least one of ``search_fields``. For From e2d7d52c33618280c10020c5143553ac0953a7b3 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 07:58:58 +0000 Subject: [PATCH 054/902] [1.2.X] Fixed #8567 -- Clarified the process of instantiating FormWizards. Thanks to ClaesBas for the suggestion, and ElliotM and timo for draft text. Backport of r13543 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13545 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/formtools/form-wizard.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 5ef862ce3dce..8dcebd1d6fb2 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -189,8 +189,10 @@ for the wizard to work properly. Hooking the wizard into a URLconf ================================= -Finally, give your new :class:`FormWizard` object a URL in ``urls.py``. The -wizard takes a list of your :class:`~django.forms.Form` objects as arguments:: +Finally, we need to specify which forms to use in the wizard, and then +deploy the new :class:`FormWizard` object a URL in ``urls.py``. The +wizard takes a list of your :class:`~django.forms.Form` objects as +arguments when you instantiate the Wizard:: from django.conf.urls.defaults import * from mysite.testapp.forms import ContactForm1, ContactForm2, ContactWizard From 94528b90670b3a72135f5318c1b7224f0fdc365a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 14:18:47 +0000 Subject: [PATCH 055/902] [1.2.X] Fixed #14076 -- Updated Danish translation. Thanks to finngruwier. Backport of r13546 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13547 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/locale/da/LC_MESSAGES/djangojs.mo | Bin 1662 -> 2587 bytes django/conf/locale/da/LC_MESSAGES/djangojs.po | 37 ++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/django/conf/locale/da/LC_MESSAGES/djangojs.mo b/django/conf/locale/da/LC_MESSAGES/djangojs.mo index 697f1a9f6a80d1d5d2d7771f29151434837d4232..47490d86205c3666adce3c33f2963f731fae7b35 100644 GIT binary patch literal 2587 zcmaKt%WoS+9LJ|n9t{Obc|U}H5{lZ2tm{Oyjlx5Oq;AtBO;tOsDumF)`}2C@^^7|^ z>(>7O2Tn-90WO?cgv5aZhg^{_h)cx<2?@~?7ZBpoBdYlRcAZBbWaXL9&dhIqubKV% z_k(*sS9o3-wSU2JwBPoi!_#{}sT1I3@L}*9@Iml9a6kAx_$c_pX#FSfag2Wk$H1H5 zKJd>GZ-EbCd>h;g?uD{da33goJ_KdH4dSP|=)~@2Q1o5}3H%5Yxv#-#@LN#y{sD@f zzd*5j3;Yti4SoiGflWQI0^y^G@hfl=;~Ov{`}RDd6n<(9-2w0*DD$t4H~}8S_!ubh zod(5D9TfjQ01=w%fZ~q@>tFySjvq(70gBu&pve6S%Kn?+tKi=tTvq#G{0;B`C~_x3 z@n;d8=xczVg71S8=Re>l;Ky)Ej!!zYat2SMJ%=X#7C8guk^IOV*?i=Vsqw=Zr$;h!EgC=Ev;nV};AY@> z*R|uME8U8kjbk<2*Cy3#DbskS=5)$7)*dgm$*G!aJC`wO3Z~0jqqJDa*cMw-)}FM? zrLNggr%lZ2jJCP<1FG?wFJ6|lZ?dO&A#%jnVp&E`p-1*&r|aCeP8tu%uZ zo^}0lVxF7Di&{3ZHAy?Dv(8y`SJZj#LQ2(XnHqEvtwwcKFET&_mwRfqooVvn>f%YcR#CgU1|dn-mTMVy#+&IY zoeS<>=V-v8Lg#S&xFKa`R~8A9vcL2^P02EJVbXKt^U||2B|DtTZE5oUso>az;(l)C zWT92KE9!P67!i27ThkQ-7uFbQ%R)t+^KKpAMzjz|7uaVwml>L0M}%(dJolUr(Q=lU zIC?KnvZ&!^XzbidBbxK9a#5M(4S2MCZM7 zQ&Th5YALs*vk*1Bwppr!^D|U4*3v1TUokkmCGGK5z16~8Qck7C%)3b!A8DoOy6=oF z!QWgb95q;X;o|bb^2zORCM(skIcEb~m>KloZ{VxpXfM^qzA1MN2EyB`jaoFlEhp}@ z*heRA)5W-5Gc>(s!q`>?(c9IfuhS&_|H5&?SCOz)4U@Kp8|%VkcpVo!HAzun7K;kQ zUd>vYrzu`846mmNaX^iUvtDjHVKjxB!s>z;EOyqs?iZrP{Hj;=&U&7XO3h}aL2>aR z+bT5cQfQ168grjQ)7h=i=pgkb)`>cstJ*qyJ1R3PMVVPrHRn0#gm68NB5O>N8*&MC zQiCFM!DOmF+(30n;8cNzk&2SgiY?G^<*?>iW|xK=C?knlpt2uja)FZJHLvF7`}Bp4 zK&ZU<%;OYiH6a$`a?x};OnHpN`#{#=90<9OJ;ZRidc$kf;uOWILh~FZWLGxakR|+3 zP`bD@I3`*xG(;V%dp)QygaQ}ie5B@_;_l``Wfz~I>JFz8?gk_ZN!sC1z<&YMvfKh{ zq2n$87A1v!wr~gk4Ja#OTL*%kE=$DaO^9(S!|RaP%!as&u(w>x#qb?QahLD1iR@#K zU(-A6pT?&w0hGy<3~gteT3%D^T`!8=6&oScoKfKWhz2{l2H8TW8H(a|tQGYiRmRuR delta 541 zcmY+dw%cT;IF^>8Sq>SZGg;2?o|vErJ=0Q()( zKPjLlETa}uLu!^Qq)VM?6K_xp{&sDn#&wV`KTP_)Nzj^rpzTn8b@Z`aPGZpGjTgQ7mOrQ@rCJdG#vJ=XYGw#Ie(HrF51hx gr@2&+)1*z0#GGL((U(3bomKL=((Hbrv1fIRKO^}(0ssI2 diff --git a/django/conf/locale/da/LC_MESSAGES/djangojs.po b/django/conf/locale/da/LC_MESSAGES/djangojs.po index 4db760129414..f220938ae1e4 100644 --- a/django/conf/locale/da/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/da/LC_MESSAGES/djangojs.po @@ -6,9 +6,9 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-04-26 15:49+0200\n" -"PO-Revision-Date: 2008-08-13 22:00+0200\n" -"Last-Translator: Finn Gruwier Larsen\n" +"POT-Creation-Date: 2010-08-07 11:57+0200\n" +"PO-Revision-Date: 2010-08-07 22:00+0200\n" +"Last-Translator: Finn Gruwier Larsen\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -44,13 +44,42 @@ msgstr "Foretag dit/dine valg og klik " msgid "Clear all" msgstr "Fravælg alle" -#: contrib/admin/media/js/actions.js:17 +#: contrib/admin/media/js/actions.js:18 #: contrib/admin/media/js/actions.min.js:1 msgid "%(sel)s of %(cnt)s selected" msgid_plural "%(sel)s of %(cnt)s selected" msgstr[0] "%(sel)s af %(cnt)s valgt" msgstr[1] "%(sel)s af %(cnt)s valgt" +#: contrib/admin/media/js/actions.js:109 +#: contrib/admin/media/js/actions.min.js:5 +msgid "" +"You have unsaved changes on individual editable fields. If you run an " +"action, your unsaved changes will be lost." +msgstr "" +"Du har ugemte ændringer af et eller flere redigerbare felter. Hvis du " +"udfører en handling fra drop-down-menuen, vil du miste disse ændringer." + +#: contrib/admin/media/js/actions.js:121 +#: contrib/admin/media/js/actions.min.js:6 +msgid "" +"You have selected an action, but you haven't saved your changes to " +"individual fields yet. Please click OK to save. You'll need to re-run the " +"action." +msgstr "" +"Du har valgt en handling, men du har ikke gemt dine ændringer til et eller " +"flere felter. Klik venligst OK for at gemme og vælg dernæst handlingen igen." + +#: contrib/admin/media/js/actions.js:123 +#: contrib/admin/media/js/actions.min.js:6 +msgid "" +"You have selected an action, and you haven't made any changes on individual " +"fields. You're probably looking for the Go button rather than the Save " +"button." +msgstr "" +"Du har valgt en handling, og du har ikke udført nogen ændringer på felter. " +"Det, du søger er formentlig Udfør-knappen i stedet for Gem-knappen." + #: contrib/admin/media/js/calendar.js:24 #: contrib/admin/media/js/dateparse.js:32 msgid "" From 7c05d23af162ba0481d2abb28022fd43a2e0bd4a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 14:27:15 +0000 Subject: [PATCH 056/902] [1.2.X] Fixed #11800 -- Updated Sphinx metadata in queryset docs. Thanks to timo for the patch. Backport of r13548 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13549 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/admin/index.txt | 15 +++++++-------- docs/ref/models/querysets.txt | 11 ----------- docs/releases/1.1-beta-1.txt | 23 ++++++++++++----------- docs/releases/1.1.txt | 5 +++-- docs/topics/db/aggregation.txt | 2 +- docs/topics/db/optimization.txt | 13 +++++++------ docs/topics/db/sql.txt | 6 +++--- 7 files changed, 33 insertions(+), 42 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index d527b30ced77..9a4a12af87c4 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -474,17 +474,16 @@ change list page. By default, this is set to ``100``. .. attribute:: ModelAdmin.list_select_related -Set ``list_select_related`` to tell Django to use ``select_related()`` in -retrieving the list of objects on the admin change list page. This can save you -a bunch of database queries. +Set ``list_select_related`` to tell Django to use +:meth:`~django.db.models.QuerySet.select_related` in retrieving the list of +objects on the admin change list page. This can save you a bunch of database +queries. The value should be either ``True`` or ``False``. Default is ``False``. -Note that Django will use ``select_related()``, regardless of this setting, -if one of the ``list_display`` fields is a ``ForeignKey``. - -For more on ``select_related()``, see -:ref:`the select_related() docs `. +Note that Django will use :meth:`~django.db.models.QuerySet.select_related`, +regardless of this setting, if one of the ``list_display`` fields is a +``ForeignKey``. .. attribute:: ModelAdmin.inlines diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 56ff6444e4ba..91d14150435d 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -332,8 +332,6 @@ a model which defines a default ordering, or when using ordering was undefined prior to calling ``reverse()``, and will remain undefined afterward). -.. _queryset-distinct: - ``distinct()`` ~~~~~~~~~~~~~~ @@ -367,9 +365,6 @@ query spans multiple tables, it's possible to get duplicate results when a ``values()`` together, be careful when ordering by fields not in the ``values()`` call. - -.. _queryset-values: - ``values(*fields)`` ~~~~~~~~~~~~~~~~~~~ @@ -679,8 +674,6 @@ related object. ``OneToOneFields`` will not be traversed in the reverse direction if you are performing a depth-based ``select_related``. -.. _queryset-extra: - ``extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -843,8 +836,6 @@ of the arguments is required, but you should use at least one of them. Entry.objects.extra(where=['headline=%s'], params=['Lennon']) -.. _queryset-defer: - ``defer(*fields)`` ~~~~~~~~~~~~~~~~~~ @@ -1143,8 +1134,6 @@ Example:: If you pass ``in_bulk()`` an empty list, you'll get an empty dictionary. -.. _queryset-iterator: - ``iterator()`` ~~~~~~~~~~~~~~ diff --git a/docs/releases/1.1-beta-1.txt b/docs/releases/1.1-beta-1.txt index a433efc33cfa..e7dcb4633ddd 100644 --- a/docs/releases/1.1-beta-1.txt +++ b/docs/releases/1.1-beta-1.txt @@ -71,8 +71,9 @@ processing to convert them to Python objects. If you know you don't need those particular fields, you can now tell Django not to retrieve them from the database. -You'll do this with the :ref:`new queryset methods ` -``defer()`` and ``only()``. +You'll do this with the new queryset methods +:meth:`~django.db.models.QuerySet.defer` and +:meth:`~django.db.models.QuerySet.only`. New admin features ------------------ @@ -108,13 +109,13 @@ A couple of small but very useful improvements have been made to the * The test :class:`Client` now can automatically follow redirects with the ``follow`` argument to :meth:`Client.get` and :meth:`Client.post`. This makes testing views that issue redirects simpler. - + * It's now easier to get at the template context in the response returned the test client: you'll simply access the context as ``request.context[key]``. The old way, which treats ``request.context`` as a list of contexts, one for each rendered template, is still available if you need it. - + Conditional view processing --------------------------- @@ -133,23 +134,23 @@ release, including: * The :djadmin:`dumpdata` management command now accepts individual model names as arguments, allowing you to export the data just from particular models. - + * There's a new :tfilter:`safeseq` template filter which works just like :tfilter:`safe` for lists, marking each item in the list as safe. - + * :ref:`Cache backends ` now support ``incr()`` and ``decr()`` commands to increment and decrement the value of a cache key. On cache backends that support atomic increment/decrement -- most notably, the memcached backend -- these operations will be atomic, and quite fast. - + * Django now can :ref:`easily delegate authentication to the web server ` via a new authentication backend that supports the standard ``REMOTE_USER`` environment variable used for this purpose. - + * There's a new :func:`django.shortcuts.redirect` function that makes it easier to issue redirects given an object, a view name, or a URL. - + * The ``postgresql_psycopg2`` backend now supports :ref:`native PostgreSQL autocommit `. This is an advanced, PostgreSQL-specific feature, that can make certain read-heavy applications a good deal @@ -183,7 +184,7 @@ central place to search for open issues: * http://code.djangoproject.com/timeline Please open new tickets if no existing ticket corresponds to a problem you're -running into. +running into. Additionally, discussion of Django development, including progress toward the 1.1 release, takes place daily on the django-developers mailing list: @@ -195,7 +196,7 @@ interested in helping out with Django's development, feel free to join the discussions there. Django's online documentation also includes pointers on how to contribute to -Django: +Django: * :ref:`How to contribute to Django ` diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index edb7cf1af227..30ef9197c75d 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -258,8 +258,9 @@ processing to convert them to Python objects. If you know you don't need those particular fields, you can now tell Django not to retrieve them from the database. -You'll do this with the :ref:`new queryset methods ` -``defer()`` and ``only()``. +You'll do this with the new queryset methods +:meth:`~django.db.models.QuerySet.defer` and +:meth:`~django.db.models.QuerySet.only`. Testing improvements -------------------- diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index 087c1bf23948..41580c94b6be 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -353,7 +353,7 @@ without any harmful effects, since that is already playing a role in the query. This behavior is the same as that noted in the queryset documentation for -:ref:`distinct() ` and the general rule is the same: +:meth:`~django.db.models.QuerySet.distinct` and the general rule is the same: normally you won't want extra columns playing a part in the result, so clear out the ordering, or at least make sure it's restricted only to those fields you also select in a ``values()`` call. diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 5d74fc9ce98d..bb40139f23d6 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -101,7 +101,7 @@ Use ``iterator()`` When you have a lot of objects, the caching behaviour of the ``QuerySet`` can cause a large amount of memory to be used. In this case, -:ref:`QuerySet.iterator() ` may help. +:meth:`~django.db.models.QuerySet.iterator()` may help. Do database work in the database rather than in Python ====================================================== @@ -121,9 +121,9 @@ If these aren't enough to generate the SQL you need: Use ``QuerySet.extra()`` ------------------------ -A less portable but more powerful method is :ref:`QuerySet.extra() -`, which allows some SQL to be explicitly added to the query. -If that still isn't powerful enough: +A less portable but more powerful method is +:meth:`~django.db.models.QuerySet.extra()`, which allows some SQL to be +explicitly added to the query. If that still isn't powerful enough: Use raw SQL ----------- @@ -159,7 +159,7 @@ Use ``QuerySet.values()`` and ``values_list()`` ----------------------------------------------- When you just want a dict/list of values, and don't need ORM model objects, make -appropriate usage of :ref:`QuerySet.values() `. +appropriate usage of :meth:`~django.db.models.QuerySet.values()`. These can be useful for replacing model objects in template code - as long as the dicts you supply have the same attributes as those used in the template, you are fine. @@ -167,7 +167,8 @@ are fine. Use ``QuerySet.defer()`` and ``only()`` --------------------------------------- -Use :ref:`defer() and only() ` if there are database columns you +Use :meth:`~django.db.models.QuerySet.defer()` and +:meth:`~django.db.models.QuerySet.only()` if there are database columns you know that you won't need (or won't need in most cases) to avoid loading them. Note that if you *do* use them, the ORM will have to go and get them in a separate query, making this a pessimization if you use it inappropriately. diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index f55a1643733e..c3272da7571d 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -116,9 +116,9 @@ Fields may also be left out:: >>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person') -The ``Person`` objects returned by this query will be :ref:`deferred -` model instances. This means that the fields that are omitted -from the query will be loaded on demand. For example:: +The ``Person`` objects returned by this query will be deferred model instances +(see :meth:`~django.db.models.QuerySet.defer()`). This means that the fields +that are omitted from the query will be loaded on demand. For example:: >>> for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'): ... print p.first_name, # This will be retrieved by the original query From 0fda7c8a2bf1c21bcd9b971d9843af1f52ca14c8 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 15:00:03 +0000 Subject: [PATCH 057/902] [1.2.X] Fixed #11748 -- Clarified the ways that search_field can be used. Thanks to Michael Richardson for the patch. Backport of r13550 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13557 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/admin/index.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 9a4a12af87c4..a59d16806669 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -594,8 +594,8 @@ This should be set to a list of field names that will be searched whenever somebody submits a search query in that text box. These fields should be some kind of text field, such as ``CharField`` or -``TextField``. You can also perform a related lookup on a ``ForeignKey`` with -the lookup API "follow" notation:: +``TextField``. You can also perform a related lookup on a ``ForeignKey`` or +``ManyToManyField`` with the lookup API "follow" notation:: search_fields = ['foreign_key__related_fieldname'] From 15ca40d555ee75ea3c2e30c142cec3370c5ce88c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 15:00:29 +0000 Subject: [PATCH 058/902] [1.2.X] Fixed #13627 -- Added instructions on how to reset your Trac password. Thanks to zerok for the suggestion. Backport of r13551 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13558 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/contributing.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index c555f205b10b..41d1cffdb232 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -145,7 +145,11 @@ and time availability), claim it by following these steps: * Claim the ticket by clicking the radio button next to "Accept ticket" near the bottom of the page, then clicking "Submit changes." +If you have an account but have forgotten your password, you can reset it +using the `password reset page`_. + .. _Create an account: http://www.djangoproject.com/accounts/register/ +.. _password reset page: http://www.djangoproject.com/accounts/password/reset/ Ticket claimers' responsibility ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 9e33599bc3e2815f3d15c19a0d42f18731f49a2d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 15:00:54 +0000 Subject: [PATCH 059/902] [1.2.X] Fixed #11882 -- Added documentation for formfield_for_manytomany. Thanks to Rob Hudson, timo and Simon Meers for their work on the patch. Backport of r13552 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13559 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/admin/index.txt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index a59d16806669..1fbae3f06026 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -869,11 +869,26 @@ return a subset of objects for this foreign key field based on the user:: def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "car": kwargs["queryset"] = Car.objects.filter(owner=request.user) - return db_field.formfield(**kwargs) return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) This uses the ``HttpRequest`` instance to filter the ``Car`` foreign key field -to only the cars owned by the ``User`` instance. +to only display the cars owned by the ``User`` instance. + +.. method:: ModelAdmin.formfield_for_manytomany(self, db_field, request, **kwargs) + +.. versionadded:: 1.1 + +Like the ``formfield_for_foreignkey`` method, the ``formfield_for_manytomany`` +method can be overridden to change the default formfield for a many to many +field. For example, if an owner can own multiple cars and cars can belong +to multiple owners -- a many to many relationship -- you could filter the +``Car`` foreign key field to only display the cars owned by the ``User``:: + + class MyModelAdmin(admin.ModelAdmin): + def formfield_for_manytomany(self, db_field, request, **kwargs): + if db_field.name == "cars": + kwargs["queryset"] = Car.objects.filter(owner=request.user) + return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs) .. method:: ModelAdmin.queryset(self, request) From d176d112b9b975b1fc2de7ad1484daef62f16b6c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 15:01:17 +0000 Subject: [PATCH 060/902] [1.2.X] Fixed #11735 -- Corrected an example of FormSet subclassing. Thanks to claudep for the report. Backport of r13553 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13560 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/forms/modelforms.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index fd3edf5104e1..02cce34fbc03 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -595,8 +595,8 @@ Alternatively, you can create a subclass that sets ``self.queryset`` in class BaseAuthorFormSet(BaseModelFormSet): def __init__(self, *args, **kwargs): - self.queryset = Author.objects.filter(name__startswith='O') super(BaseAuthorFormSet, self).__init__(*args, **kwargs) + self.queryset = Author.objects.filter(name__startswith='O') Then, pass your ``BaseAuthorFormSet`` class to the factory function:: From 0ea661cb12649f0b374d598725eea5803b5689cc Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 15:01:38 +0000 Subject: [PATCH 061/902] [1.2.X] Fixed #11047 -- Clarified the explanation of arguments to GenericForeignKey. Thanks to psmith and timo for their work on the patch. Backport of r13554 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13561 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/contenttypes.txt | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index 3085bf3ee9e6..da5d934d38c2 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -324,15 +324,19 @@ same types of lookups manually:: ... object_id=b.id) [, ] -Note that if the model with a :class:`~django.contrib.contenttypes.generic.GenericForeignKey` -that you're referring to uses a non-default value for ``ct_field`` or ``fk_field`` -(e.g. the :mod:`django.contrib.comments` app uses ``ct_field="object_pk"``), -you'll need to pass ``content_type_field`` and ``object_id_field`` to -:class:`~django.contrib.contenttypes.generic.GenericRelation`.:: - - comments = generic.GenericRelation(Comment, content_type_field="content_type", object_id_field="object_pk") - -Note that if you delete an object that has a +Note that if the model in a +:class:`~django.contrib.contenttypes.generic.GenericRelation` uses a +non-default value for ``ct_field`` or ``fk_field`` in its +:class:`~django.contrib.contenttypes.generic.GenericForeignKey` (e.g. the +:mod:`django.contrib.comments` app uses ``ct_field="object_pk"``), +you'll need to set ``content_type_field`` and/or ``object_id_field`` in +the :class:`~django.contrib.contenttypes.generic.GenericRelation` to +match the ``ct_field`` and ``fk_field``, respectively, in the +:class:`~django.contrib.contenttypes.generic.GenericForeignKey`:: + + comments = generic.GenericRelation(Comment, object_id_field="object_pk") + +Note also, that if you delete an object that has a :class:`~django.contrib.contenttypes.generic.GenericRelation`, any objects which have a :class:`~django.contrib.contenttypes.generic.GenericForeignKey` pointing at it will be deleted as well. In the example above, this means that From c7c59bc24f6b680277c05e4b631d9e4a69f44473 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 15:02:01 +0000 Subject: [PATCH 062/902] [1.2.X] Fixed #11021 -- Clarified newline stripping behavior in the truncatewords and truncatewords_html filters. Thanks to Ben Spaulding for the report and patch. Backport of r13555 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13562 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/defaultfilters.py | 4 ++++ django/utils/text.py | 12 +++++++++--- docs/ref/templates/builtins.txt | 4 ++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index d8e7e91efe4a..1e58ff71ca46 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -256,6 +256,8 @@ def truncatewords(value, arg): Truncates a string after a certain number of words. Argument: Number of words to truncate after. + + Newlines within the string are removed. """ from django.utils.text import truncate_words try: @@ -271,6 +273,8 @@ def truncatewords_html(value, arg): Truncates HTML after a certain number of words. Argument: Number of words to truncate after. + + Newlines in the HTML are preserved. """ from django.utils.text import truncate_html_words try: diff --git a/django/utils/text.py b/django/utils/text.py index 5d633b7afe06..b05460486dab 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -39,7 +39,10 @@ def _generator(): def truncate_words(s, num, end_text='...'): """Truncates a string after a certain number of words. Takes an optional argument of what should be used to notify that the string has been - truncated, defaults to ellipsis (...)""" + truncated, defaulting to ellipsis (...) + + Newlines in the string will be stripped. + """ s = force_unicode(s) length = int(num) words = s.split() @@ -51,10 +54,13 @@ def truncate_words(s, num, end_text='...'): truncate_words = allow_lazy(truncate_words, unicode) def truncate_html_words(s, num, end_text='...'): - """Truncates html to a certain number of words (not counting tags and + """Truncates HTML to a certain number of words (not counting tags and comments). Closes opened tags if they were correctly closed in the given html. Takes an optional argument of what should be used to notify that the - string has been truncated, defaults to ellipsis (...).""" + string has been truncated, defaulting to ellipsis (...). + + Newlines in the HTML are preserved. + """ s = force_unicode(s) length = int(num) if length <= 0: diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 002aa3f4168e..0cf445cab7ab 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1883,6 +1883,8 @@ For example:: If ``value`` is ``"Joel is a slug"``, the output will be ``"Joel is ..."``. +Newlines within the string will be removed. + .. templatefilter:: truncatewords_html truncatewords_html @@ -1902,6 +1904,8 @@ For example:: If ``value`` is ``"

        Joel is a slug

        "``, the output will be ``"

        Joel is ...

        "``. +Newlines in the HTML content will be preserved. + .. templatefilter:: unordered_list unordered_list From ff51df013a31af1e0bdf9f8e65aca98e42ac04a9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 7 Aug 2010 15:02:21 +0000 Subject: [PATCH 063/902] [1.2.X] Fixed #10952 -- Corrected the documentation for the behavior of password reset forms and views. Thanks to danielhepper for the report and initial patch, timo for the patch update. Backport of r13556 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13563 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index eae07ed71715..480509961b0c 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -898,8 +898,9 @@ includes a few other useful built-in views located in .. function:: views.password_reset(request[, is_admin_site, template_name, email_template_name, password_reset_form, token_generator, post_reset_redirect]) - Allows a user to reset their password, and sends them the new password - in an e-mail. + Allows a user to reset their password by generating a one-time use link + that can be used to reset the password, and sending that link to the + user's registered e-mail address. **Optional arguments:** @@ -1005,8 +1006,8 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`: .. class:: PasswordResetForm - A form for resetting a user's password and e-mailing the new password to - them. + A form for generating and e-mailing a one-time use link to reset a + user's password. .. class:: SetPasswordForm From 653da2dcebbeb39359bb01409fa1faca1b1a598b Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Thu, 12 Aug 2010 10:50:22 +0000 Subject: [PATCH 064/902] [1.2.X] Fixed #14100: Corrected spelling error in description of user_email. Thanks tom_simpson. r13574 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13575 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/comments/models.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/comments/models.txt b/docs/ref/contrib/comments/models.txt index af85d68f00a8..51aa117a1ca6 100644 --- a/docs/ref/contrib/comments/models.txt +++ b/docs/ref/contrib/comments/models.txt @@ -51,7 +51,7 @@ The built-in comment models .. attribute:: user_email - The email of the user who posteed the comment. + The email of the user who posted the comment. .. attribute:: user_url From 8a006e18199607825b1b96d46071e9b049155869 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 14 Aug 2010 12:07:25 +0000 Subject: [PATCH 065/902] [1.2.X] Fixed #13679, #13231, #7287 -- Ensured that models that have ForeignKeys/ManyToManyField can use a a callable default that returns a model instance/queryset. #13679 was a regression in behavior; the other two tickets are pleasant side effects. Thanks to 3point2 for the report. Backport of r13577 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13578 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/forms/fields.py | 3 + django/forms/forms.py | 12 ++-- django/forms/models.py | 24 ++++--- django/forms/widgets.py | 17 ++--- tests/regressiontests/forms/models.py | 100 ++++++++++++++++++++++---- 5 files changed, 122 insertions(+), 34 deletions(-) diff --git a/django/forms/fields.py b/django/forms/fields.py index 0bae4ba15740..f6d9c4db54da 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -127,6 +127,9 @@ def __init__(self, required=True, widget=None, label=None, initial=None, self.validators = self.default_validators + validators + def prepare_value(self, value): + return value + def to_python(self, value): return value diff --git a/django/forms/forms.py b/django/forms/forms.py index b3718efa9a6f..46e97ef5b994 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -18,10 +18,10 @@ NON_FIELD_ERRORS = '__all__' def pretty_name(name): - """Converts 'first_name' to 'First name'""" - if not name: - return u'' - return name.replace('_', ' ').capitalize() + """Converts 'first_name' to 'First name'""" + if not name: + return u'' + return name.replace('_', ' ').capitalize() def get_declared_fields(bases, attrs, with_base_fields=True): """ @@ -423,6 +423,7 @@ def as_widget(self, widget=None, attrs=None, only_initial=False): """ if not widget: widget = self.field.widget + attrs = attrs or {} auto_id = self.auto_id if auto_id and 'id' not in attrs and 'id' not in widget.attrs: @@ -430,6 +431,7 @@ def as_widget(self, widget=None, attrs=None, only_initial=False): attrs['id'] = auto_id else: attrs['id'] = self.html_initial_id + if not self.form.is_bound: data = self.form.initial.get(self.name, self.field.initial) if callable(data): @@ -439,6 +441,8 @@ def as_widget(self, widget=None, attrs=None, only_initial=False): data = self.form.initial.get(self.name, self.field.initial) else: data = self.data + data = self.field.prepare_value(data) + if not only_initial: name = self.html_name else: diff --git a/django/forms/models.py b/django/forms/models.py index 8accd61f30b9..a14a09f5534b 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -906,12 +906,7 @@ def __len__(self): return len(self.queryset) def choice(self, obj): - if self.field.to_field_name: - key = obj.serializable_value(self.field.to_field_name) - else: - key = obj.pk - return (key, self.field.label_from_instance(obj)) - + return (self.field.prepare_value(obj), self.field.label_from_instance(obj)) class ModelChoiceField(ChoiceField): """A ChoiceField whose choices are a model QuerySet.""" @@ -971,8 +966,8 @@ def _get_choices(self): return self._choices # Otherwise, execute the QuerySet in self.queryset to determine the - # choices dynamically. Return a fresh QuerySetIterator that has not been - # consumed. Note that we're instantiating a new QuerySetIterator *each* + # choices dynamically. Return a fresh ModelChoiceIterator that has not been + # consumed. Note that we're instantiating a new ModelChoiceIterator *each* # time _get_choices() is called (and, thus, each time self.choices is # accessed) so that we can ensure the QuerySet has not been consumed. This # construct might look complicated but it allows for lazy evaluation of @@ -981,6 +976,14 @@ def _get_choices(self): choices = property(_get_choices, ChoiceField._set_choices) + def prepare_value(self, value): + if hasattr(value, '_meta'): + if self.to_field_name: + return value.serializable_value(self.to_field_name) + else: + return value.pk + return super(ModelChoiceField, self).prepare_value(value) + def to_python(self, value): if value in EMPTY_VALUES: return None @@ -1030,3 +1033,8 @@ def clean(self, value): if force_unicode(val) not in pks: raise ValidationError(self.error_messages['invalid_choice'] % val) return qs + + def prepare_value(self, value): + if hasattr(value, '__iter__'): + return [super(ModelMultipleChoiceField, self).prepare_value(v) for v in value] + return super(ModelMultipleChoiceField, self).prepare_value(value) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 1480527d9d2a..c2293ba837fe 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -450,13 +450,14 @@ def render(self, name, value, attrs=None, choices=()): output.append(u'') return mark_safe(u'\n'.join(output)) + def render_option(self, selected_choices, option_value, option_label): + option_value = force_unicode(option_value) + selected_html = (option_value in selected_choices) and u' selected="selected"' or '' + return u'' % ( + escape(option_value), selected_html, + conditional_escape(force_unicode(option_label))) + def render_options(self, choices, selected_choices): - def render_option(option_value, option_label): - option_value = force_unicode(option_value) - selected_html = (option_value in selected_choices) and u' selected="selected"' or '' - return u'' % ( - escape(option_value), selected_html, - conditional_escape(force_unicode(option_label))) # Normalize to strings. selected_choices = set([force_unicode(v) for v in selected_choices]) output = [] @@ -464,10 +465,10 @@ def render_option(option_value, option_label): if isinstance(option_label, (list, tuple)): output.append(u'' % escape(force_unicode(option_value))) for option in option_label: - output.append(render_option(*option)) + output.append(self.render_option(selected_choices, *option)) output.append(u'') else: - output.append(render_option(option_value, option_label)) + output.append(self.render_option(selected_choices, option_value, option_label)) return u'\n'.join(output) class NullBooleanSelect(Select): diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py index 229c50556e8b..028ff9bad2a9 100644 --- a/tests/regressiontests/forms/models.py +++ b/tests/regressiontests/forms/models.py @@ -38,11 +38,28 @@ class ChoiceOptionModel(models.Model): Can't reuse ChoiceModel because error_message tests require that it have no instances.""" name = models.CharField(max_length=10) + class Meta: + ordering = ('name',) + + def __unicode__(self): + return u'ChoiceOption %d' % self.pk + class ChoiceFieldModel(models.Model): """Model with ForeignKey to another model, for testing ModelForm generation with ModelChoiceField.""" choice = models.ForeignKey(ChoiceOptionModel, blank=False, - default=lambda: ChoiceOptionModel.objects.all()[0]) + default=lambda: ChoiceOptionModel.objects.get(name='default')) + choice_int = models.ForeignKey(ChoiceOptionModel, blank=False, related_name='choice_int', + default=lambda: 1) + + multi_choice = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='multi_choice', + default=lambda: ChoiceOptionModel.objects.filter(name='default')) + multi_choice_int = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='multi_choice_int', + default=lambda: [1]) + +class ChoiceFieldForm(django_forms.ModelForm): + class Meta: + model = ChoiceFieldModel class FileModel(models.Model): file = models.FileField(storage=temp_storage, upload_to='tests') @@ -74,6 +91,74 @@ def test_choices_not_fetched_when_not_rendering(self): # only one query is required to pull the model from DB self.assertEqual(initial_queries+1, len(connection.queries)) +class ModelFormCallableModelDefault(TestCase): + def test_no_empty_option(self): + "If a model's ForeignKey has blank=False and a default, no empty option is created (Refs #10792)." + option = ChoiceOptionModel.objects.create(name='default') + + choices = list(ChoiceFieldForm().fields['choice'].choices) + self.assertEquals(len(choices), 1) + self.assertEquals(choices[0], (option.pk, unicode(option))) + + def test_callable_initial_value(self): + "The initial value for a callable default returning a queryset is the pk (refs #13769)" + obj1 = ChoiceOptionModel.objects.create(id=1, name='default') + obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2') + obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3') + self.assertEquals(ChoiceFieldForm().as_p(), """

        +

        +

        Hold down "Control", or "Command" on a Mac, to select more than one.

        +

        Hold down "Control", or "Command" on a Mac, to select more than one.

        """) + + def test_initial_instance_value(self): + "Initial instances for model fields may also be instances (refs #7287)" + obj1 = ChoiceOptionModel.objects.create(id=1, name='default') + obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2') + obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3') + self.assertEquals(ChoiceFieldForm(initial={ + 'choice': obj2, + 'choice_int': obj2, + 'multi_choice': [obj2,obj3], + 'multi_choice_int': ChoiceOptionModel.objects.exclude(name="default"), + }).as_p(), """

        +

        +

        + Hold down "Control", or "Command" on a Mac, to select more than one.

        +

        + Hold down "Control", or "Command" on a Mac, to select more than one.

        """) + __test__ = {'API_TESTS': """ >>> from django.forms.models import ModelForm @@ -155,18 +240,5 @@ def test_choices_not_fetched_when_not_rendering(self): datetime.date(1999, 3, 2) >>> shutil.rmtree(temp_storage_location) -In a ModelForm with a ModelChoiceField, if the model's ForeignKey has blank=False and a default, -no empty option is created (regression test for #10792). - -First we need at least one instance of ChoiceOptionModel: - ->>> ChoiceOptionModel.objects.create(name='default') - - ->>> class ChoiceFieldForm(ModelForm): -... class Meta: -... model = ChoiceFieldModel ->>> list(ChoiceFieldForm().fields['choice'].choices) -[(1, u'ChoiceOptionModel object')] """} From e4a8b6c3f9991f1c503d201f15d5f9ae6849ed25 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 14 Aug 2010 13:02:03 +0000 Subject: [PATCH 066/902] [1.2.X] Corrected some alphabetization and dupe issues in the AUTHORS file. Backport of r13586 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13587 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0a7f9db204a0..545111877ceb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,7 +29,6 @@ answer newbie questions, and generally made Django that much better: Gisle Aas ajs alang@bright-green.com - Alcides Fonseca Andi Albrecht Marty Alchin Ahmad Alhashemi @@ -39,7 +38,6 @@ answer newbie questions, and generally made Django that much better: AgarFu Dagur Páll Ammendrup Collin Anderson - Nicolas Lara Jeff Anderson Marian Andre Andreas @@ -85,19 +83,19 @@ answer newbie questions, and generally made Django that much better: Sean Brant Andrew Brehaut brut.alll@gmail.com + bthomas btoll@bestweb.net Jonathan Buchanan Keith Bussell + C8E Chris Cahoon Juan Manuel Caicedo Trevor Caira Brett Cannon Ricardo Javier Cárdenes Medina Jeremy Carbaugh - Carl Meyer Graham Carlyle Antonio Cavedoni - C8E cedric@terramater.net Chris Chamberlin Amit Chakradeo @@ -137,6 +135,7 @@ answer newbie questions, and generally made Django that much better: Rajesh Dhawan Sander Dijkhuis Jordan Dimov + Nebojša Dorđević dne@mayonnaise.net dready Maximillian Dornseif @@ -167,7 +166,6 @@ answer newbie questions, and generally made Django that much better: Liang Feng Bill Fenner Stefane Fermgier - Afonso Fernández Nogueira J. Pablo Fernandez Maciej Fijalkowski Ben Firshman @@ -175,6 +173,7 @@ answer newbie questions, and generally made Django that much better: Eric Floehr Eric Florenzano Vincent Foley + Alcides Fonseca Rudolph Froger Jorge Gajon gandalf@owca.info @@ -275,11 +274,11 @@ answer newbie questions, and generally made Django that much better: kurtiss@meetro.com Denis Kuzmichyov Panos Laganakos - Lakin Wecker Nick Lane Stuart Langridge Paul Lanier David Larlet + Nicolas Lara Nicola Larosa Finn Gruwier Larsen Lau Bech Lauritzen @@ -300,7 +299,6 @@ answer newbie questions, and generally made Django that much better: Simon Litchfield Daniel Lindsley Trey Long - msaelices Martin Mahner Matt McClanahan Stanislaus Madueke @@ -313,20 +311,21 @@ answer newbie questions, and generally made Django that much better: Petr Marhoun Petar Marić Nuno Mariz - Marijn Vriens mark@junklight.com Orestis Markou Takashi Matsuo Yasushi Masuda mattycakes@gmail.com + Glenn Maynard Jason McBrayer Kevin McConnell mccutchen@gmail.com + michael.mcewan@gmail.com Paul McLanahan Tobias McNulty Zain Memon Christian Metts - michael.mcewan@gmail.com + Carl Meyer michal@plovarna.cz Slawek Mikula mitakummaa@gmail.com @@ -336,13 +335,13 @@ answer newbie questions, and generally made Django that much better: Aljosa Mohorovic Ramiro Morales Eric Moritz + msaelices Gregor Müllegger Robin Munn James Murty msundstr Robert Myers Alexander Myodov - Nebojša Dorđević Doug Napoleone Gopal Narayanan Fraser Nevett @@ -370,7 +369,6 @@ answer newbie questions, and generally made Django that much better: phil.h.smith@gmail.com Gustavo Picon Michael Placentra II - Luke Plant plisk Daniel Poelzleithner polpak@yahoo.com @@ -401,7 +399,6 @@ answer newbie questions, and generally made Django that much better: Henrique Romano Armin Ronacher Daniel Roseman - Brian Rosner Rozza Oliver Rutherfurd ryankanno @@ -480,6 +477,7 @@ answer newbie questions, and generally made Django that much better: George Vilches Vlado Zachary Voase + Marijn Vriens Milton Waddams Chris Wagner Rick Wagner @@ -488,6 +486,7 @@ answer newbie questions, and generally made Django that much better: Filip Wasilewski Dan Watson Joel Watts + Lakin Wecker Chris Wesseling James Wheare Mike Wiacek @@ -509,8 +508,6 @@ answer newbie questions, and generally made Django that much better: Gasper Zejn Jarek Zgoda Cheng Zhang - Glenn Maynard - bthomas A big THANK YOU goes to: From 13953badb7dd784c07ec4617e6a28e5143462ed2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 14 Aug 2010 13:43:13 +0000 Subject: [PATCH 067/902] [1.2.X] Fixed #13796 -- Ensure that builtin tags and filters are included in admin documentation views. Backport of r13588 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13589 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admindocs/views.py | 8 +++-- tests/regressiontests/admin_views/tests.py | 37 ++++++++++++++++++++++ tests/runtests.py | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index e154c9299ae7..5bfa0f7184e9 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -54,7 +54,9 @@ def template_tag_index(request): load_all_installed_template_libraries() tags = [] - for module_name, library in template.libraries.items(): + app_libs = template.libraries.items() + builtin_libs = [(None, lib) for lib in template.builtins] + for module_name, library in builtin_libs + app_libs: for tag_name, tag_func in library.tags.items(): title, body, metadata = utils.parse_docstring(tag_func.__doc__) if title: @@ -87,7 +89,9 @@ def template_filter_index(request): load_all_installed_template_libraries() filters = [] - for module_name, library in template.libraries.items(): + app_libs = template.libraries.items() + builtin_libs = [(None, lib) for lib in template.builtins] + for module_name, library in builtin_libs + app_libs: for filter_name, filter_func in library.filters.items(): title, body, metadata = utils.parse_docstring(filter_func.__doc__) if title: diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 41aade0561ce..bb787be63882 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -2170,3 +2170,40 @@ def test_user_add_another(self): self.assertRedirects(response, '/test_admin/admin/auth/user/add/') self.assertEquals(User.objects.count(), user_count + 1) self.assertNotEquals(new_user.password, UNUSABLE_PASSWORD) + +class AdminDocsTest(TestCase): + fixtures = ['admin-views-users.xml'] + + def setUp(self): + self.client.login(username='super', password='secret') + + def tearDown(self): + self.client.logout() + + def test_tags(self): + response = self.client.get('/test_admin/admin/doc/tags/') + + # The builtin tag group exists + self.assertContains(response, "

        Built-in tags

        ", count=2) + + # A builtin tag exists in both the index and detail + self.assertContains(response, '

        autoescape

        ') + self.assertContains(response, '
      • autoescape
      • ') + + # An app tag exists in both the index and detail + # The builtin tag group exists + self.assertContains(response, "

        admin_list

        ", count=2) + + # A builtin tag exists in both the index and detail + self.assertContains(response, '

        autoescape

        ') + self.assertContains(response, '
      • admin_actions
      • ') + + def test_filters(self): + response = self.client.get('/test_admin/admin/doc/filters/') + + # The builtin filter group exists + self.assertContains(response, "

        Built-in filters

        ", count=2) + + # A builtin filter exists in both the index and detail + self.assertContains(response, '

        add

        ') + self.assertContains(response, '
      • add
      • ') diff --git a/tests/runtests.py b/tests/runtests.py index 5585f75d5ad8..cd60cabc930e 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -27,6 +27,7 @@ 'django.contrib.messages', 'django.contrib.comments', 'django.contrib.admin', + 'django.contrib.admindocs', ] def get_test_models(): From 1e3ed71ca6454691ec74d1415255fe7082822e56 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 16 Aug 2010 06:51:15 +0000 Subject: [PATCH 068/902] [1.2.X] Fixed #14118 -- Removed a reference to the "Python" serializer. Backport of r13595 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13596 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/serialization.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index b99a3b925e95..ef799be6db4e 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -141,10 +141,6 @@ to install third-party Python modules: ``json`` Serializes to and from JSON_ (using a version of simplejson_ bundled with Django). - ``python`` Translates to and from "simple" Python objects (lists, dicts, - strings, etc.). Not really all that useful on its own, but - used as a base for other serializers. - ``yaml`` Serializes to YAML (YAML Ain't a Markup Language). This serializer is only available if PyYAML_ is installed. ========== ============================================================== From 14fa7f9907bae2c13c33789fb65d9cae56f6f60b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 17 Aug 2010 07:08:26 +0000 Subject: [PATCH 069/902] [1.2.X] Fixed #14102 -- Ensure that fields that have been excluded from a form aren't included in the unique_for_* checks, either. Thanks to Travis Cline for the report and fix. Backport of r13598 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13599 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/db/models/base.py | 9 ++++----- tests/modeltests/model_forms/tests.py | 4 ++++ tests/modeltests/validation/test_unique.py | 9 +++++++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 545111877ceb..69da9144ca95 100644 --- a/AUTHORS +++ b/AUTHORS @@ -108,6 +108,7 @@ answer newbie questions, and generally made Django that much better: Michal Chruszcz Can Burak Çilingir Ian Clelland + Travis Cline Russell Cloran colin@owlfish.com crankycoder@gmail.com diff --git a/django/db/models/base.py b/django/db/models/base.py index 6304e009d3d8..d1232ee4cf26 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1,6 +1,5 @@ import types import sys -import os from itertools import izip import django.db.models.manager # Imported to register signal handler. from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS @@ -16,7 +15,7 @@ from django.utils.translation import ugettext_lazy as _ import django.utils.copycompat as copy from django.utils.functional import curry, update_wrapper -from django.utils.encoding import smart_str, force_unicode, smart_unicode +from django.utils.encoding import smart_str, force_unicode from django.utils.text import get_text_list, capfirst from django.conf import settings @@ -744,11 +743,11 @@ def _get_unique_checks(self, exclude=None): continue if f.unique: unique_checks.append((model_class, (name,))) - if f.unique_for_date: + if f.unique_for_date and f.unique_for_date not in exclude: date_checks.append((model_class, 'date', name, f.unique_for_date)) - if f.unique_for_year: + if f.unique_for_year and f.unique_for_year not in exclude: date_checks.append((model_class, 'year', name, f.unique_for_year)) - if f.unique_for_month: + if f.unique_for_month and f.unique_for_month not in exclude: date_checks.append((model_class, 'month', name, f.unique_for_month)) return unique_checks, date_checks diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py index 6a5f9395cc13..c5647c714f4f 100644 --- a/tests/modeltests/model_forms/tests.py +++ b/tests/modeltests/model_forms/tests.py @@ -156,6 +156,10 @@ def test_unique_for_date(self): form = PostForm({'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p) self.assertTrue(form.is_valid()) + form = PostForm({'title': "Django 1.0 is released"}) + self.assertFalse(form.is_valid()) + self.assertEqual(len(form.errors), 1) + self.assertEqual(form.errors['posted'], [u'This field is required.']) def test_inherited_unique_for_date(self): p = Post.objects.create(title="Django 1.0 is released", diff --git a/tests/modeltests/validation/test_unique.py b/tests/modeltests/validation/test_unique.py index 1b966390c440..fb77c4d28c3d 100644 --- a/tests/modeltests/validation/test_unique.py +++ b/tests/modeltests/validation/test_unique.py @@ -40,6 +40,15 @@ def test_unique_for_date_gets_picked_up(self): ), m._get_unique_checks() ) + def test_unique_for_date_exclusion(self): + m = UniqueForDateModel() + self.assertEqual(( + [(UniqueForDateModel, ('id',))], + [(UniqueForDateModel, 'year', 'count', 'end_date'), + (UniqueForDateModel, 'month', 'order', 'end_date')] + ), m._get_unique_checks(exclude='start_date') + ) + class PerformUniqueChecksTest(unittest.TestCase): def setUp(self): # Set debug to True to gain access to connection.queries. From 6fd09c600f69db77eafd3873d616838e166490cb Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Wed, 18 Aug 2010 02:44:58 +0000 Subject: [PATCH 070/902] [1.2.X] Fixed #14127: Adding a couple of missing backticks. Thanks kishkin. r13600 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13601 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial02.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index a7ab158faa88..b8f899cedc00 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -426,7 +426,7 @@ Then, just edit the file and replace the generic Django text with your own site's name as you see fit. This template file contains lots of text like ``{% block branding %}`` -and ``{{ title }}. The ``{%`` and ``{{`` tags are part of Django's +and ``{{ title }}``. The ``{%`` and ``{{`` tags are part of Django's template language. When Django renders ``admin/base_site.html``, this template language will be evaluated to produce the final HTML page. Don't worry if you can't make any sense of the template right now -- From d9bba6b13f39da08353cf8c9f63e22cc325e397f Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 18 Aug 2010 16:32:25 +0000 Subject: [PATCH 071/902] [1.2.X] Fixed #14033 -- Fixed another problem with xrefs and Sphinx 1.X in the Django doc extension. Thanks for the report and patch, Ramiro Morales and Georg Brandl. Backport of r13602 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13603 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/_ext/djangodocs.py | 69 +++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index cee14ba6f15e..d6a6df1c7aed 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -19,6 +19,7 @@ from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.writers.html import SmartyPantsHTMLTranslator from sphinx.util.console import bold +from sphinx.util.compat import Directive def setup(app): @@ -55,38 +56,46 @@ def setup(app): parse_node = parse_django_adminopt_node, ) app.add_config_value('django_next_version', '0.0', True) - app.add_directive('versionadded', parse_version_directive, 1, (1, 1, 1)) - app.add_directive('versionchanged', parse_version_directive, 1, (1, 1, 1)) + app.add_directive('versionadded', VersionDirective) + app.add_directive('versionchanged', VersionDirective) app.add_transform(SuppressBlockquotes) app.add_builder(DjangoStandaloneHTMLBuilder) -def parse_version_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - env = state.document.settings.env - is_nextversion = env.config.django_next_version == arguments[0] - ret = [] - node = addnodes.versionmodified() - ret.append(node) - if not is_nextversion: - if len(arguments) == 1: - linktext = 'Please, see the release notes ' % (arguments[0]) - try: - xrefs = roles.XRefRole()('ref', linktext, linktext, lineno, state) # Sphinx >= 1.0 - except: - xrefs = roles.xfileref_role('ref', linktext, linktext, lineno, state) # Sphinx < 1.0 - node.extend(xrefs[0]) - node['version'] = arguments[0] - else: - node['version'] = "Development version" - node['type'] = name - if len(arguments) == 2: - inodes, messages = state.inline_text(arguments[1], lineno+1) - node.extend(inodes) - if content: - state.nested_parse(content, content_offset, node) - ret = ret + messages - env.note_versionchange(node['type'], node['version'], node, lineno) - return ret + +class VersionDirective(Directive): + has_content = True + required_arguments = 1 + optional_arguments = 1 + final_argument_whitespace = True + option_spec = {} + + def run(self): + env = self.state.document.settings.env + arg0 = self.arguments[0] + is_nextversion = env.config.django_next_version == arg0 + ret = [] + node = addnodes.versionmodified() + ret.append(node) + if not is_nextversion: + if len(self.arguments) == 1: + linktext = 'Please, see the release notes ' % (arg0) + try: + xrefs = roles.XRefRole()('std:ref', linktext, linktext, self.lineno, self.state) # Sphinx >= 1.0 + except AttributeError: + xrefs = roles.xfileref_role('ref', linktext, linktext, self.lineno, self.state) # Sphinx < 1.0 + node.extend(xrefs[0]) + node['version'] = arg0 + else: + node['version'] = "Development version" + node['type'] = self.name + if len(self.arguments) == 2: + inodes, messages = self.state.inline_text(self.arguments[1], self.lineno+1) + node.extend(inodes) + if self.content: + self.state.nested_parse(self.content, self.content_offset, node) + ret = ret + messages + env.note_versionchange(node['type'], node['version'], node, self.lineno) + return ret class SuppressBlockquotes(transforms.Transform): @@ -185,7 +194,7 @@ def parse_django_adminopt_node(env, sig, signode): """A copy of sphinx.directives.CmdoptionDesc.parse_signature()""" try: from sphinx.domains.std import option_desc_re # Sphinx >= 1.0 - except: + except ImportError: from sphinx.directives.desc import option_desc_re # Sphinx < 1.0 count = 0 firstname = '' From 558887052947e2c18786c3013620cbca299bae5f Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 18 Aug 2010 16:35:48 +0000 Subject: [PATCH 072/902] [1.2.X] Fixed #14111 -- Updated Sphinx version recommendation in documentation. Thanks, Ramiro Morales and Paul McMillan. Backport of r13604 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13605 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/documentation.txt | 2 +- docs/intro/whatsnext.txt | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/internals/documentation.txt b/docs/internals/documentation.txt index 9aa8551266b6..d12e35e19547 100644 --- a/docs/internals/documentation.txt +++ b/docs/internals/documentation.txt @@ -18,7 +18,7 @@ Sphinx -- ``easy_install Sphinx`` should do the trick. .. note:: Generation of the Django documentation will work with Sphinx version 0.6 - or newer, but we recommend going straigh to Sphinx 1.0 or newer. + or newer, but we recommend going straigh to Sphinx 1.0.2 or newer. Then, building the html is easy; just ``make html`` from the ``docs`` directory. diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index 0949b2299e94..c18251f92f6d 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -187,11 +187,10 @@ You can get a local copy of the HTML documentation following a few easy steps: * The HTML documentation will be placed in ``docs/_build/html``. -.. warning:: +.. note:: - At the time of this writing, Django's using a version of Sphinx not - yet released, so you'll currently need to install Sphinx from the - source. We'll fix this shortly. + Generation of the Django documentation will work with Sphinx version 0.6 + or newer, but we recommend going straight to Sphinx 1.0.2 or newer. __ http://sphinx.pocoo.org/ __ http://www.gnu.org/software/make/ From c9016c15a9a8d16f8ae557c869e23db28fc5b690 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 19 Aug 2010 13:12:52 +0000 Subject: [PATCH 073/902] =?UTF-8?q?[1.2.X]=20Fixed=20#14123=20--=20Made=20?= =?UTF-8?q?AdminDocs=20tests=20optional,=20based=20on=20the=20availability?= =?UTF-8?q?=20of=20docutils.=20Thanks=20to=20PaulM=20for=20the=20original?= =?UTF-8?q?=20report,=20and=20=C5=81ukasz=20Rekucki=20for=20narrowing=20do?= =?UTF-8?q?wn=20the=20cause.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport of r13606 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13607 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/admin_views/tests.py | 59 ++++++++++++---------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index bb787be63882..f5a54f3f6cd3 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -2171,39 +2171,46 @@ def test_user_add_another(self): self.assertEquals(User.objects.count(), user_count + 1) self.assertNotEquals(new_user.password, UNUSABLE_PASSWORD) -class AdminDocsTest(TestCase): - fixtures = ['admin-views-users.xml'] +try: + # If docutils isn't installed, skip the AdminDocs tests. + import docutils - def setUp(self): - self.client.login(username='super', password='secret') + class AdminDocsTest(TestCase): + fixtures = ['admin-views-users.xml'] - def tearDown(self): - self.client.logout() + def setUp(self): + self.client.login(username='super', password='secret') + + def tearDown(self): + self.client.logout() + + def test_tags(self): + response = self.client.get('/test_admin/admin/doc/tags/') - def test_tags(self): - response = self.client.get('/test_admin/admin/doc/tags/') + # The builtin tag group exists + self.assertContains(response, "

        Built-in tags

        ", count=2) - # The builtin tag group exists - self.assertContains(response, "

        Built-in tags

        ", count=2) + # A builtin tag exists in both the index and detail + self.assertContains(response, '

        autoescape

        ') + self.assertContains(response, '
      • autoescape
      • ') - # A builtin tag exists in both the index and detail - self.assertContains(response, '

        autoescape

        ') - self.assertContains(response, '
      • autoescape
      • ') + # An app tag exists in both the index and detail + # The builtin tag group exists + self.assertContains(response, "

        admin_list

        ", count=2) - # An app tag exists in both the index and detail - # The builtin tag group exists - self.assertContains(response, "

        admin_list

        ", count=2) + # A builtin tag exists in both the index and detail + self.assertContains(response, '

        autoescape

        ') + self.assertContains(response, '
      • admin_actions
      • ') - # A builtin tag exists in both the index and detail - self.assertContains(response, '

        autoescape

        ') - self.assertContains(response, '
      • admin_actions
      • ') + def test_filters(self): + response = self.client.get('/test_admin/admin/doc/filters/') - def test_filters(self): - response = self.client.get('/test_admin/admin/doc/filters/') + # The builtin filter group exists + self.assertContains(response, "

        Built-in filters

        ", count=2) - # The builtin filter group exists - self.assertContains(response, "

        Built-in filters

        ", count=2) + # A builtin filter exists in both the index and detail + self.assertContains(response, '

        add

        ') + self.assertContains(response, '
      • add
      • ') - # A builtin filter exists in both the index and detail - self.assertContains(response, '

        add

        ') - self.assertContains(response, '
      • add
      • ') +except ImportError: + pass From 936203434e0db40c94556c50e49c6c531e4628fb Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Thu, 19 Aug 2010 19:31:56 +0000 Subject: [PATCH 074/902] [1.2.X] Fixed #14141: docs now use the :doc: construct for links between documents. Thanks, Ramiro Morales. Backport of [13608] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13609 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/_ext/djangodocs.py | 6 +- docs/faq/admin.txt | 6 +- docs/faq/contributing.txt | 4 +- docs/faq/general.txt | 10 +- docs/faq/help.txt | 2 - docs/faq/index.txt | 2 - docs/faq/install.txt | 10 +- docs/faq/models.txt | 6 +- docs/faq/usage.txt | 2 - docs/glossary.txt | 32 +-- docs/howto/apache-auth.txt | 4 +- docs/howto/auth-remote-user.txt | 2 - docs/howto/custom-file-storage.txt | 6 +- docs/howto/custom-management-commands.txt | 8 +- docs/howto/custom-model-fields.txt | 26 ++- docs/howto/custom-template-tags.txt | 6 +- docs/howto/deployment/fastcgi.txt | 8 +- docs/howto/deployment/index.txt | 10 +- docs/howto/deployment/modpython.txt | 10 +- docs/howto/deployment/modwsgi.txt | 2 - docs/howto/error-reporting.txt | 6 +- docs/howto/i18n.txt | 4 +- docs/howto/index.txt | 4 +- docs/howto/initial-data.txt | 16 +- docs/howto/jython.txt | 2 - docs/howto/legacy-databases.txt | 4 +- docs/howto/outputting-csv.txt | 6 +- docs/howto/outputting-pdf.txt | 4 +- docs/howto/static-files.txt | 6 +- docs/index.txt | 208 ++++++++++---------- docs/internals/committers.txt | 2 - docs/internals/contributing.txt | 22 +-- docs/internals/deprecation.txt | 4 +- docs/internals/documentation.txt | 50 +++-- docs/internals/index.txt | 2 - docs/internals/release-process.txt | 2 - docs/internals/svn.txt | 16 +- docs/intro/index.txt | 2 - docs/intro/install.txt | 22 +-- docs/intro/overview.txt | 26 ++- docs/intro/tutorial01.txt | 42 ++-- docs/intro/tutorial02.txt | 8 +- docs/intro/tutorial03.txt | 16 +- docs/intro/tutorial04.txt | 22 +-- docs/intro/whatsnext.txt | 34 ++-- docs/misc/api-stability.txt | 58 +++--- docs/misc/design-philosophies.txt | 2 - docs/misc/distributions.txt | 2 - docs/misc/index.txt | 2 - docs/obsolete/admin-css.txt | 2 - docs/obsolete/index.txt | 2 - docs/ref/authbackends.txt | 8 +- docs/ref/contrib/admin/actions.txt | 4 +- docs/ref/contrib/admin/index.txt | 22 +-- docs/ref/contrib/auth.txt | 4 +- docs/ref/contrib/comments/custom.txt | 2 - docs/ref/contrib/comments/example.txt | 20 +- docs/ref/contrib/comments/forms.txt | 6 +- docs/ref/contrib/comments/index.txt | 14 +- docs/ref/contrib/comments/models.txt | 62 +++--- docs/ref/contrib/comments/moderation.txt | 2 - docs/ref/contrib/comments/settings.txt | 6 +- docs/ref/contrib/comments/signals.txt | 8 +- docs/ref/contrib/comments/upgrade.txt | 8 +- docs/ref/contrib/contenttypes.txt | 8 +- docs/ref/contrib/csrf.txt | 2 - docs/ref/contrib/databrowse.txt | 16 +- docs/ref/contrib/flatpages.txt | 10 +- docs/ref/contrib/formtools/form-preview.txt | 2 - docs/ref/contrib/formtools/form-wizard.txt | 8 +- docs/ref/contrib/formtools/index.txt | 2 - docs/ref/contrib/gis/admin.txt | 2 +- docs/ref/contrib/gis/commands.txt | 2 +- docs/ref/contrib/gis/db-api.txt | 44 ++--- docs/ref/contrib/gis/deployment.txt | 4 +- docs/ref/contrib/gis/feeds.txt | 10 +- docs/ref/contrib/gis/geoquerysets.txt | 110 +++++------ docs/ref/contrib/gis/install.txt | 204 +++++++++---------- docs/ref/contrib/gis/layermapping.txt | 92 ++++----- docs/ref/contrib/gis/measure.txt | 22 +-- docs/ref/contrib/gis/model-api.txt | 40 ++-- docs/ref/contrib/gis/testing.txt | 2 +- docs/ref/contrib/gis/tutorial.txt | 106 +++++----- docs/ref/contrib/humanize.txt | 2 - docs/ref/contrib/index.txt | 42 ++-- docs/ref/contrib/localflavor.txt | 6 +- docs/ref/contrib/messages.txt | 14 +- docs/ref/contrib/redirects.txt | 10 +- docs/ref/contrib/sitemaps.txt | 14 +- docs/ref/contrib/sites.txt | 8 +- docs/ref/contrib/syndication.txt | 18 +- docs/ref/contrib/webdesign.txt | 6 +- docs/ref/databases.txt | 6 +- docs/ref/django-admin.txt | 24 ++- docs/ref/exceptions.txt | 2 - docs/ref/files/file.txt | 6 +- docs/ref/files/index.txt | 2 - docs/ref/files/storage.txt | 2 - docs/ref/forms/api.txt | 6 +- docs/ref/forms/fields.txt | 10 +- docs/ref/forms/index.txt | 4 +- docs/ref/forms/validation.txt | 2 - docs/ref/forms/widgets.txt | 2 - docs/ref/generic-views.txt | 30 ++- docs/ref/index.txt | 2 - docs/ref/middleware.txt | 24 ++- docs/ref/models/fields.txt | 20 +- docs/ref/models/index.txt | 4 +- docs/ref/models/instances.txt | 24 ++- docs/ref/models/options.txt | 2 - docs/ref/models/querysets.txt | 22 +-- docs/ref/models/relations.txt | 2 - docs/ref/request-response.txt | 10 +- docs/ref/settings.txt | 92 +++++---- docs/ref/signals.txt | 14 +- docs/ref/templates/api.txt | 18 +- docs/ref/templates/builtins.txt | 12 +- docs/ref/templates/index.txt | 4 +- docs/ref/unicode.txt | 4 +- docs/ref/utils.txt | 6 +- docs/ref/validators.txt | 6 +- docs/releases/0.95.txt | 4 +- docs/releases/0.96.txt | 2 - docs/releases/1.0-alpha-1.txt | 8 +- docs/releases/1.0-alpha-2.txt | 16 +- docs/releases/1.0-beta-2.txt | 24 ++- docs/releases/1.0-beta.txt | 14 +- docs/releases/1.0-porting-guide.txt | 12 +- docs/releases/1.0.1.txt | 2 - docs/releases/1.0.2.txt | 6 +- docs/releases/1.0.txt | 34 ++-- docs/releases/1.1-alpha-1.txt | 22 +-- docs/releases/1.1-beta-1.txt | 20 +- docs/releases/1.1-rc-1.txt | 8 +- docs/releases/1.1.2.txt | 4 +- docs/releases/1.1.txt | 42 ++-- docs/releases/1.2-alpha-1.txt | 26 ++- docs/releases/1.2-beta-1.txt | 16 +- docs/releases/1.2-rc-1.txt | 10 +- docs/releases/1.2.txt | 62 +++--- docs/releases/index.txt | 2 - docs/topics/auth.txt | 38 ++-- docs/topics/cache.txt | 6 +- docs/topics/conditional-view-processing.txt | 2 - docs/topics/db/aggregation.txt | 6 +- docs/topics/db/index.txt | 2 - docs/topics/db/managers.txt | 6 +- docs/topics/db/models.txt | 18 +- docs/topics/db/multi-db.txt | 2 - docs/topics/db/optimization.txt | 18 +- docs/topics/db/queries.txt | 10 +- docs/topics/db/sql.txt | 6 +- docs/topics/db/transactions.txt | 4 +- docs/topics/email.txt | 2 - docs/topics/files.txt | 10 +- docs/topics/forms/formsets.txt | 1 - docs/topics/forms/index.txt | 14 +- docs/topics/forms/media.txt | 2 - docs/topics/forms/modelforms.txt | 8 +- docs/topics/generic-views.txt | 10 +- docs/topics/http/file-uploads.txt | 6 +- docs/topics/http/generic-views.txt | 4 +- docs/topics/http/index.txt | 2 - docs/topics/http/middleware.txt | 14 +- docs/topics/http/sessions.txt | 10 +- docs/topics/http/shortcuts.txt | 2 - docs/topics/http/urls.txt | 8 +- docs/topics/http/views.txt | 4 +- docs/topics/i18n/deployment.txt | 6 +- docs/topics/i18n/index.txt | 8 +- docs/topics/i18n/internationalization.txt | 6 +- docs/topics/i18n/localization.txt | 4 +- docs/topics/index.txt | 2 - docs/topics/install.txt | 22 +-- docs/topics/pagination.txt | 2 - docs/topics/serialization.txt | 2 - docs/topics/settings.txt | 8 +- docs/topics/signals.txt | 8 +- docs/topics/templates.txt | 20 +- docs/topics/testing.txt | 14 +- 180 files changed, 1222 insertions(+), 1523 deletions(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index d6a6df1c7aed..325ed76cdc23 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -78,11 +78,11 @@ def run(self): ret.append(node) if not is_nextversion: if len(self.arguments) == 1: - linktext = 'Please, see the release notes ' % (arg0) + linktext = 'Please, see the release notes ' % (arg0) try: - xrefs = roles.XRefRole()('std:ref', linktext, linktext, self.lineno, self.state) # Sphinx >= 1.0 + xrefs = roles.XRefRole()('doc', linktext, linktext, self.lineno, self.state) # Sphinx >= 1.0 except AttributeError: - xrefs = roles.xfileref_role('ref', linktext, linktext, self.lineno, self.state) # Sphinx < 1.0 + xrefs = roles.xfileref_role('doc', linktext, linktext, self.lineno, self.state) # Sphinx < 1.0 node.extend(xrefs[0]) node['version'] = arg0 else: diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt index ed705d5f21bf..8ee6cc184bb8 100644 --- a/docs/faq/admin.txt +++ b/docs/faq/admin.txt @@ -1,5 +1,3 @@ -.. _faq-admin: - FAQ: The admin ============== @@ -32,7 +30,7 @@ How can I prevent the cache middleware from caching the admin site? ------------------------------------------------------------------- Set the :setting:`CACHE_MIDDLEWARE_ANONYMOUS_ONLY` setting to ``True``. See the -:ref:`cache documentation ` for more information. +:doc:`cache documentation ` for more information. How do I automatically set a field's value to the user who last edited the object in the admin? ----------------------------------------------------------------------------------------------- @@ -91,5 +89,5 @@ We like it, but if you don't agree, you can modify the admin site's presentation by editing the CSS stylesheet and/or associated image files. The site is built using semantic HTML and plenty of CSS hooks, so any changes you'd like to make should be possible by editing the stylesheet. We've got a -:ref:`guide to the CSS used in the admin ` to get you started. +:doc:`guide to the CSS used in the admin ` to get you started. diff --git a/docs/faq/contributing.txt b/docs/faq/contributing.txt index 51a9bc2c6a83..81c06f365fa0 100644 --- a/docs/faq/contributing.txt +++ b/docs/faq/contributing.txt @@ -1,5 +1,3 @@ -.. _faq-contributing: - FAQ: Contributing code ====================== @@ -7,7 +5,7 @@ How can I get started contributing code to Django? -------------------------------------------------- Thanks for asking! We've written an entire document devoted to this question. -It's titled :ref:`Contributing to Django `. +It's titled :doc:`Contributing to Django `. I submitted a bug fix in the ticket system several weeks ago. Why are you ignoring my patch? -------------------------------------------------------------------------------------------- diff --git a/docs/faq/general.txt b/docs/faq/general.txt index 1181d261be70..1fc0f1882a93 100644 --- a/docs/faq/general.txt +++ b/docs/faq/general.txt @@ -1,5 +1,3 @@ -.. _faq-general: - FAQ: General ============ @@ -63,15 +61,15 @@ at any level -- database servers, caching servers or Web/application servers. The framework cleanly separates components such as its database layer and application layer. And it ships with a simple-yet-powerful -:ref:`cache framework `. +:doc:`cache framework `. 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 :ref:`list of committers -` +volunteers; you can read all about them over at the :doc:`list of committers +` Which sites use Django? ----------------------- @@ -146,7 +144,7 @@ philosophies 100%. Like we said: We're picky. We've documented our philosophies on the -:ref:`design philosophies page `. +:doc:`design philosophies page `. Is Django a content-management-system (CMS)? -------------------------------------------- diff --git a/docs/faq/help.txt b/docs/faq/help.txt index 5d7faf6fec8b..d84b3f529fed 100644 --- a/docs/faq/help.txt +++ b/docs/faq/help.txt @@ -1,5 +1,3 @@ -.. _faq-help: - FAQ: Getting Help ================= diff --git a/docs/faq/index.txt b/docs/faq/index.txt index d357a3ebb0e3..347cabaabcea 100644 --- a/docs/faq/index.txt +++ b/docs/faq/index.txt @@ -1,5 +1,3 @@ -.. _faq-index: - ========== Django FAQ ========== diff --git a/docs/faq/install.txt b/docs/faq/install.txt index f5feb98aff4a..3fbcb3842d92 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -1,5 +1,3 @@ -.. _faq-install: - FAQ: Installation ================= @@ -7,9 +5,9 @@ How do I get started? --------------------- #. `Download the code`_. - #. Install Django (read the :ref:`installation guide `). - #. Walk through the :ref:`tutorial `. - #. Check out the rest of the :ref:`documentation `, and `ask questions`_ if you + #. Install Django (read the :doc:`installation guide `). + #. Walk through the :doc:`tutorial `. + #. Check out the rest of the :doc:`documentation `, and `ask questions`_ if you run into trouble. .. _`Download the code`: http://www.djangoproject.com/download/ @@ -26,7 +24,7 @@ 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 own lightweight development server. For a production environment, Django follows the WSGI_ spec, which means it can run on a variety of server -platforms. See :ref:`Deploying Django ` for some +platforms. See :doc:`Deploying Django ` for some popular alternatives. Also, the `server arrangements wiki page`_ contains details for several deployment strategies. diff --git a/docs/faq/models.txt b/docs/faq/models.txt index 2732c0b8e19f..f00d453d887e 100644 --- a/docs/faq/models.txt +++ b/docs/faq/models.txt @@ -1,5 +1,3 @@ -.. _faq-models: - FAQ: Databases and models ========================= @@ -30,7 +28,7 @@ backend, and not all backends provide a way to retrieve the SQL after quoting. .. versionadded:: 1.2 -If you are using :ref:`multiple databases`, you can use the +If you are using :doc:`multiple databases`, you can use the same interface on each member of the ``connections`` dictionary:: >>> from django.db import connections @@ -39,7 +37,7 @@ same interface on each member of the ``connections`` dictionary:: Can I use Django with a pre-existing database? ---------------------------------------------- -Yes. See :ref:`Integrating with a legacy database `. +Yes. See :doc:`Integrating with a legacy database `. If I make changes to a model, how do I update the database? ----------------------------------------------------------- diff --git a/docs/faq/usage.txt b/docs/faq/usage.txt index 6c3c518bb277..856b97c35c51 100644 --- a/docs/faq/usage.txt +++ b/docs/faq/usage.txt @@ -1,5 +1,3 @@ -.. _faq-usage: - FAQ: Using Django ================= diff --git a/docs/glossary.txt b/docs/glossary.txt index 67a62ca31a1d..b8f7a6b9047a 100644 --- a/docs/glossary.txt +++ b/docs/glossary.txt @@ -9,19 +9,19 @@ Glossary field An attribute on a :term:`model`; a given field usually maps directly to a single database column. - - See :ref:`topics-db-models`. + + See :doc:`/topics/db/models`. generic view A higher-order :term:`view` function that provides an abstract/generic implementation of a common idiom or pattern found in view development. - - See :ref:`ref-generic-views`. + + See :doc:`/ref/generic-views`. model Models store your application's data. - - See :ref:`topics-db-models`. + + See :doc:`/topics/db/models`. MTV See :ref:`mtv`. @@ -41,7 +41,7 @@ Glossary property Also known as "managed attributes", and a feature of Python since version 2.2. From `the property documentation`__: - + Properties are a neat way to implement attributes whose usage resembles attribute access, but whose implementation uses method calls. [...] You @@ -56,26 +56,26 @@ Glossary queryset An object representing some set of rows to be fetched from the database. - - See :ref:`topics-db-queries`. + + See :doc:`/topics/db/queries`. slug A short label for something, containing only letters, numbers, underscores or hyphens. They're generally used in URLs. For example, in a typical blog entry URL: - + .. parsed-literal:: - + http://www.djangoproject.com/weblog/2008/apr/12/**spring**/ - + the last bit (``spring``) is the slug. template A chunk of text that acts as formatting for representing data. A template helps to abstract the presentation of data from the data itself. - - See :ref:`topics-templates`. - + + See :doc:`/topics/templates`. + view - A function responsible for rending a page. \ No newline at end of file + A function responsible for rending a page. diff --git a/docs/howto/apache-auth.txt b/docs/howto/apache-auth.txt index 8fd3da2612f9..2ebae0b736fd 100644 --- a/docs/howto/apache-auth.txt +++ b/docs/howto/apache-auth.txt @@ -1,12 +1,10 @@ -.. _howto-apache-auth: - ========================================================= Authenticating against Django's user database from Apache ========================================================= Since keeping multiple authentication databases in sync is a common problem when dealing with Apache, you can configuring Apache to authenticate against Django's -:ref:`authentication system ` directly. For example, you +:doc:`authentication system ` directly. For example, you could: * Serve static/media files directly from Apache only to authenticated users. diff --git a/docs/howto/auth-remote-user.txt b/docs/howto/auth-remote-user.txt index f0e83c0ba5e0..9dbde29e5c13 100644 --- a/docs/howto/auth-remote-user.txt +++ b/docs/howto/auth-remote-user.txt @@ -1,5 +1,3 @@ -.. _howto-auth-remote-user: - ==================================== Authentication using ``REMOTE_USER`` ==================================== diff --git a/docs/howto/custom-file-storage.txt b/docs/howto/custom-file-storage.txt index 5005feaa809c..1b0f32fb3fae 100644 --- a/docs/howto/custom-file-storage.txt +++ b/docs/howto/custom-file-storage.txt @@ -1,5 +1,3 @@ -.. _howto-custom-file-storage: - Writing a custom storage system =============================== @@ -37,7 +35,7 @@ You'll need to follow these steps: the ``path()`` method. Your custom storage system may override any of the storage methods explained in -:ref:`ref-files-storage`, but you **must** implement the following methods: +:doc:`/ref/files/storage`, but you **must** implement the following methods: * :meth:`Storage.delete` * :meth:`Storage.exists` @@ -63,7 +61,7 @@ backend storage system. Called by ``Storage.save()``. The ``name`` will already have gone through ``get_valid_name()`` and ``get_available_name()``, and the ``content`` will be a -``File`` object itself. +``File`` object itself. 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 diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 3e7af8a8de38..4a1747f68b27 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -1,5 +1,3 @@ -.. _howto-custom-management-commands: - ==================================== Writing custom django-admin commands ==================================== @@ -10,7 +8,7 @@ Applications can register their own actions with ``manage.py``. For example, you might want to add a ``manage.py`` action for a Django app that you're distributing. In this document, we will be building a custom ``closepoll`` command for the ``polls`` application from the -:ref:`tutorial`. +:doc:`tutorial`. To do this, just add a ``management/commands`` directory to the application. Each Python module in that directory will be auto-discovered and registered as @@ -70,7 +68,7 @@ The new custom command can be called using ``python manage.py closepoll The ``handle()`` method takes zero or more ``poll_ids`` and sets ``poll.opened`` to ``False`` for each one. If the user referenced any nonexistant polls, a :class:`CommandError` is raised. The ``poll.opened`` attribute does not exist -in the :ref:`tutorial` and was added to +in the :doc:`tutorial` and was added to ``polls.models.Poll`` for this example. The same ``closepoll`` could be easily modified to delete a given poll instead @@ -92,7 +90,7 @@ must be added to :attr:`~BaseCommand.option_list` like this: # ... In addition to being able to add custom command line options, all -:ref:`management commands` can accept some +:doc:`management commands` can accept some default options such as :djadminopt:`--verbosity` and :djadminopt:`--traceback`. Command objects diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 90851459c14b..fa4c07fed2f3 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -1,5 +1,3 @@ -.. _howto-custom-model-fields: - =========================== Writing custom model fields =========================== @@ -10,7 +8,7 @@ Writing custom model fields Introduction ============ -The :ref:`model reference ` documentation explains how to use +The :doc:`model reference ` documentation explains how to use Django's standard field classes -- :class:`~django.db.models.CharField`, :class:`~django.db.models.DateField`, etc. For many purposes, those classes are all you'll need. Sometimes, though, the Django version won't meet your precise @@ -109,7 +107,7 @@ What does a field class do? --------------------------- All of Django's fields (and when we say *fields* in this document, we always -mean model fields and not :ref:`form fields `) are subclasses +mean model fields and not :doc:`form fields `) are subclasses of :class:`django.db.models.Field`. Most of the information that Django records about a field is common to all fields -- name, help text, uniqueness and so forth. Storing all that information is handled by ``Field``. We'll get into the @@ -124,7 +122,7 @@ when the model class is created (the precise details of how this is done are unimportant here). This is because the field classes aren't necessary when you're just creating and modifying attributes. Instead, they provide the machinery for converting between the attribute value and what is stored in the -database or sent to the :ref:`serializer `. +database or sent to the :doc:`serializer `. Keep this in mind when creating your own custom fields. The Django ``Field`` subclass you write provides the machinery for converting between your Python @@ -209,8 +207,8 @@ parameters: * :attr:`~django.db.models.Field.default` * :attr:`~django.db.models.Field.editable` * :attr:`~django.db.models.Field.serialize`: If ``False``, the field will - not be serialized when the model is passed to Django's :ref:`serializers - `. Defaults to ``True``. + not be serialized when the model is passed to Django's :doc:`serializers + `. Defaults to ``True``. * :attr:`~django.db.models.Field.unique_for_date` * :attr:`~django.db.models.Field.unique_for_month` * :attr:`~django.db.models.Field.unique_for_year` @@ -225,8 +223,8 @@ parameters: inheritance. For advanced use only. All of the options without an explanation in the above list have the same -meaning they do for normal Django fields. See the :ref:`field documentation -` for examples and details. +meaning they do for normal Django fields. See the :doc:`field documentation +` for examples and details. The ``SubfieldBase`` metaclass ------------------------------ @@ -270,8 +268,8 @@ value. This means that whenever a value may be assigned to the field, you need to ensure that it will be of the correct datatype, or that you handle any exceptions. -This is especially important if you use :ref:`ModelForms -`. When saving a ModelForm, Django will use +This is especially important if you use :doc:`ModelForms +`. When saving a ModelForm, Django will use form values to instantiate model instances. However, if the cleaned form data can't be used as valid input to the field, the normal form validation process will break. @@ -611,8 +609,8 @@ All of the ``kwargs`` dictionary is passed directly to the form field's :meth:`~django.forms.Field__init__` method. Normally, all you need to do is set up a good default for the ``form_class`` argument and then delegate further handling to the parent class. This might require you to write a custom form -field (and even a form widget). See the :ref:`forms documentation -` for information about this, and take a look at the code in +field (and even a form widget). See the :doc:`forms documentation +` for information about this, and take a look at the code in :mod:`django.contrib.localflavor` for some examples of custom widgets. Continuing our ongoing example, we can write the :meth:`formfield` method as:: @@ -721,7 +719,7 @@ Django provides a ``File`` class, which is used as a proxy to the file's contents and operations. This can be subclassed to customize how the file is accessed, and what methods are available. It lives at ``django.db.models.fields.files``, and its default behavior is explained in the -:ref:`file documentation `. +:doc:`file documentation `. Once a subclass of ``File`` is created, the new ``FileField`` subclass must be told to use it. To do so, simply assign the new ``File`` subclass to the special diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 1406d19b588f..c4d2315bd480 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -1,5 +1,3 @@ -.. _howto-custom-template-tags: - ================================ Custom template tags and filters ================================ @@ -7,8 +5,8 @@ Custom template tags and filters Introduction ============ -Django's template system comes with a wide variety of :ref:`built-in -tags and filters ` designed to address the +Django's template system comes with a wide variety of :doc:`built-in +tags and filters ` designed to address the presentation logic needs of your application. Nevertheless, you may find yourself needing functionality that is not covered by the core set of template primitives. You can extend the template engine by diff --git a/docs/howto/deployment/fastcgi.txt b/docs/howto/deployment/fastcgi.txt index cf05174390d4..9326ee97dc7c 100644 --- a/docs/howto/deployment/fastcgi.txt +++ b/docs/howto/deployment/fastcgi.txt @@ -1,13 +1,11 @@ -.. _howto-deployment-fastcgi: - ============================================ How to use Django with FastCGI, SCGI, or AJP ============================================ .. highlight:: bash -Although the current preferred setup for running Django is :ref:`Apache with -mod_wsgi `, many people use shared hosting, on +Although the current preferred setup for running Django is :doc:`Apache with +mod_wsgi `, many people use shared hosting, on which protocols such as FastCGI, SCGI or AJP are the only viable options. In some setups, these protocols may provide better performance than mod_wsgi_. @@ -74,7 +72,7 @@ TCP socket. What you choose is a manner of preference; a TCP socket is usually easier due to permissions issues. To start your server, first change into the directory of your project (wherever -your :ref:`manage.py ` is), and then run the +your :doc:`manage.py ` is), and then run the :djadmin:`runfcgi` command:: ./manage.py runfcgi [options] diff --git a/docs/howto/deployment/index.txt b/docs/howto/deployment/index.txt index 78cfb037f538..70c2ff8bbd26 100644 --- a/docs/howto/deployment/index.txt +++ b/docs/howto/deployment/index.txt @@ -1,5 +1,3 @@ -.. _howto-deployment-index: - Deploying Django ================ @@ -10,18 +8,18 @@ ways to easily deploy Django: .. toctree:: :maxdepth: 1 - + modwsgi modpython fastcgi - + If you're new to deploying Django and/or Python, we'd recommend you try -:ref:`mod_wsgi ` first. In most cases it'll be the easiest, +:doc:`mod_wsgi ` first. In most cases it'll be the easiest, fastest, and most stable deployment choice. .. seealso:: * `Chapter 12 of The Django Book`_ discusses deployment and especially scaling in more detail. - + .. _chapter 12 of the django book: http://djangobook.com/en/2.0/chapter12/ diff --git a/docs/howto/deployment/modpython.txt b/docs/howto/deployment/modpython.txt index 143a6d5ae319..d35cac8fcd5f 100644 --- a/docs/howto/deployment/modpython.txt +++ b/docs/howto/deployment/modpython.txt @@ -1,5 +1,3 @@ -.. _howto-deployment-modpython: - ============================================ How to use Django with Apache and mod_python ============================================ @@ -8,7 +6,7 @@ How to use Django with Apache and mod_python The `mod_python`_ module for Apache_ can be used to deploy Django to a production server, although it has been mostly superseded by the simpler -:ref:`mod_wsgi deployment option `. +:doc:`mod_wsgi deployment option `. mod_python is similar to (and inspired by) `mod_perl`_ : It embeds Python within Apache and loads Python code into memory when the server starts. Code stays in @@ -25,8 +23,8 @@ Django requires Apache 2.x and mod_python 3.x, and you should use Apache's Apache, there's no better source than `Apache's own official documentation`_ - * You may also be interested in :ref:`How to use Django with FastCGI, SCGI, - or AJP `. + * You may also be interested in :doc:`How to use Django with FastCGI, SCGI, + or AJP `. .. _Apache: http://httpd.apache.org/ .. _mod_python: http://www.modpython.org/ @@ -383,7 +381,7 @@ If you get a UnicodeEncodeError =============================== If you're taking advantage of the internationalization features of Django (see -:ref:`topics-i18n`) and you intend to allow users to upload files, you must +:doc:`/topics/i18n/index`) and you intend to allow users to upload files, you must ensure that the environment used to start Apache is configured to accept non-ASCII file names. If your environment is not correctly configured, you will trigger ``UnicodeEncodeError`` exceptions when calling functions like diff --git a/docs/howto/deployment/modwsgi.txt b/docs/howto/deployment/modwsgi.txt index 12de53f53d5f..fc51f24f80d5 100644 --- a/docs/howto/deployment/modwsgi.txt +++ b/docs/howto/deployment/modwsgi.txt @@ -1,5 +1,3 @@ -.. _howto-deployment-modwsgi: - ========================================== How to use Django with Apache and mod_wsgi ========================================== diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index 97842d7263dd..1ec009dd2a27 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -1,5 +1,3 @@ -.. _howto-error-reporting: - Error reporting via e-mail ========================== @@ -30,8 +28,8 @@ the HTTP request that caused the error. to specify :setting:`EMAIL_HOST` and possibly :setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD`, though other settings may be also required depending on your mail - server's configuration. Consult :ref:`the Django settings - documentation ` for a full list of email-related + server's configuration. Consult :doc:`the Django settings + documentation ` for a full list of email-related settings. By default, Django will send email from root@localhost. However, some mail diff --git a/docs/howto/i18n.txt b/docs/howto/i18n.txt index 853162aa70b8..6bec531177cc 100644 --- a/docs/howto/i18n.txt +++ b/docs/howto/i18n.txt @@ -1,5 +1,3 @@ -.. _howto-i18n: - .. _using-translations-in-your-own-projects: =============================================== @@ -46,7 +44,7 @@ To create message files, you use the :djadmin:`django-admin.py makemessages ` to produce the binary ``.mo`` files that are used by ``gettext``. Read the -:ref:`topics-i18n-localization` document for more details. +:doc:`/topics/i18n/localization` document for more details. You can also run ``django-admin.py compilemessages --settings=path.to.settings`` to make the compiler process all the directories in your :setting:`LOCALE_PATHS` diff --git a/docs/howto/index.txt b/docs/howto/index.txt index c582c8ed171b..49d06440343a 100644 --- a/docs/howto/index.txt +++ b/docs/howto/index.txt @@ -1,11 +1,9 @@ -.. _howto-index: - "How-to" guides =============== Here you'll find short answers to "How do I....?" types of questions. These how-to guides don't cover topics in depth -- you'll find that material in the -:ref:`topics-index` and the :ref:`ref-index`. However, these guides will help +:doc:`/topics/index` and the :doc:`/ref/index`. However, these guides will help you quickly accomplish common tasks. .. toctree:: diff --git a/docs/howto/initial-data.txt b/docs/howto/initial-data.txt index b071d6d5295e..cf3f65d2990a 100644 --- a/docs/howto/initial-data.txt +++ b/docs/howto/initial-data.txt @@ -1,5 +1,3 @@ -.. _howto-initial-data: - ================================= Providing initial data for models ================================= @@ -20,10 +18,10 @@ Providing initial data with fixtures A fixture is a collection of data that Django knows how to import into a database. The most straightforward way of creating a fixture if you've already -got some data is to use the :djadmin:`manage.py dumpdata` command. Or, you can -write fixtures by hand; fixtures can be written as XML, YAML, or JSON documents. -The :ref:`serialization documentation ` has more details -about each of these supported :ref:`serialization formats +got some data is to use the :djadmin:`manage.py dumpdata ` command. +Or, you can write fixtures by hand; fixtures can be written as XML, YAML, or +JSON documents. The :doc:`serialization documentation ` +has more details about each of these supported :ref:`serialization formats `. As an example, though, here's what a fixture for a simple ``Person`` model might @@ -114,9 +112,9 @@ which will insert the desired data (e.g., properly-formatted ``INSERT`` statements separated by semicolons). The SQL files are read by the :djadmin:`sqlcustom`, :djadmin:`sqlreset`, -:djadmin:`sqlall` and :djadmin:`reset` commands in :ref:`manage.py -`. Refer to the :ref:`manage.py documentation -` for more information. +:djadmin:`sqlall` and :djadmin:`reset` commands in :doc:`manage.py +`. Refer to the :doc:`manage.py documentation +` for more information. Note that if you have multiple SQL data files, there's no guarantee of the order in which they're executed. The only thing you can assume is diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt index 385790e9e6e1..673c9360bd51 100644 --- a/docs/howto/jython.txt +++ b/docs/howto/jython.txt @@ -1,5 +1,3 @@ -.. _howto-jython: - ======================== Running Django on Jython ======================== diff --git a/docs/howto/legacy-databases.txt b/docs/howto/legacy-databases.txt index b2aa7e4ea624..2121871fa2d1 100644 --- a/docs/howto/legacy-databases.txt +++ b/docs/howto/legacy-databases.txt @@ -1,5 +1,3 @@ -.. _howto-legacy-databases: - ========================================= Integrating Django with a legacy database ========================================= @@ -9,7 +7,7 @@ possible to integrate it into legacy databases. Django includes a couple of utilities to automate as much of this process as possible. This document assumes you know the Django basics, as covered in the -:ref:`tutorial `. +:doc:`tutorial `. Once you've got Django set up, you'll follow this general process to integrate with an existing database. diff --git a/docs/howto/outputting-csv.txt b/docs/howto/outputting-csv.txt index 234454c2657f..169114ff9516 100644 --- a/docs/howto/outputting-csv.txt +++ b/docs/howto/outputting-csv.txt @@ -1,5 +1,3 @@ -.. _howto-outputting-csv: - ========================== Outputting CSV with Django ========================== @@ -61,7 +59,7 @@ mention: Using the template system ========================= -Alternatively, you can use the :ref:`Django template system ` +Alternatively, you can use the :doc:`Django template system ` to generate CSV. This is lower-level than using the convenient CSV, but the solution is presented here for completeness. @@ -113,4 +111,4 @@ Other text-based formats Notice that there isn't very much specific to CSV here -- just the specific output format. You can use either of these techniques to output any text-based format you can dream of. You can also use a similar technique to generate -arbitrary binary data; see :ref:`howto-outputting-pdf` for an example. +arbitrary binary data; see :doc:`/howto/outputting-pdf` for an example. diff --git a/docs/howto/outputting-pdf.txt b/docs/howto/outputting-pdf.txt index 94acab83112e..32e38aebc675 100644 --- a/docs/howto/outputting-pdf.txt +++ b/docs/howto/outputting-pdf.txt @@ -1,5 +1,3 @@ -.. _howto-outputting-pdf: - =========================== Outputting PDFs with Django =========================== @@ -154,5 +152,5 @@ Other formats Notice that there isn't a lot in these examples that's PDF-specific -- just the bits using ``reportlab``. You can use a similar technique to generate any arbitrary format that you can find a Python library for. Also see -:ref:`howto-outputting-csv` for another example and some techniques you can use +:doc:`/howto/outputting-csv` for another example and some techniques you can use when generated text-based formats. diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index f93a4e9ba42c..c3692d527173 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -1,5 +1,3 @@ -.. _howto-static-files: - ========================= How to serve static files ========================= @@ -42,7 +40,7 @@ Here's the formal definition of the :func:`~django.views.static.serve` view: .. function:: def serve(request, path, document_root, show_indexes=False) -To use it, just put this in your :ref:`URLconf `:: +To use it, just put this in your :doc:`URLconf `:: (r'^site_media/(?P.*)$', 'django.views.static.serve', {'document_root': '/path/to/media'}), @@ -71,7 +69,7 @@ required. For example, if we have a line in ``settings.py`` that says:: STATIC_DOC_ROOT = '/path/to/media' -...we could write the above :ref:`URLconf ` entry as:: +...we could write the above :doc:`URLconf ` entry as:: from django.conf import settings ... diff --git a/docs/index.txt b/docs/index.txt index aae2e27cb676..c031b03f5411 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -12,10 +12,10 @@ Getting help Having trouble? We'd like to help! -* Try the :ref:`FAQ ` -- it's got answers to many common questions. +* Try the :doc:`FAQ ` -- it's got answers to many common questions. * Looking for specific information? Try the :ref:`genindex`, :ref:`modindex` or - the :ref:`detailed table of contents `. + the :doc:`detailed table of contents `. * Search for information in the `archives of the django-users mailing list`_, or `post a question`_. @@ -35,179 +35,179 @@ First steps =========== * **From scratch:** - :ref:`Overview ` | - :ref:`Installation ` + :doc:`Overview ` | + :doc:`Installation ` * **Tutorial:** - :ref:`Part 1 ` | - :ref:`Part 2 ` | - :ref:`Part 3 ` | - :ref:`Part 4 ` + :doc:`Part 1 ` | + :doc:`Part 2 ` | + :doc:`Part 3 ` | + :doc:`Part 4 ` The model layer =============== * **Models:** - :ref:`Model syntax ` | - :ref:`Field types ` | - :ref:`Meta options ` + :doc:`Model syntax ` | + :doc:`Field types ` | + :doc:`Meta options ` * **QuerySets:** - :ref:`Executing queries ` | - :ref:`QuerySet method reference ` + :doc:`Executing queries ` | + :doc:`QuerySet method reference ` * **Model instances:** - :ref:`Instance methods ` | - :ref:`Accessing related objects ` + :doc:`Instance methods ` | + :doc:`Accessing related objects ` * **Advanced:** - :ref:`Managers ` | - :ref:`Raw SQL ` | - :ref:`Transactions ` | - :ref:`Aggregation ` | - :ref:`Custom fields ` | - :ref:`Multiple databases ` + :doc:`Managers ` | + :doc:`Raw SQL ` | + :doc:`Transactions ` | + :doc:`Aggregation ` | + :doc:`Custom fields ` | + :doc:`Multiple databases ` * **Other:** - :ref:`Supported databases ` | - :ref:`Legacy databases ` | - :ref:`Providing initial data ` | - :ref:`Optimize database access ` + :doc:`Supported databases ` | + :doc:`Legacy databases ` | + :doc:`Providing initial data ` | + :doc:`Optimize database access ` The template layer ================== * **For designers:** - :ref:`Syntax overview ` | - :ref:`Built-in tags and filters ` + :doc:`Syntax overview ` | + :doc:`Built-in tags and filters ` * **For programmers:** - :ref:`Template API ` | - :ref:`Custom tags and filters ` + :doc:`Template API ` | + :doc:`Custom tags and filters ` The view layer ============== * **The basics:** - :ref:`URLconfs ` | - :ref:`View functions ` | - :ref:`Shortcuts ` + :doc:`URLconfs ` | + :doc:`View functions ` | + :doc:`Shortcuts ` - * **Reference:** :ref:`Request/response objects ` + * **Reference:** :doc:`Request/response objects ` * **File uploads:** - :ref:`Overview ` | - :ref:`File objects ` | - :ref:`Storage API ` | - :ref:`Managing files ` | - :ref:`Custom storage ` + :doc:`Overview ` | + :doc:`File objects ` | + :doc:`Storage API ` | + :doc:`Managing files ` | + :doc:`Custom storage ` * **Generic views:** - :ref:`Overview` | - :ref:`Built-in generic views` + :doc:`Overview` | + :doc:`Built-in generic views` * **Advanced:** - :ref:`Generating CSV ` | - :ref:`Generating PDF ` + :doc:`Generating CSV ` | + :doc:`Generating PDF ` * **Middleware:** - :ref:`Overview ` | - :ref:`Built-in middleware classes ` + :doc:`Overview ` | + :doc:`Built-in middleware classes ` Forms ===== * **The basics:** - :ref:`Overview ` | - :ref:`Form API ` | - :ref:`Built-in fields ` | - :ref:`Built-in widgets ` + :doc:`Overview ` | + :doc:`Form API ` | + :doc:`Built-in fields ` | + :doc:`Built-in widgets ` * **Advanced:** - :ref:`Forms for models ` | - :ref:`Integrating media ` | - :ref:`Formsets ` | - :ref:`Customizing validation ` + :doc:`Forms for models ` | + :doc:`Integrating media ` | + :doc:`Formsets ` | + :doc:`Customizing validation ` * **Extras:** - :ref:`Form preview ` | - :ref:`Form wizard ` + :doc:`Form preview ` | + :doc:`Form wizard ` The development process ======================= * **Settings:** - :ref:`Overview ` | - :ref:`Full list of settings ` + :doc:`Overview ` | + :doc:`Full list of settings ` * **Exceptions:** - :ref:`Overview ` + :doc:`Overview ` * **django-admin.py and manage.py:** - :ref:`Overview ` | - :ref:`Adding custom commands ` + :doc:`Overview ` | + :doc:`Adding custom commands ` - * **Testing:** :ref:`Overview ` + * **Testing:** :doc:`Overview ` * **Deployment:** - :ref:`Overview ` | - :ref:`Apache/mod_wsgi ` | - :ref:`Apache/mod_python ` | - :ref:`FastCGI/SCGI/AJP ` | - :ref:`Apache authentication ` | - :ref:`Serving static files ` | - :ref:`Tracking code errors by e-mail ` + :doc:`Overview ` | + :doc:`Apache/mod_wsgi ` | + :doc:`Apache/mod_python ` | + :doc:`FastCGI/SCGI/AJP ` | + :doc:`Apache authentication ` | + :doc:`Serving static files ` | + :doc:`Tracking code errors by e-mail ` Other batteries included ======================== - * :ref:`Admin site ` | :ref:`Admin actions ` - * :ref:`Authentication ` - * :ref:`Cache system ` - * :ref:`Conditional content processing ` - * :ref:`Comments ` | :ref:`Moderation ` | :ref:`Custom comments ` - * :ref:`Content types ` - * :ref:`Cross Site Request Forgery protection ` - * :ref:`Databrowse ` - * :ref:`E-mail (sending) ` - * :ref:`Flatpages ` - * :ref:`GeoDjango ` - * :ref:`Humanize ` - * :ref:`Internationalization ` - * :ref:`Jython support ` - * :ref:`"Local flavor" ` - * :ref:`Messages ` - * :ref:`Pagination ` - * :ref:`Redirects ` - * :ref:`Serialization ` - * :ref:`Sessions ` - * :ref:`Signals ` - * :ref:`Sitemaps ` - * :ref:`Sites ` - * :ref:`Syndication feeds (RSS/Atom) ` - * :ref:`Unicode in Django ` - * :ref:`Web design helpers ` - * :ref:`Validators ` + * :doc:`Admin site ` | :doc:`Admin actions ` + * :doc:`Authentication ` + * :doc:`Cache system ` + * :doc:`Conditional content processing ` + * :doc:`Comments ` | :doc:`Moderation ` | :doc:`Custom comments ` + * :doc:`Content types ` + * :doc:`Cross Site Request Forgery protection ` + * :doc:`Databrowse ` + * :doc:`E-mail (sending) ` + * :doc:`Flatpages ` + * :doc:`GeoDjango ` + * :doc:`Humanize ` + * :doc:`Internationalization ` + * :doc:`Jython support ` + * :doc:`"Local flavor" ` + * :doc:`Messages ` + * :doc:`Pagination ` + * :doc:`Redirects ` + * :doc:`Serialization ` + * :doc:`Sessions ` + * :doc:`Signals ` + * :doc:`Sitemaps ` + * :doc:`Sites ` + * :doc:`Syndication feeds (RSS/Atom) ` + * :doc:`Unicode in Django ` + * :doc:`Web design helpers ` + * :doc:`Validators ` The Django open-source project ============================== * **Community:** - :ref:`How to get involved ` | - :ref:`The release process ` | - :ref:`Team of committers ` | - :ref:`The Django source code repository ` + :doc:`How to get involved ` | + :doc:`The release process ` | + :doc:`Team of committers ` | + :doc:`The Django source code repository ` * **Design philosophies:** - :ref:`Overview ` + :doc:`Overview ` * **Documentation:** - :ref:`About this documentation ` + :doc:`About this documentation ` * **Third-party distributions:** - :ref:`Overview ` + :doc:`Overview ` * **Django over time:** - :ref:`API stability ` | - :ref:`Release notes and upgrading instructions ` | - :ref:`Deprecation Timeline ` + :doc:`API stability ` | + :doc:`Release notes and upgrading instructions ` | + :doc:`Deprecation Timeline ` diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index d2eb80c7102b..b0bb18b9557d 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -1,5 +1,3 @@ -.. _internals-committers: - ================= Django committers ================= diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index 41d1cffdb232..399e458c2af1 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -1,5 +1,3 @@ -.. _internals-contributing: - ====================== Contributing to Django ====================== @@ -42,7 +40,7 @@ amount of overhead involved in working with any bug tracking system, so your help in keeping our ticket tracker as useful as possible is appreciated. In particular: - * **Do** read the :ref:`FAQ ` to see if your issue might be a well-known question. + * **Do** read the :doc:`FAQ ` to see if your issue might be a well-known question. * **Do** `search the tracker`_ to see if your issue has already been filed. @@ -398,7 +396,7 @@ Various parts of Django, such as the admin site and validation error messages, are internationalized. This means they display different text depending on a user's language setting. For this, Django uses the same internationalization infrastructure available to Django applications described in the -:ref:`i18n documentation`. +:doc:`i18n documentation`. These translations are contributed by Django users worldwide. If you find an incorrect translation, or if you'd like to add a language that isn't yet @@ -409,7 +407,7 @@ translated, here's what to do: * Make sure you read the notes about :ref:`specialties-of-django-i18n`. * Create translations using the methods described in the - :ref:`localization documentation `. For this + :doc:`localization documentation `. For this you will use the ``django-admin.py makemessages`` tool. In this particular case it should be run from the top-level ``django`` directory of the Django source tree. @@ -535,8 +533,8 @@ Please follow these coding standards when writing code for inclusion in Django: * Use ``InitialCaps`` for class names (or for factory functions that return classes). - * Mark all strings for internationalization; see the :ref:`i18n - documentation ` for details. + * Mark all strings for internationalization; see the :doc:`i18n + documentation ` for details. * In docstrings, use "action words" such as:: @@ -698,8 +696,8 @@ General improvements, or other changes to the APIs that should be emphasized should use the ".. versionchanged:: X.Y" directive (with the same format as the ``versionadded`` mentioned above. -There's a full page of information about the :ref:`Django documentation -system ` that you should read prior to working on the +There's a full page of information about the :doc:`Django documentation +system ` that you should read prior to working on the documentation. Guidelines for ReST files @@ -829,7 +827,7 @@ The tests cover: We appreciate any and all contributions to the test suite! The Django tests all use the testing infrastructure that ships with Django for -testing applications. See :ref:`Testing Django applications ` +testing applications. See :doc:`Testing Django applications ` for an explanation of how to write new tests. Running the unit tests @@ -1017,8 +1015,8 @@ for feature branches: public, please add the branch to the `Django branches`_ wiki page. 2. Feature branches using SVN have a higher bar. If you want a branch in SVN - itself, you'll need a "mentor" among the :ref:`core committers - `. This person is responsible for actually creating + itself, you'll need a "mentor" among the :doc:`core committers + `. This person is responsible for actually creating the branch, monitoring your process (see below), and ultimately merging the branch into trunk. diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 8479a32bcfa9..e04579533803 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -1,5 +1,3 @@ -.. _internals-deprecation: - =========================== Django Deprecation Timeline =========================== @@ -52,7 +50,7 @@ their deprecation, as per the :ref:`Django deprecation policy associated methods (``user.message_set.create()`` and ``user.get_and_delete_messages()``), which have been deprecated since the 1.2 release, will be removed. The - :ref:`messages framework ` should be used + :doc:`messages framework ` should be used instead. * Authentication backends need to support the ``obj`` parameter for diff --git a/docs/internals/documentation.txt b/docs/internals/documentation.txt index d12e35e19547..63f248d3a972 100644 --- a/docs/internals/documentation.txt +++ b/docs/internals/documentation.txt @@ -1,5 +1,3 @@ -.. _internals-documentation: - How the Django documentation works ================================== @@ -88,27 +86,55 @@ __ http://sphinx.pocoo.org/markup/desc.html An example ---------- -For a quick example of how it all fits together, check this out: +For a quick example of how it all fits together, consider this hypothetical +example: - * First, the ``ref/settings.txt`` document starts out like this:: + * First, the ``ref/settings.txt`` document could have an overall layout + like this: - .. _ref-settings: + .. code-block:: rst - Available settings - ================== + ======== + Settings + ======== ... - * Next, if you look at the ``topics/settings.txt`` document, you can see how - a link to ``ref/settings`` works:: + .. _available-settings: Available settings ================== - For a full list of available settings, see the :ref:`settings reference - `. + ... + + .. _deprecated-settings: + + Deprecated settings + =================== + + ... + + * Next, the ``topics/settings.txt`` document could contain something like + this: + + .. code-block:: rst + + You can access a :ref:`listing of all available settings + `. For a list of deprecated settings see + :ref:`deprecated-settings`. + + You can find both in the :doc:`settings reference document `. + + We use the Sphinx doc_ cross reference element when we want to link to + another document as a whole and the ref_ element when we want to link to + an arbitrary location in a document. + +.. _doc: http://sphinx.pocoo.org/markup/inline.html#role-doc +.. _ref: http://sphinx.pocoo.org/markup/inline.html#role-ref + + * Next, notice how the settings are annotated: - * Next, notice how the settings (right now just the top few) are annotated:: + .. code-block:: rst .. setting:: ADMIN_FOR diff --git a/docs/internals/index.txt b/docs/internals/index.txt index 4f9007705e78..26c941a09644 100644 --- a/docs/internals/index.txt +++ b/docs/internals/index.txt @@ -1,5 +1,3 @@ -.. _internals-index: - Django internals ================ diff --git a/docs/internals/release-process.txt b/docs/internals/release-process.txt index 20bc36584411..2a56f0be9238 100644 --- a/docs/internals/release-process.txt +++ b/docs/internals/release-process.txt @@ -1,5 +1,3 @@ -.. _internals-release-process: - ======================== Django's release process ======================== diff --git a/docs/internals/svn.txt b/docs/internals/svn.txt index 372fbd120201..c66e494e5f06 100644 --- a/docs/internals/svn.txt +++ b/docs/internals/svn.txt @@ -1,5 +1,3 @@ -.. _internals-svn: - ================================= The Django source code repository ================================= @@ -87,8 +85,8 @@ the ``django`` module within your checkout. If you're going to be working on Django's code (say, to fix a bug or develop a new feature), you can probably stop reading here and move -over to :ref:`the documentation for contributing to Django -`, which covers things like the preferred +over to :doc:`the documentation for contributing to Django +`, which covers things like the preferred coding style and how to generate and submit a patch. @@ -129,20 +127,20 @@ part of Django itself, and so are no longer separately maintained: object-relational mapper. This has been part of Django since the 1.0 release, as the bundled application ``django.contrib.gis``. -* ``i18n``: Added :ref:`internationalization support ` to +* ``i18n``: Added :doc:`internationalization support ` to Django. This has been part of Django since the 0.90 release. * ``magic-removal``: A major refactoring of both the internals and public APIs of Django's object-relational mapper. This has been part of Django since the 0.95 release. -* ``multi-auth``: A refactoring of :ref:`Django's bundled - authentication framework ` which added support for +* ``multi-auth``: A refactoring of :doc:`Django's bundled + authentication framework ` which added support for :ref:`authentication backends `. This has been part of Django since the 0.95 release. -* ``new-admin``: A refactoring of :ref:`Django's bundled - administrative application `. This became part of +* ``new-admin``: A refactoring of :doc:`Django's bundled + administrative application `. This became part of Django as of the 0.91 release, but was superseded by another refactoring (see next listing) prior to the Django 1.0 release. diff --git a/docs/intro/index.txt b/docs/intro/index.txt index 2135bc7fe9be..90ee627ba6db 100644 --- a/docs/intro/index.txt +++ b/docs/intro/index.txt @@ -1,5 +1,3 @@ -.. _intro-index: - Getting started =============== diff --git a/docs/intro/install.txt b/docs/intro/install.txt index dcb9c8e0c486..95728c75fc3f 100644 --- a/docs/intro/install.txt +++ b/docs/intro/install.txt @@ -1,10 +1,8 @@ -.. _intro-install: - Quick install guide =================== Before you can use Django, you'll need to get it installed. We have a -:ref:`complete installation guide ` that covers all the +:doc:`complete installation guide ` that covers all the possibilities; this guide will guide you to a simple, minimal installation that'll work while you walk through the introduction. @@ -14,7 +12,7 @@ Install Python Being a Python Web framework, Django requires Python. It works with any Python version from 2.4 to 2.7 (due to backwards incompatibilities in Python 3.0, Django does not currently work with -Python 3.0; see :ref:`the Django FAQ ` for more +Python 3.0; see :doc:`the Django FAQ ` for more information on supported Python versions and the 3.0 transition), but we recommend installing Python 2.5 or later. If you do so, you won't need to set up a database just yet: Python 2.5 or later includes a lightweight database called SQLite_. .. _sqlite: http://sqlite.org/ @@ -25,17 +23,17 @@ probably already have it installed. .. admonition:: Django on Jython If you use Jython_ (a Python implementation for the Java platform), you'll - need to follow a few additional steps. See :ref:`howto-jython` for details. + need to follow a few additional steps. See :doc:`/howto/jython` for details. .. _jython: http://www.jython.org/ You can verify that Python's installed by typing ``python`` from your shell; you should see something like:: - Python 2.5.1 (r251:54863, Jan 17 2008, 19:35:17) + Python 2.5.1 (r251:54863, Jan 17 2008, 19:35:17) [GCC 4.0.1 (Apple Inc. build 5465)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> - + Set up a database ----------------- @@ -57,18 +55,18 @@ Install Django You've got three easy options to install Django: - * Install a version of Django :ref:`provided by your operating system - distribution `. This is the quickest option for those + * Install a version of Django :doc:`provided by your operating system + distribution `. This is the quickest option for those who have operating systems that distribute Django. * :ref:`Install an official release `. This is the best approach for users who want a stable version number and aren't concerned about running a slightly older version of Django. - + * :ref:`Install the latest development version `. This is best for users who want the latest-and-greatest features and aren't afraid of running brand-new code. - + .. warning:: If you do either of the first two steps, keep an eye out for parts of the @@ -79,7 +77,7 @@ You've got three easy options to install Django: That's it! ---------- -That's it -- you can now :ref:`move onto the tutorial `. +That's it -- you can now :doc:`move onto the tutorial `. diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 594c9fe5826d..0c47e59e14d4 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -1,5 +1,3 @@ -.. _intro-overview: - ================== Django at a glance ================== @@ -11,8 +9,8 @@ overview of how to write a database-driven Web app with Django. The goal of this document is to give you enough technical specifics to understand how Django works, but this isn't intended to be a tutorial or reference -- but we've got both! When you're ready to start a project, you can -:ref:`start with the tutorial ` or :ref:`dive right into more -detailed documentation `. +:doc:`start with the tutorial ` or :doc:`dive right into more +detailed documentation `. Design your model ================= @@ -21,7 +19,7 @@ Although you can use Django without a database, it comes with an object-relational mapper in which you describe your database layout in Python code. -The :ref:`data-model syntax ` offers many rich ways of +The :doc:`data-model syntax ` offers many rich ways of representing your models -- so far, it's been solving two years' worth of database-schema problems. Here's a quick example:: @@ -56,7 +54,7 @@ tables in your database for whichever tables don't already exist. Enjoy the free API ================== -With that, you've got a free, and rich, :ref:`Python API ` to +With that, you've got a free, and rich, :doc:`Python API ` to access your data. The API is created on the fly, no code generation necessary:: >>> from mysite.models import Reporter, Article @@ -131,7 +129,7 @@ A dynamic admin interface: it's not just scaffolding -- it's the whole house ============================================================================ Once your models are defined, Django can automatically create a professional, -production ready :ref:`administrative interface ` -- a Web +production ready :doc:`administrative interface ` -- a Web site that lets authenticated users add, change and delete objects. It's as easy as registering your model in the admin site:: @@ -168,8 +166,8 @@ A clean, elegant URL scheme is an important detail in a high-quality Web application. Django encourages beautiful URL design and doesn't put any cruft in URLs, like ``.php`` or ``.asp``. -To design URLs for an app, you create a Python module called a :ref:`URLconf -`. A table of contents for your app, it contains a simple mapping +To design URLs for an app, you create a Python module called a :doc:`URLconf +`. A table of contents for your app, it contains a simple mapping between URL patterns and Python callback functions. URLconfs also serve to decouple URLs from Python code. @@ -216,7 +214,7 @@ and renders the template with the retrieved data. Here's an example view for a_list = Article.objects.filter(pub_date__year=year) return render_to_response('news/year_archive.html', {'year': year, 'article_list': a_list}) -This example uses Django's :ref:`template system `, which has +This example uses Django's :doc:`template system `, which has several powerful features but strives to stay simple enough for non-programmers to use. @@ -307,17 +305,17 @@ This is just the surface This has been only a quick overview of Django's functionality. Some more useful features: - * A :ref:`caching framework ` that integrates with memcached + * A :doc:`caching framework ` that integrates with memcached or other backends. - * A :ref:`syndication framework ` that makes + * A :doc:`syndication framework ` that makes creating RSS and Atom feeds as easy as writing a small Python class. * More sexy automatically-generated admin features -- this overview barely scratched the surface. -The next obvious steps are for you to `download Django`_, read :ref:`the -tutorial ` and join `the community`_. Thanks for your +The next obvious steps are for you to `download Django`_, read :doc:`the +tutorial ` and join `the community`_. Thanks for your interest! .. _download Django: http://www.djangoproject.com/download/ diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index c38fa7d7f514..6045eb111e69 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -1,5 +1,3 @@ -.. _intro-tutorial01: - ===================================== Writing your first Django app, part 1 ===================================== @@ -14,7 +12,7 @@ It'll consist of two parts: * A public site that lets people view polls and vote in them. * An admin site that lets you add, change and delete polls. -We'll assume you have :ref:`Django installed ` already. You can +We'll assume you have :doc:`Django installed ` already. You can tell Django is installed by running the Python interactive interpreter and typing ``import django``. If that command runs successfully, with no errors, Django is installed. @@ -47,8 +45,8 @@ create a ``mysite`` directory in your current directory. 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 :ref:`django-admin.py - ` is installed, then run the command + the ``cd`` command) to the directory where :doc:`django-admin.py + ` is installed, then run the command ``chmod +x django-admin.py``. .. note:: @@ -58,11 +56,11 @@ create a ``mysite`` directory in your current directory. ``django`` (which will conflict with Django itself) or ``test`` (which conflicts with a built-in Python package). -:ref:`django-admin.py ` should be on your system path if you +:doc:`django-admin.py ` should be on your system path if you 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 :ref:`django-admin.py -` from some place on your path, such as +within your Python installation. Consider symlinking to :doc:`django-admin.py +` from some place on your path, such as :file:`/usr/local/bin`. .. admonition:: Where should this code live? @@ -93,14 +91,14 @@ These files are: * :file:`manage.py`: A command-line utility that lets you interact with this Django project in various ways. You can read all the details about - :file:`manage.py` in :ref:`ref-django-admin`. + :file:`manage.py` in :doc:`/ref/django-admin`. * :file:`settings.py`: Settings/configuration for this Django project. - :ref:`topics-settings` will tell you all about how settings work. + :doc:`/topics/settings` will tell you all about how settings work. * :file:`urls.py`: The URL declarations for this Django project; a "table of contents" of your Django-powered site. You can read more about URLs in - :ref:`topics-http-urls`. + :doc:`/topics/http/urls`. .. _more about packages: http://docs.python.org/tutorial/modules.html#packages @@ -473,7 +471,7 @@ added to your project since the last time you ran syncdb. :djadmin:`syncdb` can be called as often as you like, and it will only ever create the tables that don't exist. -Read the :ref:`django-admin.py documentation ` for full +Read the :doc:`django-admin.py documentation ` for full information on what the ``manage.py`` utility can do. Playing with the API @@ -508,10 +506,10 @@ things: set the ``DJANGO_SETTINGS_MODULE`` environment variable to ``mysite.settings``. - For more information on all of this, see the :ref:`django-admin.py - documentation `. + For more information on all of this, see the :doc:`django-admin.py + documentation `. -Once you're in the shell, explore the :ref:`database API `:: +Once you're in the shell, explore the :doc:`database API `:: >>> from mysite.polls.models import Poll, Choice # Import the model classes we just wrote. @@ -570,8 +568,8 @@ of this object. Let's fix that by editing the polls model (in the models and don't see any change in how they're represented, you're most likely using an old version of Django. (This version of the tutorial is written for the latest development version of Django.) If you're using a - Subversion checkout of Django's development version (see :ref:`the - installation docs ` for more information), you shouldn't have + Subversion checkout of Django's development version (see :doc:`the + installation docs ` for more information), you shouldn't have any problems. If you want to stick with an older version of Django, you'll want to switch @@ -693,9 +691,9 @@ Save these changes and start a new Python interactive shell by running >>> c = p.choice_set.filter(choice__startswith='Just hacking') >>> c.delete() -For more information on model relations, see :ref:`Accessing related objects -`. For full details on the database API, see our -:ref:`Database API reference `. +For more information on model relations, see :doc:`Accessing related objects +`. For full details on the database API, see our +:doc:`Database API reference `. -When you're comfortable with the API, read :ref:`part 2 of this tutorial -` to get Django's automatic admin working. +When you're comfortable with the API, read :doc:`part 2 of this tutorial +` to get Django's automatic admin working. diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index b8f899cedc00..fcdb812c81c5 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -1,10 +1,8 @@ -.. _intro-tutorial02: - ===================================== Writing your first Django app, part 2 ===================================== -This tutorial begins where :ref:`Tutorial 1 ` left off. We're +This tutorial begins where :doc:`Tutorial 1 ` left off. We're continuing the Web-poll application and will focus on Django's automatically-generated admin site. @@ -463,5 +461,5 @@ object-specific admin pages in whatever way you think is best. Again, don't worry if you can't understand the template language -- we'll cover that in more detail in Tutorial 3. -When you're comfortable with the admin site, read :ref:`part 3 of this tutorial -` to start working on public poll views. +When you're comfortable with the admin site, read :doc:`part 3 of this tutorial +` to start working on public poll views. diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 0e09693778ef..1865b1bd5c44 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -1,10 +1,8 @@ -.. _intro-tutorial03: - ===================================== Writing your first Django app, part 3 ===================================== -This tutorial begins where :ref:`Tutorial 2 ` left off. We're +This tutorial begins where :doc:`Tutorial 2 ` left off. We're continuing the Web-poll application and will focus on creating the public interface -- "views." @@ -68,8 +66,8 @@ arbitrary keyword arguments from the dictionary (an optional third item in the tuple). For more on :class:`~django.http.HttpRequest` objects, see the -:ref:`ref-request-response`. For more details on URLconfs, see the -:ref:`topics-http-urls`. +:doc:`/ref/request-response`. For more details on URLconfs, see the +:doc:`/topics/http/urls`. When you ran ``django-admin.py startproject mysite`` at the beginning of Tutorial 1, it created a default URLconf in ``mysite/urls.py``. It also @@ -205,7 +203,7 @@ you want, using whatever Python libraries you want. All Django wants is that :class:`~django.http.HttpResponse`. Or an exception. Because it's convenient, let's use Django's own database API, which we covered -in :ref:`Tutorial 1 `. Here's one stab at the ``index()`` +in :doc:`Tutorial 1 `. Here's one stab at the ``index()`` view, which displays the latest 5 poll questions in the system, separated by commas, according to publication date:: @@ -425,7 +423,7 @@ Method-calling happens in the ``{% for %}`` loop: ``poll.choice_set.all`` is interpreted as the Python code ``poll.choice_set.all()``, which returns an iterable of Choice objects and is suitable for use in the ``{% for %}`` tag. -See the :ref:`template guide ` for more about templates. +See the :doc:`template guide ` for more about templates. Simplifying the URLconfs ======================== @@ -514,5 +512,5 @@ under "/content/polls/", or any other URL root, and the app will still work. All the poll app cares about is its relative URLs, not its absolute URLs. -When you're comfortable with writing views, read :ref:`part 4 of this tutorial -` to learn about simple form processing and generic views. +When you're comfortable with writing views, read :doc:`part 4 of this tutorial +` to learn about simple form processing and generic views. diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt index ee3a3b204582..a7a9aaea3355 100644 --- a/docs/intro/tutorial04.txt +++ b/docs/intro/tutorial04.txt @@ -1,10 +1,8 @@ -.. _intro-tutorial04: - ===================================== Writing your first Django app, part 4 ===================================== -This tutorial begins where :ref:`Tutorial 3 ` left off. We're +This tutorial begins where :doc:`Tutorial 3 ` left off. We're continuing the Web-poll application and will focus on simple form processing and cutting down our code. @@ -70,7 +68,7 @@ The details of how this works are explained in the documentation for :ref:`RequestContext `. Now, let's create a Django view that handles the submitted data and does -something with it. Remember, in :ref:`Tutorial 3 `, we +something with it. Remember, in :doc:`Tutorial 3 `, we created a URLconf for the polls application that includes this line:: (r'^(?P\d+)/vote/$', 'vote'), @@ -149,7 +147,7 @@ This code includes a few things we haven't covered yet in this tutorial: As mentioned in Tutorial 3, ``request`` is a :class:`~django.http.HttpRequest` object. For more on :class:`~django.http.HttpRequest` objects, see the -:ref:`request and response documentation `. +:doc:`request and response documentation `. After somebody votes in a poll, the ``vote()`` view redirects to the results page for the poll. Let's write that view:: @@ -158,8 +156,8 @@ page for the poll. Let's write that view:: p = get_object_or_404(Poll, pk=poll_id) return render_to_response('polls/results.html', {'poll': p}) -This is almost exactly the same as the ``detail()`` view from :ref:`Tutorial 3 -`. The only difference is the template name. We'll fix this +This is almost exactly the same as the ``detail()`` view from :doc:`Tutorial 3 +`. The only difference is the template name. We'll fix this redundancy later. Now, create a ``results.html`` template: @@ -183,7 +181,7 @@ without having chosen a choice, you should see the error message. Use generic views: Less code is better ====================================== -The ``detail()`` (from :ref:`Tutorial 3 `) and ``results()`` +The ``detail()`` (from :doc:`Tutorial 3 `) and ``results()`` views are stupidly simple -- and, as mentioned above, redundant. The ``index()`` view (also from Tutorial 3), which displays a list of polls, is similar. @@ -328,8 +326,8 @@ are) used multiple times -- but we can use the name we've given:: Run the server, and use your new polling app based on generic views. -For full details on generic views, see the :ref:`generic views documentation -`. +For full details on generic views, see the :doc:`generic views documentation +`. Coming soon =========== @@ -344,5 +342,5 @@ will cover: * Advanced admin features: Permissions * Advanced admin features: Custom JavaScript -In the meantime, you might want to check out some pointers on :ref:`where to go -from here ` +In the meantime, you might want to check out some pointers on :doc:`where to go +from here ` diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index c18251f92f6d..fe385ffd9ac0 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -1,10 +1,8 @@ -.. _intro-whatsnext: - ================= What to read next ================= -So you've read all the :ref:`introductory material ` and have +So you've read all the :doc:`introductory material ` and have decided you'd like to keep using Django. We've only just scratched the surface with this intro (in fact, if you've read every single word you've still read less than 10% of the overall documentation). @@ -37,15 +35,15 @@ How the documentation is organized Django's main documentation is broken up into "chunks" designed to fill different needs: - * The :ref:`introductory material ` is designed for people new + * The :doc:`introductory material ` is designed for people new to Django -- or to web development in general. It doesn't cover anything in depth, but instead gives a high-level overview of how developing in Django "feels". - * The :ref:`topic guides `, on the other hand, dive deep into + * The :doc:`topic guides `, on the other hand, dive deep into individual parts of Django. There are complete guides to Django's - :ref:`model system `, :ref:`template engine - `, :ref:`forms framework `, and much + :doc:`model system `, :doc:`template engine + `, :doc:`forms framework `, and much more. This is probably where you'll want to spend most of your time; if you work @@ -53,27 +51,27 @@ different needs: everything there is to know about Django. * Web development is often broad, not deep -- problems span many domains. - We've written a set of :ref:`how-to guides ` that answer + We've written a set of :doc:`how-to guides ` that answer common "How do I ...?" questions. Here you'll find information about - :ref:`generating PDFs with Django `, :ref:`writing - custom template tags `, and more. + :doc:`generating PDFs with Django `, :doc:`writing + custom template tags `, and more. - Answers to really common questions can also be found in the :ref:`FAQ - `. + Answers to really common questions can also be found in the :doc:`FAQ + `. * The guides and how-to's don't cover every single class, function, and method available in Django -- that would be overwhelming when you're trying to learn. Instead, details about individual classes, functions, - methods, and modules are kept in the :ref:`reference `. This is + methods, and modules are kept in the :doc:`reference `. This is where you'll turn to find the details of a particular function or whathaveyou. * Finally, there's some "specialized" documentation not usually relevant to - most developers. This includes the :ref:`release notes `, - :ref:`documentation of obsolete features `, - :ref:`internals documentation ` for those who want to add - code to Django itself, and a :ref:`few other things that simply don't fit - elsewhere `. + most developers. This includes the :doc:`release notes `, + :doc:`documentation of obsolete features `, + :doc:`internals documentation ` for those who want to add + code to Django itself, and a :doc:`few other things that simply don't fit + elsewhere `. How documentation is updated diff --git a/docs/misc/api-stability.txt b/docs/misc/api-stability.txt index a648c873cc67..70e522159282 100644 --- a/docs/misc/api-stability.txt +++ b/docs/misc/api-stability.txt @@ -1,10 +1,8 @@ -.. _misc-api-stability: - ============= API stability ============= -:ref:`The release of Django 1.0 ` comes with a promise of API +:doc:`The release of Django 1.0 ` comes with a promise of API stability and forwards-compatibility. In a nutshell, this means that code you develop against Django 1.0 will continue to work against 1.1 unchanged, and you should need to make only minor changes for any 1.X release. @@ -37,67 +35,67 @@ Stable APIs =========== In general, everything covered in the documentation -- with the exception of -anything in the :ref:`internals area ` is considered stable as +anything in the :doc:`internals area ` is considered stable as of 1.0. This includes these APIs: - - :ref:`Authorization ` + - :doc:`Authorization ` - - :ref:`Caching `. + - :doc:`Caching `. - - :ref:`Model definition, managers, querying and transactions - ` + - :doc:`Model definition, managers, querying and transactions + ` - - :ref:`Sending e-mail `. + - :doc:`Sending e-mail `. - - :ref:`File handling and storage ` + - :doc:`File handling and storage ` - - :ref:`Forms ` + - :doc:`Forms ` - - :ref:`HTTP request/response handling `, including file + - :doc:`HTTP request/response handling `, including file uploads, middleware, sessions, URL resolution, view, and shortcut APIs. - - :ref:`Generic views `. + - :doc:`Generic views `. - - :ref:`Internationalization `. + - :doc:`Internationalization `. - - :ref:`Pagination ` + - :doc:`Pagination ` - - :ref:`Serialization ` + - :doc:`Serialization ` - - :ref:`Signals ` + - :doc:`Signals ` - - :ref:`Templates `, including the language, Python-level - :ref:`template APIs `, and :ref:`custom template tags - and libraries `. We may add new template + - :doc:`Templates `, including the language, Python-level + :doc:`template APIs `, and :doc:`custom template tags + and libraries `. We may add new template tags in the future and the names may inadvertently clash with external template tags. Before adding any such tags, we'll ensure that Django raises an error if it tries to load tags with duplicate names. - - :ref:`Testing ` + - :doc:`Testing ` - - :ref:`django-admin utility `. + - :doc:`django-admin utility `. - - :ref:`Built-in middleware ` + - :doc:`Built-in middleware ` - - :ref:`Request/response objects `. + - :doc:`Request/response objects `. - - :ref:`Settings `. Note, though that while the :ref:`list of - built-in settings ` can be considered complete we may -- and + - :doc:`Settings `. Note, though that while the :doc:`list of + built-in settings ` can be considered complete we may -- and probably will -- add new settings in future versions. This is one of those places where "'stable' does not mean 'complete.'" - - :ref:`Built-in signals `. Like settings, we'll probably add + - :doc:`Built-in signals `. Like settings, we'll probably add new signals in the future, but the existing ones won't break. - - :ref:`Unicode handling `. + - :doc:`Unicode handling `. - - Everything covered by the :ref:`HOWTO guides `. + - Everything covered by the :doc:`HOWTO guides `. ``django.utils`` ---------------- Most of the modules in ``django.utils`` are designed for internal use. Only -the following parts of :ref:`django.utils ` can be considered stable: +the following parts of :doc:`django.utils ` can be considered stable: - ``django.utils.cache`` - ``django.utils.datastructures.SortedDict`` -- only this single class; the diff --git a/docs/misc/design-philosophies.txt b/docs/misc/design-philosophies.txt index 43bb8096c9aa..631097ae2bc4 100644 --- a/docs/misc/design-philosophies.txt +++ b/docs/misc/design-philosophies.txt @@ -1,5 +1,3 @@ -.. _misc-design-philosophies: - =================== Design philosophies =================== diff --git a/docs/misc/distributions.txt b/docs/misc/distributions.txt index 6a0845801d48..d9281ad3dab4 100644 --- a/docs/misc/distributions.txt +++ b/docs/misc/distributions.txt @@ -1,5 +1,3 @@ -.. _misc-distributions: - =================================== Third-party distributions of Django =================================== diff --git a/docs/misc/index.txt b/docs/misc/index.txt index 534171b6eda1..b42baeb9fdb2 100644 --- a/docs/misc/index.txt +++ b/docs/misc/index.txt @@ -1,5 +1,3 @@ -.. _misc-index: - Meta-documentation and miscellany ================================= diff --git a/docs/obsolete/admin-css.txt b/docs/obsolete/admin-css.txt index 4f8fb663e2d0..f4cca549b444 100644 --- a/docs/obsolete/admin-css.txt +++ b/docs/obsolete/admin-css.txt @@ -1,5 +1,3 @@ -.. _obsolete-admin-css: - ====================================== Customizing the Django admin interface ====================================== diff --git a/docs/obsolete/index.txt b/docs/obsolete/index.txt index 09e0826b88bf..ddc86237cc0f 100644 --- a/docs/obsolete/index.txt +++ b/docs/obsolete/index.txt @@ -1,5 +1,3 @@ -.. _obsolete-index: - Deprecated/obsolete documentation ================================= diff --git a/docs/ref/authbackends.txt b/docs/ref/authbackends.txt index 0e98c21b21f2..a50b414c782c 100644 --- a/docs/ref/authbackends.txt +++ b/docs/ref/authbackends.txt @@ -1,5 +1,3 @@ -.. _ref-authentication-backends: - ======================= Authentication backends ======================= @@ -10,8 +8,8 @@ Authentication backends This document details the authentication backends that come with Django. For information on how to use them and how to write your own authentication backends, see the :ref:`Other authentication sources section -` of the :ref:`User authentication guide -`. +` of the :doc:`User authentication guide +`. Available authentication backends @@ -33,5 +31,5 @@ The following backends are available in :mod:`django.contrib.auth.backends`: Use this backend to take advantage of external-to-Django-handled authentication. It authenticates using usernames passed in :attr:`request.META['REMOTE_USER'] `. See - the :ref:`Authenticating against REMOTE_USER ` + the :doc:`Authenticating against REMOTE_USER ` documentation. diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index 62f944d9b6cf..f3bdf1a82f50 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-admin-actions: - ============= Admin actions ============= @@ -208,7 +206,7 @@ objects. To provide an intermediary page, simply return an :class:`~django.http.HttpResponse` (or subclass) from your action. For example, you might write a simple export function that uses Django's -:ref:`serialization functions ` to dump some selected +:doc:`serialization functions ` to dump some selected objects as JSON:: from django.http import HttpResponse diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 1fbae3f06026..86163c8401a7 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-admin: - ===================== The Django admin site ===================== @@ -678,7 +676,7 @@ do that:: Note that the key in the dictionary is the actual field class, *not* a string. The value is another dictionary; these arguments will be passed to -:meth:`~django.forms.Field.__init__`. See :ref:`ref-forms-api` for details. +:meth:`~django.forms.Field.__init__`. See :doc:`/ref/forms/api` for details. .. warning:: @@ -696,7 +694,7 @@ The value is another dictionary; these arguments will be passed to .. versionadded:: 1.1 A list of actions to make available on the change list page. See -:ref:`ref-contrib-admin-actions` for details. +:doc:`/ref/contrib/admin/actions` for details. .. attribute:: ModelAdmin.actions_on_top .. attribute:: ModelAdmin.actions_on_bottom @@ -747,8 +745,8 @@ templates used by the :class:`ModelAdmin` views: Path to a custom template, used by the :meth:`delete_selected` action method for displaying a confirmation page when deleting one - or more objects. See the :ref:`actions - documentation`. + or more objects. See the :doc:`actions + documentation`. .. attribute:: ModelAdmin.object_history_template @@ -805,7 +803,7 @@ described above in the :attr:`ModelAdmin.readonly_fields` section. The ``get_urls`` method on a ``ModelAdmin`` returns the URLs to be used for that ModelAdmin in the same way as a URLconf. Therefore you can extend them as -documented in :ref:`topics-http-urls`:: +documented in :doc:`/topics/http/urls`:: class MyModelAdmin(admin.ModelAdmin): def get_urls(self): @@ -969,7 +967,7 @@ on your ``ModelAdmin``:: js = ("my_code.js",) Keep in mind that this will be prepended with ``MEDIA_URL``. The same rules -apply as :ref:`regular media definitions on forms `. +apply as :doc:`regular media definitions on forms `. Django admin Javascript makes use of the `jQuery`_ library. To avoid conflict with user scripts, Django's jQuery is namespaced as @@ -1002,8 +1000,8 @@ any field:: return self.cleaned_data["name"] It is important you use a ``ModelForm`` here otherwise things can break. See the -:ref:`forms ` documentation on :ref:`custom validation -` and, more specifically, the +:doc:`forms ` documentation on :doc:`custom validation +` and, more specifically, the :ref:`model form validation notes ` for more information. @@ -1075,7 +1073,7 @@ all the same functionality as well as some of its own: This controls the number of extra forms the formset will display in addition to the initial forms. See the - :ref:`formsets documentation ` for more information. + :doc:`formsets documentation ` for more information. .. versionadded:: 1.2 @@ -1298,7 +1296,7 @@ example app:: ``django.contrib.contenttypes.generic`` provides both a ``GenericTabularInline`` and ``GenericStackedInline`` and behave just like any other inline. See the -:ref:`contenttypes documentation ` for more specific +:doc:`contenttypes documentation ` for more specific information. Overriding Admin Templates diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index 03f5ff12813b..619b38e5aca6 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -1,6 +1,4 @@ -.. _ref-contrib-auth: - ``django.contrib.auth`` ======================= -See :ref:`topics-auth`. +See :doc:`/topics/auth`. diff --git a/docs/ref/contrib/comments/custom.txt b/docs/ref/contrib/comments/custom.txt index 9e32fc4fedd3..49299d4d3365 100644 --- a/docs/ref/contrib/comments/custom.txt +++ b/docs/ref/contrib/comments/custom.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-comments-custom: - ================================== Customizing the comments framework ================================== diff --git a/docs/ref/contrib/comments/example.txt b/docs/ref/contrib/comments/example.txt index d4ce623bb0e8..424bdb13f5c7 100644 --- a/docs/ref/contrib/comments/example.txt +++ b/docs/ref/contrib/comments/example.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-comments-example: - .. highlightlang:: html+django =========================================== @@ -7,7 +5,7 @@ Example of using the in-built comments app =========================================== Follow the first three steps of the quick start guide in the -:ref:`documentation `. +:doc:`documentation `. Now suppose, you have an app (``blog``) with a model (``Post``) to which you want to attach comments. Let us also suppose that @@ -85,8 +83,8 @@ It looks for the ``form.html`` under the following directories Since we customize the form in the second method, we make use of another tag called :ttag:`comment_form_target`. This tag on rendering gives the URL -where the comment form is posted. Without any :ref:`customization -`, :ttag:`comment_form_target` evaluates to +where the comment form is posted. Without any :doc:`customization +`, :ttag:`comment_form_target` evaluates to ``/comments/post/``. We use this tag in the form's ``action`` attribute. The :ttag:`get_comment_form` tag renders a ``form`` for a model instance by @@ -136,7 +134,7 @@ found under the directory structure we saw for ``form.html``. Feeds ===== -Suppose you want to export a :ref:`feed ` of the +Suppose you want to export a :doc:`feed ` of the latest comments, you can use the in-built :class:`LatestCommentFeed`. Just enable it in your project's ``urls.py``: @@ -163,8 +161,8 @@ Moderation Now that we have the comments framework working, we might want to have some moderation setup to administer the comments. The comments framework comes -in-built with :ref:`generic comment moderation -`. The comment moderation has the following +in-built with :doc:`generic comment moderation +`. The comment moderation has the following features (all of which or only certain can be enabled): * Enable comments for a particular model instance. @@ -181,18 +179,18 @@ following way: from django.contrib.comments.moderation import CommentModerator, moderator from django.db import models - + class Post(models.Model): title = models.CharField(max_length = 255) content = models.TextField() posted_date = models.DateTimeField() - + class PostModerator(CommentModerator): email_notification = True auto_close_field = 'posted_date' # Close the comments after 7 days. close_after = 7 - + moderator.register(Post, PostModerator) The generic comment moderation also has the facility to remove comments. diff --git a/docs/ref/contrib/comments/forms.txt b/docs/ref/contrib/comments/forms.txt index 3eacb5db54a2..c21a27bb9e16 100644 --- a/docs/ref/contrib/comments/forms.txt +++ b/docs/ref/contrib/comments/forms.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-comments-forms: - ==================== Comment form classes ==================== @@ -9,7 +7,7 @@ Comment form classes The ``django.contrib.comments.forms`` module contains a handful of forms you'll use when writing custom views dealing with comments, or when writing -:ref:`custom comment apps `. +:doc:`custom comment apps `. .. class:: CommentForm @@ -23,7 +21,7 @@ you'll use when writing custom views dealing with comments, or when writing Abstract comment forms for custom comment apps ---------------------------------------------- -If you're building a :ref:`custom comment app `, +If you're building a :doc:`custom comment app `, you might want to replace *some* of the form logic but still rely on parts of the existing form. diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index 9f1d3cd6e40f..817871e97654 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-comments-index: - =========================== Django's comments framework =========================== @@ -16,7 +14,7 @@ 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 :ref:`upgrade guide ` + need to upgrade. See the :doc:`upgrade guide ` for instructions. Quick start guide @@ -42,7 +40,7 @@ To get started using the ``comments`` app, follow these steps: #. Use the `comment template tags`_ below to embed comments in your templates. -You might also want to examine :ref:`ref-contrib-comments-settings`. +You might also want to examine :doc:`/ref/contrib/comments/settings`. Comment template tags ===================== @@ -124,7 +122,7 @@ For example:: {% endfor %} This returns a list of :class:`~django.contrib.comments.models.Comment` objects; -see :ref:`the comment model documentation ` for +see :doc:`the comment model documentation ` for details. .. templatetag:: get_comment_permalink @@ -212,7 +210,7 @@ Rendering a custom comment form ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want more control over the look and feel of the comment form, you use use -:ttag:`get_comment_form` to get a :ref:`form object ` that +:ttag:`get_comment_form` to get a :doc:`form object ` that you can use in the template:: {% get_comment_form for [object] as [varname] %} @@ -279,8 +277,8 @@ should know about: it with a warning field; if you use the comment form with a custom template you should be sure to do the same. -The comments app also depends on the more general :ref:`Cross Site Request -Forgery protection < ref-contrib-csrf>` that comes with Django. As described in +The comments app also depends on the more general :doc:`Cross Site Request +Forgery protection ` that comes with Django. As described in the documentation, it is best to use ``CsrfViewMiddleware``. However, if you are not using that, you will need to use the ``csrf_protect`` decorator on any views that include the comment form, in order for those views to be able to diff --git a/docs/ref/contrib/comments/models.txt b/docs/ref/contrib/comments/models.txt index 51aa117a1ca6..e773790d65d6 100644 --- a/docs/ref/contrib/comments/models.txt +++ b/docs/ref/contrib/comments/models.txt @@ -1,82 +1,80 @@ -.. _ref-contrib-comments-models: - =========================== The built-in comment models =========================== .. module:: django.contrib.comments.models :synopsis: The built-in comment models - + .. class:: Comment Django's built-in comment model. Has the following fields: - + .. attribute:: content_object - + A :class:`~django.contrib.contettypes.generic.GenericForeignKey` attribute pointing to the object the comment is attached to. You can use this to get at the related object (i.e. ``my_comment.content_object``). - + Since this field is a :class:`~django.contrib.contettypes.generic.GenericForeignKey`, it's actually syntactic sugar on top of two underlying attributes, described below. - + .. attribute:: content_type - + A :class:`~django.db.models.ForeignKey` to :class:`~django.contrib.contenttypes.models.ContentType`; this is the type of the object the comment is attached to. - + .. attribute:: object_pk - + A :class:`~django.db.models.TextField` containing the primary key of the object the comment is attached to. - + .. attribute:: site - + A :class:`~django.db.models.ForeignKey` to the :class:`~django.contrib.sites.models.Site` on which the comment was posted. - + .. attribute:: user - + A :class:`~django.db.models.ForeignKey` to the :class:`~django.contrib.auth.models.User` who posted the comment. May be blank if the comment was posted by an unauthenticated user. - + .. attribute:: user_name - + The name of the user who posted the comment. - + .. attribute:: user_email - + The email of the user who posted the comment. - + .. attribute:: user_url - + The URL entered by the person who posted the comment. - + .. attribute:: comment - + The actual content of the comment itself. - + .. attribute:: submit_date - + The date the comment was submitted. - + .. attribute:: ip_address - + The IP address of the user posting the comment. - + .. attribute:: is_public - + ``False`` if the comment is in moderation (see - :ref:`ref-contrib-comments-moderation`); If ``True``, the comment will + :doc:`/ref/contrib/comments/moderation`); If ``True``, the comment will be displayed on the site. - + .. attribute:: is_removed - + ``True`` if the comment was removed. Used to keep track of removed comments instead of just deleting them. - + diff --git a/docs/ref/contrib/comments/moderation.txt b/docs/ref/contrib/comments/moderation.txt index 2c4072ba5b69..198f78fa8905 100644 --- a/docs/ref/contrib/comments/moderation.txt +++ b/docs/ref/contrib/comments/moderation.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-comments-moderation: - ========================== Generic comment moderation ========================== diff --git a/docs/ref/contrib/comments/settings.txt b/docs/ref/contrib/comments/settings.txt index ff94d2dbccbc..1f1aecafd496 100644 --- a/docs/ref/contrib/comments/settings.txt +++ b/docs/ref/contrib/comments/settings.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-comments-settings: - ================ Comment settings ================ @@ -29,7 +27,7 @@ this will be rejected. Defaults to 3000. COMMENTS_APP ------------ -An app which provides :ref:`customization of the comments framework -`. Use the same dotted-string notation +An app which provides :doc:`customization of the comments framework +`. Use the same dotted-string notation as in :setting:`INSTALLED_APPS`. Your custom :setting:`COMMENTS_APP` must also be listed in :setting:`INSTALLED_APPS`. diff --git a/docs/ref/contrib/comments/signals.txt b/docs/ref/contrib/comments/signals.txt index cd406b56dd83..7ae34a1900f1 100644 --- a/docs/ref/contrib/comments/signals.txt +++ b/docs/ref/contrib/comments/signals.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-comments-signals: - ================================ Signals sent by the comments app ================================ @@ -7,9 +5,9 @@ Signals sent by the comments app .. module:: django.contrib.comments.signals :synopsis: Signals sent by the comment module. -The comment app sends a series of :ref:`signals ` to allow for -comment moderation and similar activities. See :ref:`the introduction to signals -` for information about how to register for and receive these +The comment app sends a series of :doc:`signals ` to allow for +comment moderation and similar activities. See :doc:`the introduction to signals +` for information about how to register for and receive these signals. comment_will_be_posted diff --git a/docs/ref/contrib/comments/upgrade.txt b/docs/ref/contrib/comments/upgrade.txt index dc9bff868c1c..3d6b5af66894 100644 --- a/docs/ref/contrib/comments/upgrade.txt +++ b/docs/ref/contrib/comments/upgrade.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-comments-upgrade: - =============================================== Upgrading from Django's previous comment system =============================================== @@ -11,8 +9,8 @@ The main changes from the old system are: * This new system is documented. - * It uses modern Django features like :ref:`forms ` and - :ref:`modelforms `. + * It uses modern Django features like :doc:`forms ` and + :doc:`modelforms `. * It has a single ``Comment`` model instead of separate ``FreeComment`` and ``Comment`` models. @@ -42,7 +40,7 @@ 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 -:ref:`quick start guide `. +:doc:`quick start guide `. 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 diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index da5d934d38c2..de56b0f0eecd 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-contenttypes: - ========================== The contenttypes framework ========================== @@ -346,7 +344,7 @@ it would be deleted at the same time. Generic relations and aggregation --------------------------------- -:ref:`Django's database aggregation API ` +:doc:`Django's database aggregation API ` doesn't work with a :class:`~django.contrib.contenttypes.generic.GenericRelation`. For example, you might be tempted to try something like:: @@ -365,8 +363,8 @@ Generic relations in forms and admin :class:`~django.contrib.contenttypes.generic.GenericInlineFormSet` and :class:`~django.contrib.contenttypes.generic.GenericInlineModelAdmin`. This enables the use of generic relations in forms and the admin. See the -:ref:`model formset ` and -:ref:`admin ` documentation for more information. +:doc:`model formset ` and +:doc:`admin ` documentation for more information. .. class:: generic.GenericInlineModelAdmin diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index d8a944b10af4..979c221581f0 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-csrf: - ===================================== Cross Site Request Forgery protection ===================================== diff --git a/docs/ref/contrib/databrowse.txt b/docs/ref/contrib/databrowse.txt index d3536d150c00..33c8228520cd 100644 --- a/docs/ref/contrib/databrowse.txt +++ b/docs/ref/contrib/databrowse.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-databrowse: - ========== Databrowse ========== @@ -49,8 +47,8 @@ How to use Databrowse Note that you should register the model *classes*, not instances. It doesn't matter where you put this, as long as it gets executed at some - point. A good place for it is in your :ref:`URLconf file - ` (``urls.py``). + point. A good place for it is in your :doc:`URLconf file + ` (``urls.py``). 3. Change your URLconf to import the :mod:`~django.contrib.databrowse` module:: @@ -73,20 +71,20 @@ code. Simply add the following import to your URLconf:: from django.contrib.auth.decorators import login_required -Then modify the :ref:`URLconf ` so that the +Then modify the :doc:`URLconf ` so that the :func:`databrowse.site.root` view is decorated with :func:`django.contrib.auth.decorators.login_required`:: (r'^databrowse/(.*)', login_required(databrowse.site.root)), -If you haven't already added support for user logins to your :ref:`URLconf -`, as described in the :ref:`user authentication docs -`, then you will need to do so now with the following +If you haven't already added support for user logins to your :doc:`URLconf +`, as described in the :doc:`user authentication docs +`, then you will need to do so now with the following mapping:: (r'^accounts/login/$', 'django.contrib.auth.views.login'), The final step is to create the login form required by :func:`django.contrib.auth.views.login`. The -:ref:`user authentication docs ` provide full details and a +:doc:`user authentication docs ` provide full details and a sample template that can be used for this purpose. diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt index 8c7f2781d02d..46b28dcfb2c6 100644 --- a/docs/ref/contrib/flatpages.txt +++ b/docs/ref/contrib/flatpages.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-flatpages: - ================= The flatpages app ================= @@ -92,8 +90,8 @@ Note that the order of :setting:`MIDDLEWARE_CLASSES` matters. Generally, you can put :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware` at the end of the list, because it's a last resort. -For more on middleware, read the :ref:`middleware docs -`. +For more on middleware, read the :doc:`middleware docs +`. .. admonition:: Ensure that your 404 template works @@ -124,9 +122,9 @@ Via the Python API .. class:: models.FlatPage Flatpages are represented by a standard - :ref:`Django model `, + :doc:`Django model `, which lives in `django/contrib/flatpages/models.py`_. You can access - flatpage objects via the :ref:`Django database API `. + flatpage objects via the :doc:`Django database API `. .. _django/contrib/flatpages/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/flatpages/models.py diff --git a/docs/ref/contrib/formtools/form-preview.txt b/docs/ref/contrib/formtools/form-preview.txt index ece69067ee16..a2cbea704a63 100644 --- a/docs/ref/contrib/formtools/form-preview.txt +++ b/docs/ref/contrib/formtools/form-preview.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-formtools-form-preview: - ============ Form preview ============ diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index 8dcebd1d6fb2..ab7b4829c9fe 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-formtools-form-wizard: - =========== Form wizard =========== @@ -10,7 +8,7 @@ Form wizard .. versionadded:: 1.0 Django comes with an optional "form wizard" application that splits -:ref:`forms ` across multiple Web pages. It maintains +:doc:`forms ` across multiple Web pages. It maintains state in hashed HTML :samp:`` fields, and the data isn't processed server-side until the final form is submitted. @@ -65,8 +63,8 @@ Defining ``Form`` classes The first step in creating a form wizard is to create the :class:`~django.forms.Form` classes. These should be standard -:class:`django.forms.Form` classes, covered in the :ref:`forms documentation -`. These classes can live anywhere in your codebase, but +:class:`django.forms.Form` classes, covered in the :doc:`forms documentation +`. These classes can live anywhere in your codebase, but convention is to put them in a file called :file:`forms.py` in your application. diff --git a/docs/ref/contrib/formtools/index.txt b/docs/ref/contrib/formtools/index.txt index 92010a25dbdc..f36470654a96 100644 --- a/docs/ref/contrib/formtools/index.txt +++ b/docs/ref/contrib/formtools/index.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-formtools-index: - django.contrib.formtools ======================== diff --git a/docs/ref/contrib/gis/admin.txt b/docs/ref/contrib/gis/admin.txt index df93c585049e..011bb6b6bf17 100644 --- a/docs/ref/contrib/gis/admin.txt +++ b/docs/ref/contrib/gis/admin.txt @@ -54,7 +54,7 @@ GeoDjango's admin site existing geometry fields in the admin. .. note:: - + This is different from adding the geometry field to :attr:`~django.contrib.admin.ModelAdmin.readonly_fields`, which will only display the WKT of the geometry. Setting diff --git a/docs/ref/contrib/gis/commands.txt b/docs/ref/contrib/gis/commands.txt index 2cb7f69887f9..3dd161ce1dd2 100644 --- a/docs/ref/contrib/gis/commands.txt +++ b/docs/ref/contrib/gis/commands.txt @@ -78,6 +78,6 @@ of using ``ogrinspect`` :ref:`in the tutorial `. all applicable fields. .. django-admin-option:: --srid - + The SRID to use for the geometry field. If not set, ``ogrinspect`` attempts to automatically determine of the SRID of the data source. diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 6797ce2de097..fbced8e6e1eb 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -14,7 +14,7 @@ Spatial Backends .. versionadded:: 1.2 -In Django 1.2, support for :ref:`multiple databases ` was +In Django 1.2, support for :doc:`multiple databases ` was introduced. In order to support multiple databases, GeoDjango has segregated its functionality into full-fledged spatial database backends: @@ -26,7 +26,7 @@ its functionality into full-fledged spatial database backends: Database Settings Backwards-Compatibility ----------------------------------------- -In :ref:`Django 1.2 `, the way +In :doc:`Django 1.2 `, the way to :ref:`specify databases ` in your settings was changed. The old database settings format (e.g., the ``DATABASE_*`` settings) is backwards compatible with GeoDjango, and will automatically use the @@ -60,7 +60,7 @@ MySQL's spatial extensions only support bounding box operations [``Contains``, ``Crosses``, ``Disjoint``, ``Intersects``, ``Overlaps``, ``Touches``, ``Within``] according to the specification. Those that are implemented return - the same result as the corresponding MBR-based functions. + the same result as the corresponding MBR-based functions. In other words, while spatial lookups such as :lookup:`contains ` are available in GeoDjango when using MySQL, the results returned are really @@ -92,8 +92,8 @@ model):: >>> z.save() Moreover, if the ``GEOSGeometry`` is in a different coordinate system (has a -different SRID value) than that of the field, then it will be implicitly -transformed into the SRID of the model's field, using the spatial database's +different SRID value) than that of the field, then it will be implicitly +transformed into the SRID of the model's field, using the spatial database's transform procedure:: >>> poly_3084 = GEOSGeometry('POLYGON(( 10 10, 10 20, 20 20, 20 15, 10 10))', srid=3084) # SRID 3084 is 'NAD83(HARN) / Texas Centric Lambert Conformal' @@ -133,17 +133,17 @@ For example:: >>> qs = Zipcode.objects.filter(poly__contains=pnt) In this case, ``poly`` is the geographic field, :lookup:`contains ` -is the spatial lookup type, and ``pnt`` is the parameter (which may be a +is the spatial lookup type, and ``pnt`` is the parameter (which may be a :class:`~django.contrib.gis.geos.GEOSGeometry` object or a string of GeoJSON , WKT, or HEXEWKB). -A complete reference can be found in the :ref:`spatial lookup reference +A complete reference can be found in the :ref:`spatial lookup reference `. .. note:: - GeoDjango constructs spatial SQL with the :class:`GeoQuerySet`, a - subclass of :class:`~django.db.models.QuerySet`. The + GeoDjango constructs spatial SQL with the :class:`GeoQuerySet`, a + subclass of :class:`~django.db.models.QuerySet`. The :class:`GeoManager` instance attached to your model is what enables use of :class:`GeoQuerySet`. @@ -156,8 +156,8 @@ Introduction ------------ Distance calculations with spatial data is tricky because, unfortunately, the Earth is not flat. Some distance queries with fields in a geographic -coordinate system may have to be expressed differently because of -limitations in PostGIS. Please see the :ref:`selecting-an-srid` section +coordinate system may have to be expressed differently because of +limitations in PostGIS. Please see the :ref:`selecting-an-srid` section in the :ref:`ref-gis-model-api` documentation for more details. .. _distance-lookups-intro: @@ -181,7 +181,7 @@ The following distance lookups are available: Distance lookups take a tuple parameter comprising: -#. A geometry to base calculations from; and +#. A geometry to base calculations from; and #. A number or :class:`~django.contrib.gis.measure.Distance` object containing the distance. If a :class:`~django.contrib.gis.measure.Distance` object is used, @@ -191,8 +191,8 @@ to be in the units of the field. .. note:: - For users of PostGIS 1.4 and below, the routine ``ST_Distance_Sphere`` - is used by default for calculating distances on geographic coordinate systems + For users of PostGIS 1.4 and below, the routine ``ST_Distance_Sphere`` + is used by default for calculating distances on geographic coordinate systems (e.g., WGS84) -- which may only be called with point geometries [#fndistsphere14]_. Thus, geographic distance lookups on traditional PostGIS geometry columns are only allowed on :class:`PointField` model fields using a point for the @@ -212,24 +212,24 @@ to be in the units of the field. You can tell GeoDjango to use a geography column by setting ``geography=True`` in your field definition. -For example, let's say we have a ``SouthTexasCity`` model (from the -`GeoDjango distance tests`__ ) on a *projected* coordinate system valid for cities +For example, let's say we have a ``SouthTexasCity`` model (from the +`GeoDjango distance tests`__ ) on a *projected* coordinate system valid for cities in southern Texas:: from django.contrib.gis.db import models - + class SouthTexasCity(models.Model): name = models.CharField(max_length=30) - # A projected coordinate system (only valid for South Texas!) + # A projected coordinate system (only valid for South Texas!) # is used, units are in meters. - point = models.PointField(srid=32140) + point = models.PointField(srid=32140) objects = models.GeoManager() Then distance queries may be performed as follows:: >>> from django.contrib.gis.geos import * >>> from django.contrib.gis.measure import D # ``D`` is a shortcut for ``Distance`` - >>> from geoapp import SouthTexasCity + >>> from geoapp import SouthTexasCity # Distances will be calculated from this point, which does not have to be projected. >>> pnt = fromstr('POINT(-96.876369 29.905320)', srid=4326) # If numeric parameter, units of field (meters in this case) are assumed. @@ -294,7 +294,7 @@ Lookup Type PostGIS Oracle MySQL [#]_ SpatiaLite ``GeoQuerySet`` Methods ----------------------- The following table provides a summary of what :class:`GeoQuerySet` methods -are available on each spatial backend. Please note that MySQL does not +are available on each spatial backend. Please note that MySQL does not support any of these methods, and is thus excluded from the table. ==================================== ======= ====== ========== @@ -330,7 +330,7 @@ Method PostGIS Oracle SpatiaLite :meth:`GeoQuerySet.translate` X X :meth:`GeoQuerySet.union` X X X :meth:`GeoQuerySet.unionagg` X X X -==================================== ======= ====== ========== +==================================== ======= ====== ========== .. rubric:: Footnotes .. [#fnwkt] *See* Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL `_, Document 99-049 (May 5, 1999), at Ch. 3.2.5, p. 3-11 (SQL Textual Representation of Geometry). diff --git a/docs/ref/contrib/gis/deployment.txt b/docs/ref/contrib/gis/deployment.txt index 2cfd367fac8c..c8dde3e540dc 100644 --- a/docs/ref/contrib/gis/deployment.txt +++ b/docs/ref/contrib/gis/deployment.txt @@ -54,7 +54,7 @@ Example:: number of ``processes`` instead. For more information, please consult Django's -:ref:`mod_wsgi documentation `. +:doc:`mod_wsgi documentation `. ``mod_python`` -------------- @@ -84,7 +84,7 @@ Example:: else your GeoDjango application may crash Apache. For more information, please consult Django's -:ref:`mod_python documentation `. +:doc:`mod_python documentation `. Lighttpd ======== diff --git a/docs/ref/contrib/gis/feeds.txt b/docs/ref/contrib/gis/feeds.txt index bb9c12ae5d38..7c3a2d011ca2 100644 --- a/docs/ref/contrib/gis/feeds.txt +++ b/docs/ref/contrib/gis/feeds.txt @@ -1,5 +1,3 @@ -.. _ref-gis-feeds: - ================ Geographic Feeds ================ @@ -8,10 +6,10 @@ Geographic Feeds :synopsis: GeoDjango's framework for generating spatial feeds. GeoDjango has its own :class:`Feed` subclass that may embed location information -in RSS/Atom feeds formatted according to either the `Simple GeoRSS`__ or +in RSS/Atom feeds formatted according to either the `Simple GeoRSS`__ or `W3C Geo`_ standards. Because GeoDjango's syndication API is a superset of -Django's, please consult `Django's syndication documentation ` -for details on general usage. +Django's, please consult :doc:`Django's syndication documentation +` for details on general usage. .. _W3C Geo: http://www.w3.org/2003/01/geo/ @@ -28,7 +26,7 @@ API Reference .. class:: Feed - In addition to methods provided by + In addition to methods provided by the :class:`django.contrib.syndication.feeds.Feed` base class, GeoDjango's ``Feed`` class provides the following overrides. Note that these overrides may be done in multiple ways:: diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index c413ff415794..69f0c02e8647 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -14,12 +14,12 @@ GeoQuerySet API Reference Spatial Lookups =============== -Just like when using the the :ref:`queryset-api`, interaction +Just like when using the the :ref:`queryset-api`, interaction with ``GeoQuerySet`` by :ref:`chaining filters `. Instead of the regular Django :ref:`field-lookups`, the spatial lookups in this section are available for :class:`GeometryField`. -For an introduction, see the :ref:`spatial lookups introduction +For an introduction, see the :ref:`spatial lookups introduction `. For an overview of what lookups are compatible with a particular spatial backend, refer to the :ref:`spatial lookup compatibility table `. @@ -31,7 +31,7 @@ bbcontains *Availability*: PostGIS, MySQL, SpatiaLite -Tests if the geometry field's bounding box completely contains the lookup +Tests if the geometry field's bounding box completely contains the lookup geometry's bounding box. Example:: @@ -53,7 +53,7 @@ bboverlaps *Availability*: PostGIS, MySQL, SpatiaLite -Tests if the geometry field's bounding box overlaps the lookup geometry's +Tests if the geometry field's bounding box overlaps the lookup geometry's bounding box. Example:: @@ -277,9 +277,9 @@ the values given in the given pattern. This lookup requires a tuple parameter, PostGIS & SpatiaLite ~~~~~~~~~~~~~~~~~~~~ -On these spatial backends the intersection pattern is a string comprising -nine characters, which define intersections between the interior, boundary, -and exterior of the geometry field and the lookup geometry. +On these spatial backends the intersection pattern is a string comprising +nine characters, which define intersections between the interior, boundary, +and exterior of the geometry field and the lookup geometry. The intersection pattern matrix may only use the following characters: ``1``, ``2``, ``T``, ``F``, or ``*``. This lookup type allows users to "fine tune" a specific geometric relationship consistent with the DE-9IM model. [#fnde9im]_ @@ -302,7 +302,7 @@ Oracle ~~~~~~ Here the relation pattern is compreised at least one of the nine relation -strings: ``TOUCH``, ``OVERLAPBDYDISJOINT``, ``OVERLAPBDYINTERSECT``, +strings: ``TOUCH``, ``OVERLAPBDYDISJOINT``, ``OVERLAPBDYINTERSECT``, ``EQUAL``, ``INSIDE``, ``COVEREDBY``, ``CONTAINS``, ``COVERS``, ``ON``, and ``ANYINTERACT``. Multiple strings may be combined with the logical Boolean operator OR, for example, ``'inside+touch'``. [#fnsdorelate]_ The relation @@ -312,7 +312,7 @@ Example:: Zipcode.objects.filter(poly__relate(geom, 'anyinteract')) -Oracle SQL equivalent:: +Oracle SQL equivalent:: SELECT ... WHERE SDO_RELATE(poly, geom, 'anyinteract') @@ -403,7 +403,7 @@ overlaps_left *Availability*: PostGIS -Tests if the geometry field's bounding box overlaps or is to the left of the lookup +Tests if the geometry field's bounding box overlaps or is to the left of the lookup geometry's bounding box. Example:: @@ -422,7 +422,7 @@ overlaps_right *Availability*: PostGIS -Tests if the geometry field's bounding box overlaps or is to the right of the lookup +Tests if the geometry field's bounding box overlaps or is to the right of the lookup geometry's bounding box. Example:: @@ -440,7 +440,7 @@ overlaps_above *Availability*: PostGIS -Tests if the geometry field's bounding box overlaps or is above the lookup +Tests if the geometry field's bounding box overlaps or is above the lookup geometry's bounding box. Example:: @@ -458,7 +458,7 @@ overlaps_below *Availability*: PostGIS -Tests if the geometry field's bounding box overlaps or is below the lookup +Tests if the geometry field's bounding box overlaps or is below the lookup geometry's bounding box. Example:: @@ -476,7 +476,7 @@ strictly_above *Availability*: PostGIS -Tests if the geometry field's bounding box is strictly above the lookup +Tests if the geometry field's bounding box is strictly above the lookup geometry's bounding box. Example:: @@ -494,7 +494,7 @@ strictly_below *Availability*: PostGIS -Tests if the geometry field's bounding box is strictly above the lookup +Tests if the geometry field's bounding box is strictly above the lookup geometry's bounding box. Example:: @@ -662,7 +662,7 @@ Keyword Argument Description ===================== ===================================================== ``field_name`` By default, ``GeoQuerySet`` methods use the first geographic field encountered in the model. This - keyword should be used to specify another + keyword should be used to specify another geographic field (e.g., ``field_name='point2'``) when there are multiple geographic fields in a model. @@ -670,7 +670,7 @@ Keyword Argument Description used on geometry fields in models that are related via a ``ForeignKey`` relation (e.g., ``field_name='related__point'``). - + ``model_att`` By default, ``GeoQuerySet`` methods typically attach their output in an attribute with the same name as the ``GeoQuerySet`` method. Setting this keyword @@ -679,12 +679,12 @@ Keyword Argument Description ``qs = Zipcode.objects.centroid(model_att='c')`` will attach the centroid of the ``Zipcode`` geometry field in a ``c`` attribute on every model rather than in a - ``centroid`` attribute. + ``centroid`` attribute. - This keyword is required if - a method name clashes with an existing - ``GeoQuerySet`` method -- if you wanted to use the - ``area()`` method on model with a ``PolygonField`` + This keyword is required if + a method name clashes with an existing + ``GeoQuerySet`` method -- if you wanted to use the + ``area()`` method on model with a ``PolygonField`` named ``area``, for example. ===================== ===================================================== @@ -705,12 +705,12 @@ each element of this GeoQuerySet. .. method:: GeoQuerySet.distance(geom, **kwargs) -This method takes a geometry as a parameter, and attaches a ``distance`` -attribute to every model in the returned queryset that contains the +This method takes a geometry as a parameter, and attaches a ``distance`` +attribute to every model in the returned queryset that contains the distance (as a :class:`~django.contrib.gis.measure.Distance` object) to the given geometry. -In the following example (taken from the `GeoDjango distance tests`__), -the distance from the `Tasmanian`__ city of Hobart to every other +In the following example (taken from the `GeoDjango distance tests`__), +the distance from the `Tasmanian`__ city of Hobart to every other :class:`PointField` in the ``AustraliaCity`` queryset is calculated:: >>> pnt = AustraliaCity.objects.get(name='Hobart').point @@ -732,7 +732,7 @@ the distance from the `Tasmanian`__ city of Hobart to every other Because the ``distance`` attribute is a :class:`~django.contrib.gis.measure.Distance` object, you can easily express the value in the units of your choice. For example, ``city.distance.mi`` is - the distance value in miles and ``city.distance.km`` is the distance value + the distance value in miles and ``city.distance.km`` is the distance value in kilometers. See the :ref:`ref-measure` for usage details and the list of :ref:`supported_units`. @@ -867,9 +867,9 @@ then 4326 (WGS84) is used by default. geometry is placed on the models. .. note:: - - What spatial reference system an integer SRID corresponds to may depend on - the spatial database used. In other words, the SRID numbers used for Oracle + + What spatial reference system an integer SRID corresponds to may depend on + the spatial database used. In other words, the SRID numbers used for Oracle are not necessarily the same as those used by PostGIS. Example:: @@ -903,7 +903,7 @@ to each element of the ``GeoQuerySet`` that is the result of the operation. .. method:: GeoQuerySet.difference(geom) Returns the spatial difference of the geographic field with the given -geometry in a ``difference`` attribute on each element of the +geometry in a ``difference`` attribute on each element of the ``GeoQuerySet``. @@ -913,7 +913,7 @@ geometry in a ``difference`` attribute on each element of the .. method:: GeoQuerySet.intersection(geom) Returns the spatial intersection of the geographic field with the -given geometry in an ``intersection`` attribute on each element of the +given geometry in an ``intersection`` attribute on each element of the ``GeoQuerySet``. ``sym_difference`` @@ -937,7 +937,7 @@ geometry in an ``union`` attribute on each element of the Geometry Output --------------- -The following ``GeoQuerySet`` methods will return an attribute that has the value +The following ``GeoQuerySet`` methods will return an attribute that has the value of the geometry field in each model converted to the requested output format. ``geohash`` @@ -967,8 +967,8 @@ Attaches a ``geojson`` attribute to every model in the queryset that contains th ===================== ===================================================== Keyword Argument Description ===================== ===================================================== -``precision`` It may be used to specify the number of significant - digits for the coordinates in the GeoJSON +``precision`` It may be used to specify the number of significant + digits for the coordinates in the GeoJSON representation -- the default value is 8. ``crs`` Set this to ``True`` if you want the coordinate @@ -988,8 +988,8 @@ __ http://geojson.org/ *Availability*: PostGIS, Oracle -Attaches a ``gml`` attribute to every model in the queryset that contains the -`Geographic Markup Language (GML)`__ representation of the geometry. +Attaches a ``gml`` attribute to every model in the queryset that contains the +`Geographic Markup Language (GML)`__ representation of the geometry. Example:: @@ -1000,9 +1000,9 @@ Example:: ===================== ===================================================== Keyword Argument Description ===================== ===================================================== -``precision`` This keyword is for PostGIS only. It may be used - to specify the number of significant digits for the - coordinates in the GML representation -- the default +``precision`` This keyword is for PostGIS only. It may be used + to specify the number of significant digits for the + coordinates in the GML representation -- the default value is 8. ``version`` This keyword is for PostGIS only. It may be used to @@ -1019,9 +1019,9 @@ __ http://en.wikipedia.org/wiki/Geography_Markup_Language *Availability*: PostGIS -Attaches a ``kml`` attribute to every model in the queryset that contains the -`Keyhole Markup Language (KML)`__ representation of the geometry fields. It -should be noted that the contents of the KML are transformed to WGS84 if +Attaches a ``kml`` attribute to every model in the queryset that contains the +`Keyhole Markup Language (KML)`__ representation of the geometry fields. It +should be noted that the contents of the KML are transformed to WGS84 if necessary. Example:: @@ -1033,8 +1033,8 @@ Example:: ===================== ===================================================== Keyword Argument Description ===================== ===================================================== -``precision`` This keyword may be used to specify the number of - significant digits for the coordinates in the KML +``precision`` This keyword may be used to specify the number of + significant digits for the coordinates in the KML representation -- the default value is 8. ===================== ===================================================== @@ -1054,11 +1054,11 @@ the `Scalable Vector Graphics (SVG)`__ path data of the geometry fields. Keyword Argument Description ===================== ===================================================== ``relative`` If set to ``True``, the path data will be implemented - in terms of relative moves. Defaults to ``False``, + in terms of relative moves. Defaults to ``False``, meaning that absolute moves are used instead. -``precision`` This keyword may be used to specify the number of - significant digits for the coordinates in the SVG +``precision`` This keyword may be used to specify the number of + significant digits for the coordinates in the SVG representation -- the default value is 8. ===================== ===================================================== @@ -1129,7 +1129,7 @@ dissolving boundaries. *Availability*: PostGIS, Oracle -Returns the extent of the ``GeoQuerySet`` as a four-tuple, comprising the +Returns the extent of the ``GeoQuerySet`` as a four-tuple, comprising the lower left coordinate and the upper right coordinate. Example:: @@ -1163,7 +1163,7 @@ Example:: *Availability*: PostGIS -Returns a ``LineString`` constructed from the point field geometries in the +Returns a ``LineString`` constructed from the point field geometries in the ``GeoQuerySet``. Currently, ordering the queryset has no effect. Example:: @@ -1184,25 +1184,25 @@ use of ``unionagg`` is processor intensive and may take a significant amount of time on large querysets. .. note:: - + If the computation time for using this method is too expensive, consider using :meth:`GeoQuerySet.collect` instead. Example:: - + >>> u = Zipcode.objects.unionagg() # This may take a long time. >>> u = Zipcode.objects.filter(poly__within=bbox).unionagg() # A more sensible approach. ===================== ===================================================== Keyword Argument Description ===================== ===================================================== -``tolerance`` This keyword is for Oracle only. It is for the +``tolerance`` This keyword is for Oracle only. It is for the tolerance value used by the ``SDOAGGRTYPE`` - procedure; the `Oracle documentation`__ has more + procedure; the `Oracle documentation`__ has more details. ===================== ===================================================== -__ http://download.oracle.com/docs/html/B14255_01/sdo_intro.htm#sthref150 +__ http://download.oracle.com/docs/html/B14255_01/sdo_intro.htm#sthref150 Aggregate Functions ------------------- diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 0b7954336cb1..ae36e167aef0 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -13,7 +13,7 @@ In general, GeoDjango installation requires: 3. :ref:`geospatial_libs` Details for each of the requirements and installation instructions -are provided in the sections below. In addition, platform-specific +are provided in the sections below. In addition, platform-specific instructions are available for: * :ref:`macosx` @@ -23,10 +23,10 @@ instructions are available for: .. admonition:: Use the Source Because GeoDjango takes advantage of the latest in the open source geospatial - software technology, recent versions of the libraries are necessary. + software technology, recent versions of the libraries are necessary. If binary packages aren't available for your platform, :ref:`installation from source ` - may be required. When compiling the libraries from source, please follow the + may be required. When compiling the libraries from source, please follow the directions closely, especially if you're a beginner. Requirements @@ -37,8 +37,8 @@ Requirements Python 2.4+ ----------- Because of heavy use of the decorator syntax, Python 2.4 is minimum -version supported by GeoDjango. Python 2.5+ is recommended because the -`ctypes`__ module comes included; otherwise, 2.4 users will need to +version supported by GeoDjango. Python 2.5+ is recommended because the +`ctypes`__ module comes included; otherwise, 2.4 users will need to `download and install ctypes`__. __ http://docs.python.org/lib/module-ctypes.html @@ -50,18 +50,18 @@ Django ------ Because GeoDjango is included with Django, please refer to Django's -:ref:`installation instructions ` for details on how to install. +:doc:`installation instructions ` for details on how to install. .. _spatial_database: Spatial Database ---------------- -PostgreSQL (with PostGIS), MySQL, Oracle, and SQLite (with SpatiaLite) are +PostgreSQL (with PostGIS), MySQL, Oracle, and SQLite (with SpatiaLite) are the spatial databases currently supported. .. note:: - PostGIS is recommended, because it is the most mature and feature-rich + PostGIS is recommended, because it is the most mature and feature-rich open source spatial database. The geospatial libraries required for a GeoDjango installation depends @@ -81,7 +81,7 @@ SQLite GEOS, GDAL, PROJ.4, SpatiaLite 3.6.+ Requires Geospatial Libraries -------------------- -GeoDjango uses and/or provides interfaces for the the following open source +GeoDjango uses and/or provides interfaces for the the following open source geospatial libraries: ======================== ==================================== ================================ ========================== @@ -117,7 +117,7 @@ Building from Source ==================== When installing from source on UNIX and GNU/Linux systems, please follow -the installation instructions carefully, and install the libraries in the +the installation instructions carefully, and install the libraries in the given order. If using MySQL or Oracle as the spatial database, only GEOS is required. @@ -147,13 +147,13 @@ internal geometry representation used by GeoDjango (it's behind the "lazy" geometries). Specifically, the C API library is called (e.g., ``libgeos_c.so``) directly from Python using ctypes. -First, download GEOS 3.2 from the refractions website and untar the source +First, download GEOS 3.2 from the refractions website and untar the source archive:: $ wget http://download.osgeo.org/geos/geos-3.2.2.tar.bz2 $ tar xjf geos-3.2.2.tar.bz2 -Next, change into the directory where GEOS was unpacked, run the configure +Next, change into the directory where GEOS was unpacked, run the configure script, compile, and install:: $ cd geos-3.2.2 @@ -172,7 +172,7 @@ When GeoDjango can't find GEOS, this error is raised:: ImportError: Could not find the GEOS library (tried "geos_c"). Try setting GEOS_LIBRARY_PATH in your settings. -The most common solution is to properly configure your :ref:`libsettings` *or* set +The most common solution is to properly configure your :ref:`libsettings` *or* set :ref:`geoslibrarypath` in your settings. If using a binary package of GEOS (e.g., on Ubuntu 8.10), you may need to :ref:`binutils`. @@ -191,7 +191,7 @@ C library. For example:: .. note:: - The setting must be the *full* path to the **C** shared library; in + The setting must be the *full* path to the **C** shared library; in other words you want to use ``libgeos_c.so``, not ``libgeos.so``. .. _proj4: @@ -199,7 +199,7 @@ C library. For example:: PROJ.4 ------ -`PROJ.4`_ is a library for converting geospatial data to different coordinate +`PROJ.4`_ is a library for converting geospatial data to different coordinate reference systems. First, download the PROJ.4 source code and datum shifting files [#]_:: @@ -228,12 +228,12 @@ PostGIS ------- `PostGIS`__ adds geographic object support to PostgreSQL, turning it -into a spatial database. :ref:`geosbuild` and :ref:`proj4` should be +into a spatial database. :ref:`geosbuild` and :ref:`proj4` should be installed prior to building PostGIS. .. note:: - The `psycopg2`_ module is required for use as the database adaptor + The `psycopg2`_ module is required for use as the database adaptor when using GeoDjango with PostGIS. .. _psycopg2: http://initd.org/projects/psycopg2 @@ -256,7 +256,7 @@ Finally, make and install:: .. note:: - GeoDjango does not automatically create a spatial database. Please + GeoDjango does not automatically create a spatial database. Please consult the section on :ref:`spatialdb_template` for more information. __ http://postgis.refractions.net/ @@ -267,7 +267,7 @@ GDAL ---- `GDAL`__ is an excellent open source geospatial library that has support for -reading most vector and raster spatial data formats. Currently, GeoDjango only +reading most vector and raster spatial data formats. Currently, GeoDjango only supports :ref:`GDAL's vector data ` capabilities [#]_. :ref:`geosbuild` and :ref:`proj4` should be installed prior to building GDAL. @@ -287,11 +287,11 @@ Configure, make and install:: .. note:: Because GeoDjango has it's own Python interface, the preceding instructions - do not build GDAL's own Python bindings. The bindings may be built by + do not build GDAL's own Python bindings. The bindings may be built by adding the ``--with-python`` flag when running ``configure``. See - `GDAL/OGR In Python`__ for more information on GDAL's bindings. + `GDAL/OGR In Python`__ for more information on GDAL's bindings. -If you have any problems, please see the troubleshooting section below for +If you have any problems, please see the troubleshooting section below for suggestions and solutions. __ http://trac.osgeo.org/gdal/ @@ -312,7 +312,7 @@ will be false:: >>> gdal.HAS_GDAL False -The solution is to properly configure your :ref:`libsettings` *or* set +The solution is to properly configure your :ref:`libsettings` *or* set :ref:`gdallibrarypath` in your settings. .. _gdallibrarypath: @@ -332,22 +332,22 @@ the GDAL library. For example:: Can't find GDAL data files (``GDAL_DATA``) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When installed from source, GDAL versions 1.5.1 and below have an autoconf bug -that places data in the wrong location. [#]_ This can lead to error messages +When installed from source, GDAL versions 1.5.1 and below have an autoconf bug +that places data in the wrong location. [#]_ This can lead to error messages like this:: ERROR 4: Unable to open EPSG support file gcs.csv. ... OGRException: OGR failure. -The solution is to set the ``GDAL_DATA`` environment variable to the location of the -GDAL data files before invoking Python (typically ``/usr/local/share``; use +The solution is to set the ``GDAL_DATA`` environment variable to the location of the +GDAL data files before invoking Python (typically ``/usr/local/share``; use ``gdal-config --datadir`` to find out). For example:: $ export GDAL_DATA=`gdal-config --datadir` $ python manage.py shell -If using Apache, you may need to add this environment variable to your configuration +If using Apache, you may need to add this environment variable to your configuration file:: SetEnv GDAL_DATA /usr/local/share @@ -363,13 +363,13 @@ SpatiaLite Mac OS X users should follow the instructions in the :ref:`kyngchaos` section, as it is much easier than building from source. -`SpatiaLite`__ adds spatial support to SQLite, turning it into a full-featured +`SpatiaLite`__ adds spatial support to SQLite, turning it into a full-featured spatial database. Because SpatiaLite has special requirements, it typically -requires SQLite and pysqlite2 (the Python SQLite DB-API adaptor) to be built from +requires SQLite and pysqlite2 (the Python SQLite DB-API adaptor) to be built from source. :ref:`geosbuild` and :ref:`proj4` should be installed prior to building SpatiaLite. -After installation is complete, don't forget to read the post-installation +After installation is complete, don't forget to read the post-installation docs on :ref:`create_spatialite_db`. __ http://www.gaia-gis.it/spatialite/index.html @@ -380,7 +380,7 @@ SQLite ^^^^^^ Typically, SQLite packages are not compiled to include the `R*Tree module`__ -- -thus it must be compiled from source. First download the latest amalgamation +thus it must be compiled from source. First download the latest amalgamation source archive from the `SQLite download page`__, and extract:: $ wget http://sqlite.org/sqlite-amalgamation-3.6.23.1.tar.gz @@ -398,7 +398,7 @@ needs to be customized so that SQLite knows to build the R*Tree module:: .. note:: If using Ubuntu, installing a newer SQLite from source can be very difficult - because it links to the existing ``libsqlite3.so`` in ``/usr/lib`` which + because it links to the existing ``libsqlite3.so`` in ``/usr/lib`` which many other packages depend on. Unfortunately, the best solution at this time is to overwrite the existing library by adding ``--prefix=/usr`` to the ``configure`` command. @@ -420,7 +420,7 @@ SpatiaLite library source and tools bundle from the `download page`__:: $ tar xzf spatialite-tools-2.3.1.tar.gz Prior to attempting to build, please read the important notes below to see if -customization of the ``configure`` command is necessary. If not, then run the +customization of the ``configure`` command is necessary. If not, then run the ``configure`` script, make, and install for the SpatiaLite library:: $ cd libspatialite-amalgamation-2.3.1 @@ -431,7 +431,7 @@ customization of the ``configure`` command is necessary. If not, then run the Finally, do the same for the SpatiaLite tools:: - $ cd spatialite-tools-2.3.1 + $ cd spatialite-tools-2.3.1 $ ./configure # May need to modified, see notes below. $ make $ sudo make install @@ -440,15 +440,15 @@ Finally, do the same for the SpatiaLite tools:: .. note:: If you've installed GEOS and PROJ.4 from binary packages, you will have to specify - their paths when running the ``configure`` scripts for *both* the library and the - tools (the configure scripts look, by default, in ``/usr/local``). For example, + their paths when running the ``configure`` scripts for *both* the library and the + tools (the configure scripts look, by default, in ``/usr/local``). For example, on Debian/Ubuntu distributions that have GEOS and PROJ.4 packages, the command would be:: - + $ ./configure --with-proj-include=/usr/include --with-proj-lib=/usr/lib --with-geos-include=/usr/include --with-geos-lib=/usr/lib .. note:: - For Mac OS X users building from source, the SpatiaLite library *and* tools + For Mac OS X users building from source, the SpatiaLite library *and* tools need to have their ``target`` configured:: $ ./configure --target=macosx @@ -463,7 +463,7 @@ pysqlite2 Because SpatiaLite must be loaded as an external extension, it requires the ``enable_load_extension`` method, which is only available in versions 2.5+. Thus, download pysqlite2 2.6, and untar:: - + $ wget http://pysqlite.googlecode.com/files/pysqlite-2.6.0.tar.gz $ tar xzf pysqlite-2.6.0.tar.gz $ cd pysqlite-2.6.0 @@ -484,7 +484,7 @@ to look like the following:: ``define=SQLITE_OMIT_LOAD_EXTENSION`` flag and that the ``include_dirs`` and ``library_dirs`` settings are uncommented and set to the appropriate path if the SQLite header files and libraries are not in ``/usr/include`` - and ``/usr/lib``, respectively. + and ``/usr/lib``, respectively. After modifying ``setup.cfg`` appropriately, then run the ``setup.py`` script to build and install:: @@ -500,7 +500,7 @@ Creating a Spatial Database Template for PostGIS ------------------------------------------------ Creating a spatial database with PostGIS is different than normal because -additional SQL must be loaded to enable spatial functionality. Because of +additional SQL must be loaded to enable spatial functionality. Because of the steps in this process, it's better to create a database template that can be reused later. @@ -518,7 +518,7 @@ user. For example, you can use the following to become the ``postgres`` user:: version 1.5 uses ``/contrib/postgis-1.5/postgis.sql``. The example below assumes PostGIS 1.5, thus you may need to modify - ``POSTGIS_SQL_PATH`` and the name of the SQL file for the specific + ``POSTGIS_SQL_PATH`` and the name of the SQL file for the specific version of PostGIS you are using. Once you're a database super user, then you may execute the following commands @@ -598,7 +598,7 @@ __ http://www.gaia-gis.it/spatialite/resources.html Add ``django.contrib.gis`` to ``INSTALLED_APPS`` ------------------------------------------------ -Like other Django contrib applications, you will *only* need to add +Like other Django contrib applications, you will *only* need to add :mod:`django.contrib.gis` to :setting:`INSTALLED_APPS` in your settings. This is the so that ``gis`` templates can be located -- if not done, then features such as the geographic admin or KML sitemaps will not function properly. @@ -630,7 +630,7 @@ Invoke the Django shell from your project and execute the In Django 1.1 the name of this function is ``add_postgis_srs``. This adds an entry for the 900913 SRID to the ``spatial_ref_sys`` (or equivalent) -table, making it possible for the spatial database to transform coordinates in +table, making it possible for the spatial database to transform coordinates in this projection. You only need to execute this command *once* per spatial database. Troubleshooting @@ -640,8 +640,8 @@ If you can't find the solution to your problem here then participate in the community! You can: * Join the ``#geodjango`` IRC channel on FreeNode (may be accessed on the - web via `Mibbit`__). Please be patient and polite -- while you may not - get an immediate response, someone will attempt to answer your question + web via `Mibbit`__). Please be patient and polite -- while you may not + get an immediate response, someone will attempt to answer your question as soon as they see it. * Ask your question on the `GeoDjango`__ mailing list. * File a ticket on the `Django trac`__ if you think there's a bug. Make @@ -659,7 +659,7 @@ Library Environment Settings By far, the most common problem when installing GeoDjango is that the external shared libraries (e.g., for GEOS and GDAL) cannot be located. [#]_ -Typically, the cause of this problem is that the operating system isn't aware +Typically, the cause of this problem is that the operating system isn't aware of the directory where the libraries built from source were installed. In general, the library path may be set on a per-user basis by setting @@ -670,9 +670,9 @@ system. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ A user may set this environment variable to customize the library paths -they want to use. The typical library directory for software +they want to use. The typical library directory for software built from source is ``/usr/local/lib``. Thus, ``/usr/local/lib`` needs -to be included in the ``LD_LIBRARY_PATH`` variable. For example, the user +to be included in the ``LD_LIBRARY_PATH`` variable. For example, the user could place the following in their bash profile:: export LD_LIBRARY_PATH=/usr/local/lib @@ -682,7 +682,7 @@ Setting System Library Path On GNU/Linux systems, there is typically a file in ``/etc/ld.so.conf``, which may include additional paths from files in another directory, such as ``/etc/ld.so.conf.d``. -As the root user, add the custom library path (like ``/usr/local/lib``) on a +As the root user, add the custom library path (like ``/usr/local/lib``) on a new line in ``ld.so.conf``. This is *one* example of how to do so:: $ sudo echo /usr/local/lib >> /etc/ld.so.conf @@ -702,9 +702,9 @@ Install ``binutils`` GeoDjango uses the ``find_library`` function (from the ``ctypes.util`` Python module) to discover libraries. The ``find_library`` routine uses a program -called ``objdump`` (part of the ``binutils`` package) to verify a shared +called ``objdump`` (part of the ``binutils`` package) to verify a shared library on GNU/Linux systems. Thus, if ``binutils`` is not installed on your -Linux system then Python's ctypes may not be able to find your library even if +Linux system then Python's ctypes may not be able to find your library even if your library path is set correctly and geospatial libraries were built perfectly. The ``binutils`` package may be installed on Debian and Ubuntu systems using the @@ -735,10 +735,10 @@ several different options for installing GeoDjango. These options are: .. note:: Currently, the easiest and recommended approach for installing GeoDjango - on OS X is to use the KyngChaos packages. + on OS X is to use the KyngChaos packages. -This section also includes instructions for installing an upgraded version -of :ref:`macosx_python` from packages provided by the Python Software +This section also includes instructions for installing an upgraded version +of :ref:`macosx_python` from packages provided by the Python Software Foundation, however, this is not required. .. _macosx_python: @@ -747,8 +747,8 @@ Python ^^^^^^ Although OS X comes with Python installed, users can use framework -installers (`2.5`__ and `2.6`__ are available) provided by -the Python Software Foundation. An advantage to using the installer is +installers (`2.5`__ and `2.6`__ are available) provided by +the Python Software Foundation. An advantage to using the installer is that OS X's Python will remain "pristine" for internal operating system use. @@ -756,7 +756,7 @@ __ http://python.org/ftp/python/2.5.4/python-2.5.4-macosx.dmg __ http://python.org/ftp/python/2.6.2/python-2.6.2-macosx2009-04-16.dmg .. note:: - + You will need to modify the ``PATH`` environment variable in your ``.profile`` file so that the new version of Python is used when ``python`` is entered at the command-line:: @@ -768,15 +768,15 @@ __ http://python.org/ftp/python/2.6.2/python-2.6.2-macosx2009-04-16.dmg KyngChaos Packages ^^^^^^^^^^^^^^^^^^ -William Kyngesburye provides a number of `geospatial library binary packages`__ -that make it simple to get GeoDjango installed on OS X without compiling +William Kyngesburye provides a number of `geospatial library binary packages`__ +that make it simple to get GeoDjango installed on OS X without compiling them from source. However, the `Apple Developer Tools`_ are still necessary for compiling the Python database adapters :ref:`psycopg2_kyngchaos` (for PostGIS) -and :ref:`pysqlite2_kyngchaos` (for SpatiaLite). +and :ref:`pysqlite2_kyngchaos` (for SpatiaLite). .. note:: - SpatiaLite users should consult the :ref:`spatialite_kyngchaos` section + SpatiaLite users should consult the :ref:`spatialite_kyngchaos` section after installing the packages for additional instructions. Download the framework packages for: @@ -834,7 +834,7 @@ described above, ``psycopg2`` may be installed using the following command:: pysqlite2 ~~~~~~~~~ -Follow the :ref:`pysqlite2` source install instructions, however, +Follow the :ref:`pysqlite2` source install instructions, however, when editing the ``setup.cfg`` use the following instead:: [build_ext] @@ -851,7 +851,7 @@ SpatiaLite When :ref:`create_spatialite_db`, the ``spatialite`` program is required. However, instead of attempting to compile the SpatiaLite tools from source, -download the `SpatiaLite Binaries`__ for OS X, and install ``spatialite`` in a +download the `SpatiaLite Binaries`__ for OS X, and install ``spatialite`` in a location available in your ``PATH``. For example:: $ curl -O http://www.gaia-gis.it/spatialite/spatialite-tools-osx-x86-2.3.1.tar.gz @@ -887,9 +887,9 @@ __ http://www.finkproject.org/ MacPorts ^^^^^^^^ -`MacPorts`__ may be used to install GeoDjango prerequisites on Macintosh +`MacPorts`__ may be used to install GeoDjango prerequisites on Macintosh computers running OS X. Because MacPorts still builds the software from source, -the `Apple Developer Tools`_ are required. +the `Apple Developer Tools`_ are required. Summary:: @@ -898,7 +898,7 @@ Summary:: $ sudo port install proj $ sudo port install postgis $ sudo port install gdal - $ sudo port install libgeoip + $ sudo port install libgeoip .. note:: @@ -929,9 +929,9 @@ Ubuntu 8.04 and lower ~~~~~~~~~~~~~~ -The 8.04 (and lower) versions of Ubuntu use GEOS v2.2.3 in their binary packages, -which is incompatible with GeoDjango. Thus, do *not* use the binary packages -for GEOS or PostGIS and build some prerequisites from source, per the instructions +The 8.04 (and lower) versions of Ubuntu use GEOS v2.2.3 in their binary packages, +which is incompatible with GeoDjango. Thus, do *not* use the binary packages +for GEOS or PostGIS and build some prerequisites from source, per the instructions in this document; however, it is okay to use the PostgreSQL binary packages. For more details, please see the Debian instructions for :ref:`etch` below. @@ -970,11 +970,11 @@ Optional packages to consider: * ``python-gdal`` for GDAL's own Python bindings -- includes interfaces for raster manipulation .. note:: - + The Ubuntu ``proj`` package does not come with the datum shifting files - installed, which will cause problems with the geographic admin because + installed, which will cause problems with the geographic admin because the ``null`` datum grid is not available for transforming geometries to the - spherical mercator projection. A solution is to download the + spherical mercator projection. A solution is to download the datum-shifting files, create the grid file, and install it yourself:: $ wget http://download.osgeo.org/proj/proj-datumgrid-1.4.tar.gz @@ -985,7 +985,7 @@ Optional packages to consider: $ sudo cp null /usr/share/proj Otherwise, the Ubuntu ``proj`` package is fine for general use as long as you - do not plan on doing any database transformation of geometries to the + do not plan on doing any database transformation of geometries to the Google projection (900913). .. note:: @@ -1032,14 +1032,14 @@ Optional packages: Source Packages ~~~~~~~~~~~~~~~ You will still have to install :ref:`geosbuild`, :ref:`proj4`, -:ref:`postgis`, and :ref:`gdalbuild` from source. Please follow the +:ref:`postgis`, and :ref:`gdalbuild` from source. Please follow the directions carefully. .. _lenny: 5.0 (Lenny) ^^^^^^^^^^^ -This version is comparable to Ubuntu :ref:`ibex`, so the command +This version is comparable to Ubuntu :ref:`ibex`, so the command is very similar:: $ sudo apt-get install binutils libgdal1-1.5.0 postgresql-8.3 postgresql-8.3-postgis postgresql-server-dev-8.3 python-psycopg2 python-setuptools @@ -1086,13 +1086,13 @@ Python ^^^^^^ First, download the `Python 2.6 installer`__ from the Python website. Next, -execute the installer and use defaults, e.g., keep 'Install for all users' +execute the installer and use defaults, e.g., keep 'Install for all users' checked and the installation path set as ``C:\Python26``. .. note:: You may already have a version of Python installed in ``C:\python`` as ESRI - products sometimes install a copy there. *You should still install a + products sometimes install a copy there. *You should still install a fresh version of Python 2.6.* __ http://python.org/ftp/python/2.6.2/python-2.6.2.msi @@ -1107,21 +1107,21 @@ the EnterpriseDB website. PostgreSQL 8.3 is required because PostGIS is not available yet for 8.4. -After downloading, simply click on the installer, follow the -on-screen directions, and keep the default options (e.g., keep the installation +After downloading, simply click on the installer, follow the +on-screen directions, and keep the default options (e.g., keep the installation path as ``C:\Program Files\PostgreSQL\8.3``). .. note:: - This PostgreSQL installation process will create both a new windows user to be the - 'postgres service account' and a special 'postgres superuser' to own the database - cluster. You will be prompted to set a password for both users (make sure to write - them down!). To see basic details on the 'service user' account right click on - 'My Computer' and select 'Manage' or go to: Control Panel -> Administrative Tools -> + This PostgreSQL installation process will create both a new windows user to be the + 'postgres service account' and a special 'postgres superuser' to own the database + cluster. You will be prompted to set a password for both users (make sure to write + them down!). To see basic details on the 'service user' account right click on + 'My Computer' and select 'Manage' or go to: Control Panel -> Administrative Tools -> Computer Management -> System Tools -> Local Users and Groups. -If installed successfully, the PostgreSQL server will run in the background each time -the system as started as a Windows service. When finished, the installer should launch +If installed successfully, the PostgreSQL server will run in the background each time +the system as started as a Windows service. When finished, the installer should launch the Application Stack Builder (ASB) -- use this to install PostGIS, see instructions below for more details. A 'PostgreSQL 8.3' start menu group should be created that contains shortcuts for the ASB and 'Command Prompt', which launches a terminal window @@ -1132,22 +1132,22 @@ __ http://www.enterprisedb.com/products/pgdownload.do#windows PostGIS ^^^^^^^ -From the Application Stack Builder (Programs -> PostgreSQL 8.3), select -'PostgreSQL Database Server 8.3 on port 5432' from the drop down menu. Next, +From the Application Stack Builder (Programs -> PostgreSQL 8.3), select +'PostgreSQL Database Server 8.3 on port 5432' from the drop down menu. Next, select 'PostGIS 1.3.6 for PostgreSQL 8.3' from the 'Spatial Extensions' tree -in the list. Select only the default options during install (do not uncheck +in the list. Select only the default options during install (do not uncheck the option to create a default PostGIS database). .. note:: - You will be prompted to enter your 'postgres superuser' password in the + You will be prompted to enter your 'postgres superuser' password in the 'Database Connection Information' dialog. psycopg2 ^^^^^^^^ The ``psycopg2`` Python module provides the interface between Python and the -PostgreSQL database. Download the `Windows installer`__ (v2.0.10) and run +PostgreSQL database. Download the `Windows installer`__ (v2.0.10) and run using the default settings. [#]_ __ http://www.stickpeople.com/projects/python/win-psycopg/psycopg2-2.0.10.win32-py2.6-pg8.3.7-release.exe @@ -1160,31 +1160,31 @@ of the process for installing GeoDjango on Windows platforms. The installer automatically installs Django 1.1, GDAL 1.6.0, PROJ 4.6.1 (including datum grid files), and configures the necessary environment variables. -Once the installer has completed, log out and log back in so that the +Once the installer has completed, log out and log back in so that the modifications to the system environment variables take effect, and you should be good to go. .. note:: The installer modifies the system ``Path`` environment variable to - include ``C:\Program Files\PostgreSQL\8.3\bin`` and + include ``C:\Program Files\PostgreSQL\8.3\bin`` and ``C:\Program Files\GeoDjango\bin``. This is required so that Python may find the GEOS DLL provided by PostGIS and the GDAL DLL provided - by the installer. The installer also sets the ``GDAL_DATA`` and + by the installer. The installer also sets the ``GDAL_DATA`` and ``PROJ_LIB`` environment variables. __ http://geodjango.org/windows/GeoDjango_Installer.exe .. rubric:: Footnotes .. [#] The datum shifting files are needed for converting data to and from certain projections. - For example, the PROJ.4 string for the `Google projection (900913) `_ - requires the ``null`` grid file only included in the extra datum shifting files. + For example, the PROJ.4 string for the `Google projection (900913) `_ + requires the ``null`` grid file only included in the extra datum shifting files. It is easier to install the shifting files now, then to have debug a problem caused by their absence later. .. [#] Specifically, GeoDjango provides support for the `OGR `_ library, a component of GDAL. .. [#] See `GDAL ticket #2382 `_. .. [#] GeoDjango uses the `find_library `_ - routine from ``ctypes.util`` to locate shared libraries. -.. [#] The ``psycopg2`` Windows installers are packaged and maintained by - `Jason Erickson `_. -.. [#] The source code for the installer is available in the `nsis_installer `_ + routine from ``ctypes.util`` to locate shared libraries. +.. [#] The ``psycopg2`` Windows installers are packaged and maintained by + `Jason Erickson `_. +.. [#] The source code for the installer is available in the `nsis_installer `_ GeoDjango mercurial repository. diff --git a/docs/ref/contrib/gis/layermapping.txt b/docs/ref/contrib/gis/layermapping.txt index a423259c114c..0b09e176f6e0 100644 --- a/docs/ref/contrib/gis/layermapping.txt +++ b/docs/ref/contrib/gis/layermapping.txt @@ -14,7 +14,7 @@ vector spatial data files (e.g. shapefiles) intoto GeoDjango models. This utility grew out of the author's personal needs to eliminate the code repetition that went into pulling geometries and fields out of -a vector layer, converting to another coordinate system (e.g. WGS84), and +a vector layer, converting to another coordinate system (e.g. WGS84), and then inserting into a GeoDjango model. .. note:: @@ -27,7 +27,7 @@ then inserting into a GeoDjango model. that :class:`LayerMapping` is using too much memory, set :setting:`DEBUG` to ``False`` in your settings. When :setting:`DEBUG` is set to ``True``, Django :ref:`automatically logs ` - *every* SQL query -- thus, when SQL statements contain geometries, it is + *every* SQL query -- thus, when SQL statements contain geometries, it is easy to consume more memory than is typical. Example @@ -50,7 +50,7 @@ Example DATUM["WGS_1984", SPHEROID["WGS_1984",6378137,298.257223563]], PRIMEM["Greenwich",0], - UNIT["Degree",0.017453292519943295]] + UNIT["Degree",0.017453292519943295]] 2. Now we define our corresponding Django model (make sure to use ``syncdb``):: @@ -71,16 +71,16 @@ Example >>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str' layer field. 'poly' : 'POLYGON', # For geometry fields use OGC name. } # The mapping is a dictionary - >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping) - >>> lm.save(verbose=True) # Save the layermap, imports the data. + >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping) + >>> lm.save(verbose=True) # Save the layermap, imports the data. Saved: Name: 1 Saved: Name: 2 Saved: Name: 3 Here, :class:`LayerMapping` just transformed the three geometries from the shapefile in their original spatial reference system (WGS84) to the spatial -reference system of the GeoDjango model (NAD83). If no spatial reference -system is defined for the layer, use the ``source_srs`` keyword with a +reference system of the GeoDjango model (NAD83). If no spatial reference +system is defined for the layer, use the ``source_srs`` keyword with a :class:`~django.contrib.gis.gdal.SpatialReference` object to specify one. ``LayerMapping`` API @@ -106,43 +106,43 @@ Argument Description model field is a geographic then it should correspond to the OGR geometry type, e.g., ``'POINT'``, ``'LINESTRING'``, ``'POLYGON'``. -================= ========================================================= +================= ========================================================= ===================== ===================================================== Keyword Arguments -===================== ===================================================== -``layer`` The index of the layer to use from the Data Source +===================== ===================================================== +``layer`` The index of the layer to use from the Data Source (defaults to 0) - -``source_srs`` Use this to specify the source SRS manually (for - example, some shapefiles don't come with a '.prj' - file). An integer SRID, WKT or PROJ.4 strings, and - :class:`django.contrib.gis.gdal.SpatialReference` + +``source_srs`` Use this to specify the source SRS manually (for + example, some shapefiles don't come with a '.prj' + file). An integer SRID, WKT or PROJ.4 strings, and + :class:`django.contrib.gis.gdal.SpatialReference` objects are accepted. - -``encoding`` Specifies the character set encoding of the strings - in the OGR data source. For example, ``'latin-1'``, - ``'utf-8'``, and ``'cp437'`` are all valid encoding + +``encoding`` Specifies the character set encoding of the strings + in the OGR data source. For example, ``'latin-1'``, + ``'utf-8'``, and ``'cp437'`` are all valid encoding parameters. - -``transaction_mode`` May be ``'commit_on_success'`` (default) or + +``transaction_mode`` May be ``'commit_on_success'`` (default) or ``'autocommit'``. - -``transform`` Setting this to False will disable coordinate + +``transform`` Setting this to False will disable coordinate transformations. In other words, geometries will be inserted into the database unmodified from their original state in the data source. - + ``unique`` Setting this to the name, or a tuple of names, from the given model will create models unique - only to the given name(s). Geometries will from - each feature will be added into the collection - associated with the unique model. Forces + only to the given name(s). Geometries will from + each feature will be added into the collection + associated with the unique model. Forces the transaction mode to be ``'autocommit'``. ``using`` New in version 1.2. Sets the database to use when importing spatial data. Default is ``'default'`` -===================== ===================================================== +===================== ===================================================== ``save()`` Keyword Arguments ---------------------------- @@ -156,42 +156,42 @@ specific feature ranges. =========================== ================================================= Save Keyword Arguments Description =========================== ================================================= -``fid_range`` May be set with a slice or tuple of - (begin, end) feature ID's to map from +``fid_range`` May be set with a slice or tuple of + (begin, end) feature ID's to map from the data source. In other words, this - keyword enables the user to selectively + keyword enables the user to selectively import a subset range of features in the geographic data source. ``progress`` When this keyword is set, status information - will be printed giving the number of features - processed and successfully saved. By default, + will be printed giving the number of features + processed and successfully saved. By default, progress information will be printed every 1000 - features processed, however, this default may - be overridden by setting this keyword with an + features processed, however, this default may + be overridden by setting this keyword with an integer for the desired interval. -``silent`` By default, non-fatal error notifications are - printed to ``sys.stdout``, but this keyword may +``silent`` By default, non-fatal error notifications are + printed to ``sys.stdout``, but this keyword may be set to disable these notifications. -``step`` If set with an integer, transactions will - occur at every step interval. For example, if - ``step=1000``, a commit would occur after the +``step`` If set with an integer, transactions will + occur at every step interval. For example, if + ``step=1000``, a commit would occur after the 1,000th feature, the 2,000th feature etc. -``stream`` Status information will be written to this file - handle. Defaults to using ``sys.stdout``, but +``stream`` Status information will be written to this file + handle. Defaults to using ``sys.stdout``, but any object with a ``write`` method is supported. -``strict`` Execution of the model mapping will cease upon +``strict`` Execution of the model mapping will cease upon the first error encountered. The default value (``False``) behavior is to attempt to continue. -``verbose`` If set, information will be printed - subsequent to each model save +``verbose`` If set, information will be printed + subsequent to each model save executed on the database. =========================== ================================================= @@ -213,7 +213,7 @@ If you encounter the following error when using ``LayerMapping`` and MySQL:: OperationalError: (1153, "Got a packet bigger than 'max_allowed_packet' bytes") Then the solution is to increase the value of the ``max_allowed_packet`` -setting in your MySQL configuration. For example, the default value may +setting in your MySQL configuration. For example, the default value may be something low like one megabyte -- the setting may be modified in MySQL's configuration file (``my.cnf``) in the ``[mysqld]`` section:: diff --git a/docs/ref/contrib/gis/measure.txt b/docs/ref/contrib/gis/measure.txt index 8b9629ef808f..6971788b4e76 100644 --- a/docs/ref/contrib/gis/measure.txt +++ b/docs/ref/contrib/gis/measure.txt @@ -7,17 +7,17 @@ Measurement Objects .. module:: django.contrib.gis.measure :synopsis: GeoDjango's distance and area measurment objects. -The :mod:`django.contrib.gis.measure` module contains objects that allow -for convenient representation of distance and area units of measure. [#]_ -Specifically, it implements two objects, :class:`Distance` and -:class:`Area` -- both of which may be accessed via the +The :mod:`django.contrib.gis.measure` module contains objects that allow +for convenient representation of distance and area units of measure. [#]_ +Specifically, it implements two objects, :class:`Distance` and +:class:`Area` -- both of which may be accessed via the :class:`D` and :class:`A` convenience aliases, respectively. Example ======= -:class:`Distance` objects may be instantiated using a keyword argument indicating the -context of the units. In the example below, two different distance objects are +:class:`Distance` objects may be instantiated using a keyword argument indicating the +context of the units. In the example below, two different distance objects are instantiated in units of kilometers (``km``) and miles (``mi``):: >>> from django.contrib.gis.measure import Distance, D @@ -40,7 +40,7 @@ Moreover, arithmetic operations may be performed between the distance objects:: >>> print d1 + d2 # Adding 5 miles to 5 kilometers - 13.04672 km + 13.04672 km >>> print d2 - d1 # Subtracting 5 kilometers from 5 miles 1.89314403881 mi @@ -67,7 +67,7 @@ Supported units ================================= ======================================== Unit Attribute Full name or alias(es) ================================= ======================================== -``km`` Kilometre, Kilometer +``km`` Kilometre, Kilometer ``mi`` Mile ``m`` Meter, Metre ``yd`` Yard @@ -163,7 +163,7 @@ Measurement API 12.949940551680001 .. classmethod:: unit_attname(unit_name) - + Returns the area unit attribute name for the given full unit name. For example:: @@ -175,6 +175,6 @@ Measurement API Alias for :class:`Area` class. .. rubric:: Footnotes -.. [#] `Robert Coup `_ is the initial author of the measure objects, - and was inspired by Brian Beck's work in `geopy `_ +.. [#] `Robert Coup `_ is the initial author of the measure objects, + and was inspired by Brian Beck's work in `geopy `_ and Geoff Biggs' PhD work on dimensioned units for robotics. diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index 7c83a7e267d8..cf73747463a1 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -8,11 +8,11 @@ GeoDjango Model API :synopsis: GeoDjango model and field API. This document explores the details of the GeoDjango Model API. Throughout this -section, we'll be using the following geographic model of a `ZIP code`__ as our +section, we'll be using the following geographic model of a `ZIP code`__ as our example:: from django.contrib.gis.db import models - + class Zipcode(models.Model): code = models.CharField(max_length=5) poly = models.PolygonField() @@ -23,7 +23,7 @@ __ http://en.wikipedia.org/wiki/ZIP_code Geometry Field Types ==================== -Each of the following geometry field types correspond with the +Each of the following geometry field types correspond with the OpenGIS Simple Features specification [#fnogc]_. ``GeometryField`` @@ -92,7 +92,7 @@ Selecting an SRID ^^^^^^^^^^^^^^^^^ Choosing an appropriate SRID for your model is an important decision that the -developer should consider carefully. The SRID is an integer specifier that +developer should consider carefully. The SRID is an integer specifier that corresponds to the projection system that will be used to interpret the data in the spatial database. [#fnsrid]_ Projection systems give the context to the coordinates that specify a location. Although the details of `geodesy`__ are @@ -105,7 +105,7 @@ location on the earth's surface. However, latitude and longitude are angles, not distances. [#fnharvard]_ In other words, while the shortest path between two points on a flat surface is a straight line, the shortest path between two points on a curved surface (such as the earth) is an *arc* of a `great circle`__. [#fnthematic]_ Thus, -additional computation is required to obtain distances in planar units (e.g., +additional computation is required to obtain distances in planar units (e.g., kilometers and miles). Using a geographic coordinate system may introduce complications for the developer later on. For example, PostGIS versions 1.4 and below do not have the capability to perform distance calculations between @@ -113,12 +113,12 @@ non-point geometries using geographic coordinate systems, e.g., constructing a query to find all points within 5 miles of a county boundary stored as WGS84. [#fndist]_ -Portions of the earth's surface may projected onto a two-dimensional, or +Portions of the earth's surface may projected onto a two-dimensional, or Cartesian, plane. Projected coordinate systems are especially convenient for region-specific applications, e.g., if you know that your database will -only cover geometries in `North Kansas`__, then you may consider using projection -system specific to that region. Moreover, projected coordinate systems are -defined in Cartesian units (such as meters or feet), easing distance +only cover geometries in `North Kansas`__, then you may consider using projection +system specific to that region. Moreover, projected coordinate systems are +defined in Cartesian units (such as meters or feet), easing distance calculations. .. note:: @@ -131,7 +131,7 @@ calculations. Additional Resources: -* `spatialreference.org`__: A Django-powered database of spatial reference +* `spatialreference.org`__: A Django-powered database of spatial reference systems. * `The State Plane Coordinate System`__: A website covering the various projection systems used in the United States. Much of the U.S. spatial @@ -150,7 +150,7 @@ __ http://welcome.warnercnr.colostate.edu/class_info/nr502/lg3/datums_coordinate .. attribute:: GeometryField.spatial_index Defaults to ``True``. Creates a spatial index for the given geometry -field. +field. .. note:: @@ -185,7 +185,7 @@ three-dimensonal support. .. attribute:: GeometryField.geography If set to ``True``, this option will create a database column of -type geography, rather than geometry. Please refer to the +type geography, rather than geometry. Please refer to the :ref:`geography type ` section below for more details. @@ -212,7 +212,7 @@ to degrees if called on a geometry column in WGS84). Because geography calculations involve more mathematics, only a subset of the PostGIS spatial lookups are available for the geography type. Practically, this means that in addition to the :ref:`distance lookups ` -only the following additional :ref:`spatial lookups ` are +only the following additional :ref:`spatial lookups ` are available for geography columns: * :lookup:`bboverlaps` @@ -231,13 +231,13 @@ determining `when to use geography data type over geometry data type .. currentmodule:: django.contrib.gis.db.models .. class:: GeoManager -In order to conduct geographic queries, each geographic model requires +In order to conduct geographic queries, each geographic model requires a ``GeoManager`` model manager. This manager allows for the proper SQL -construction for geographic queries; thus, without it, all geographic filters +construction for geographic queries; thus, without it, all geographic filters will fail. It should also be noted that ``GeoManager`` is required even if the -model does not have a geographic field itself, e.g., in the case of a -``ForeignKey`` relation to a model with a geographic field. For example, -if we had an ``Address`` model with a ``ForeignKey`` to our ``Zipcode`` +model does not have a geographic field itself, e.g., in the case of a +``ForeignKey`` relation to a model with a geographic field. For example, +if we had an ``Address`` model with a ``ForeignKey`` to our ``Zipcode`` model:: from django.contrib.gis.db import models @@ -251,7 +251,7 @@ model:: zipcode = models.ForeignKey(Zipcode) objects = models.GeoManager() -The geographic manager is needed to do spatial queries on related ``Zipcode`` objects, +The geographic manager is needed to do spatial queries on related ``Zipcode`` objects, for example:: qs = Address.objects.filter(zipcode__poly__contains='POINT(-104.590948 38.319914)') @@ -260,7 +260,7 @@ for example:: .. [#fnogc] OpenGIS Consortium, Inc., `Simple Feature Specification For SQL `_, Document 99-049 (May 5, 1999). .. [#fnogcsrid] *See id.* at Ch. 2.3.8, p. 39 (Geometry Values and Spatial Reference Systems). .. [#fnsrid] Typically, SRID integer corresponds to an EPSG (`European Petroleum Survey Group `_) identifier. However, it may also be associated with custom projections defined in spatial database's spatial reference systems table. -.. [#fnharvard] Harvard Graduate School of Design, `An Overview of Geodesy and Geographic Referencing Systems `_. This is an excellent resource for an overview of principles relating to geographic and Cartesian coordinate systems. +.. [#fnharvard] Harvard Graduate School of Design, `An Overview of Geodesy and Geographic Referencing Systems `_. This is an excellent resource for an overview of principles relating to geographic and Cartesian coordinate systems. .. [#fnthematic] Terry A. Slocum, Robert B. McMaster, Fritz C. Kessler, & Hugh H. Howard, *Thematic Cartography and Geographic Visualization* (Prentice Hall, 2nd edition), at Ch. 7.1.3. .. [#fndist] This limitation does not apply to PostGIS 1.5. It should be noted that even in previous versions of PostGIS, this isn't impossible using GeoDjango; you could for example, take a known point in a projected coordinate system, buffer it to the appropriate radius, and then perform an intersection operation with the buffer transformed to the geographic coordinate system. .. [#fngeography] Please refer to the `PostGIS Geography Type `_ documentation for more details. diff --git a/docs/ref/contrib/gis/testing.txt b/docs/ref/contrib/gis/testing.txt index b2513f670b00..3401e4d76987 100644 --- a/docs/ref/contrib/gis/testing.txt +++ b/docs/ref/contrib/gis/testing.txt @@ -6,7 +6,7 @@ Testing GeoDjango Apps In Django 1.2, the addition of :ref:`spatial-backends` simplified the process of testing GeoDjango applications. Specifically, testing -GeoDjango applications is now the same as :ref:`topics-testing`. +GeoDjango applications is now the same as :doc:`/topics/testing`. Included in this documentation are some additional notes and settings for :ref:`testing-postgis` and :ref:`testing-spatialite` users. diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 5b535186f8b4..3a56c2e7c0f8 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -15,8 +15,8 @@ geographic web applications, like location-based services. Some features includ data formats. * Editing of geometry fields inside the admin. -This tutorial assumes a familiarity with Django; thus, if you're brand new to -Django please read through the :ref:`regular tutorial ` to introduce +This tutorial assumes a familiarity with Django; thus, if you're brand new to +Django please read through the :doc:`regular tutorial ` to introduce yourself with basic Django concepts. .. note:: @@ -27,12 +27,12 @@ yourself with basic Django concepts. This tutorial is going to guide you through guide the user 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 +the code used in this tutorial is taken from and/or inspired by the `GeoDjango basic apps`_ project. [#]_ .. note:: - Proceed through the tutorial sections sequentially for step-by-step + Proceed through the tutorial sections sequentially for step-by-step instructions. .. _OGC: http://www.opengeospatial.org/ @@ -51,11 +51,11 @@ Create a Spatial Database 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 +PostgreSQL and PostGIS, then the following commands will create the database from a :ref:`spatial database template `:: $ createdb -T template_postgis geodjango - + .. note:: This command must be issued by a database user that has permissions to @@ -65,9 +65,9 @@ create the database from a :ref:`spatial database template ` $ sudo su - postgres $ createuser --createdb geo $ exit - + Replace ``geo`` to correspond to the system login user name will be - connecting to the database. For example, ``johndoe`` if that is the + connecting to the database. For example, ``johndoe`` if that is the system user that will be running GeoDjango. Users of SQLite and SpatiaLite should consult the instructions on how @@ -92,7 +92,7 @@ Configure ``settings.py`` The ``geodjango`` project settings are stored in the ``settings.py`` file. Edit the database connection settings appropriately:: - DATABASES = { + DATABASES = { 'default': { 'ENGINE': 'django.contrib.gis.db.backends.postgis', 'NAME': 'geodjango', @@ -104,7 +104,7 @@ the database connection settings appropriately:: These database settings are for Django 1.2 and above. -In addition, modify the :setting:`INSTALLED_APPS` setting to include +In addition, modify the :setting:`INSTALLED_APPS` setting to include :mod:`django.contrib.admin`, :mod:`django.contrib.gis`, and ``world`` (our newly created application):: @@ -142,8 +142,8 @@ unzipped the world borders data set includes files with the following extensions * ``.shp``: Holds the vector data for the world borders geometries. * ``.shx``: Spatial index file for geometries stored in the ``.shp``. -* ``.dbf``: Database file for holding non-geometric attribute data - (e.g., integer and character fields). +* ``.dbf``: Database file for holding non-geometric attribute data + (e.g., integer and character fields). * ``.prj``: Contains the spatial reference information for the geographic data stored in the shapefile. @@ -153,7 +153,7 @@ __ http://en.wikipedia.org/wiki/Shapefile Use ``ogrinfo`` to examine spatial data --------------------------------------- -The GDAL ``ogrinfo`` utility is excellent for examining metadata about +The GDAL ``ogrinfo`` utility is excellent for examining metadata about shapefiles (or other vector data sources):: $ ogrinfo world/data/TM_WORLD_BORDERS-0.3.shp @@ -192,13 +192,13 @@ 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"), +(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`_ website, this shows -you how to determine this information yourself when such metadata is not +you how to determine this information yourself when such metadata is not provided. Geographic Models @@ -213,7 +213,7 @@ create a GeoDjango model to represent this data:: from django.contrib.gis.db import models class WorldBorders(models.Model): - # Regular Django fields corresponding to the attributes in the + # Regular Django fields corresponding to the attributes in the # world borders shapefile. name = models.CharField(max_length=50) area = models.IntegerField() @@ -227,7 +227,7 @@ create a GeoDjango model to represent this data:: lon = models.FloatField() lat = models.FloatField() - # GeoDjango-specific: a geometry field (MultiPolygonField), and + # GeoDjango-specific: a geometry field (MultiPolygonField), and # overriding the default manager with a GeoManager instance. mpoly = models.MultiPolygonField() objects = models.GeoManager() @@ -235,23 +235,23 @@ create a GeoDjango model to represent this data:: # So the model is pluralized correctly in the admin. class Meta: verbose_name_plural = "World Borders" - - # Returns the string representation of the model. + + # Returns the string representation of the model. def __unicode__(self): return self.name Two important things to note: 1. The ``models`` module is imported from :mod:`django.contrib.gis.db`. -2. The model overrides its default manager with +2. The model overrides its default manager with :class:`~django.contrib.gis.db.models.GeoManager`; this is *required* - to perform spatial queries. + 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. +with an integer corresponding to the coordinate system of your choice. __ http://en.wikipedia.org/wiki/SRID @@ -259,7 +259,7 @@ 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 ``WorldBorders`` +First, let's look at the SQL that will generate the table for the ``WorldBorders`` model:: $ python manage.py sqlall world @@ -295,7 +295,7 @@ If satisfied, you may then create this table in the database by running the Installing custom SQL for world.WorldBorders 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 +do so (not required now, may be done at any point in the future using the ``createsuperuser`` management command). Importing Spatial Data @@ -303,11 +303,11 @@ 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 :ref:`ref-layermapping`. -There are many different different ways to import data in to a +There are many different 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: -* `ogr2ogr`_: Command-line utility, included with GDAL, that +* `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 @@ -339,7 +339,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, the world borders shapefile may be opened using GeoDjango's :class:`~django.contrib.gis.gdal.DataSource` interface:: >>> from django.contrib.gis.gdal import * @@ -347,7 +347,7 @@ Now, the world borders shapefile may be opened using GeoDjango's >>> print ds / ... /geodjango/world/data/TM_WORLD_BORDERS-0.3.shp (ESRI Shapefile) -Data source objects can have different layers of geospatial features; however, +Data source objects can have different layers of geospatial features; however, shapefiles are only allowed to have one layer:: >>> print len(ds) @@ -367,10 +367,10 @@ contains:: .. note:: Unfortunately the shapefile data format does not allow for greater - specificity with regards to geometry types. This shapefile, like + 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`` + as a GeoDjango ``PolygonField`` will not accept a ``MultiPolygon`` type geometry -- thus a ``MultiPolygonField`` is used in our model's definition instead. @@ -391,7 +391,7 @@ system associated with it -- if it does, the ``srs`` attribute will return a 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. -In addition, shapefiles also support attribute fields that may contain +In addition, shapefiles also support attribute fields that may contain additional data. Here are the fields on the World Borders layer: >>> print lyr.fields @@ -403,8 +403,8 @@ a 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'] -You can iterate over each feature in the layer and extract information from both -the feature's geometry (accessed via the ``geom`` attribute) as well as the +You can iterate over each feature in the layer and extract information from both +the feature's geometry (accessed via the ``geom`` attribute) as well as the feature's attribute fields (whose **values** are accessed via ``get()`` method):: @@ -427,7 +427,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 +Here the boundary geometry for San Marino is extracted and looking exported to WKT and GeoJSON:: >>> geom = feat.geom @@ -465,7 +465,7 @@ We're going to dive right in -- create a file called ``load.py`` inside the world_shp = os.path.abspath(os.path.join(os.path.dirname(__file__), 'data/TM_WORLD_BORDERS-0.3.shp')) def run(verbose=True): - lm = LayerMapping(WorldBorders, world_shp, world_mapping, + lm = LayerMapping(WorldBorders, world_shp, world_mapping, transform=False, encoding='iso-8859-1') lm.save(strict=True, verbose=verbose) @@ -473,8 +473,8 @@ 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 - ``WorldBorders`` model, and the value is the name of the shapefile field - that data will be loaded from. + ``WorldBorders`` model, and 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 @@ -503,10 +503,10 @@ do the work:: Try ``ogrinspect`` ------------------ -Now that you've seen how to define geographic models and import data with the +Now that you've seen how to define geographic models and import data with the :ref:`ref-layermapping`, it's possible to further automate this process with use of the :djadmin:`ogrinspect` management command. The :djadmin:`ogrinspect` -command introspects a GDAL-supported vector data source (e.g., a shapefile) and +command introspects a GDAL-supported vector data source (e.g., a shapefile) and generates a model definition and ``LayerMapping`` dictionary automatically. The general usage of the command goes as follows:: @@ -525,13 +525,13 @@ and mapping dictionary created above, automatically:: A few notes about the command-line options given above: * The ``--srid=4326`` option sets the SRID for the geographic field. -* The ``--mapping`` option tells ``ogrinspect`` to also generate a +* The ``--mapping`` option tells ``ogrinspect`` to also generate a mapping dictionary for use with :class:`~django.contrib.gis.utils.LayerMapping`. * The ``--multi`` option is specified so that the geographic field is a :class:`~django.contrib.gis.db.models.MultiPolygonField` instead of just a :class:`~django.contrib.gis.db.models.PolygonField`. -The command produces the following output, which may be copied +The command produces the following output, which may be copied directly into the ``models.py`` of a GeoDjango application:: # This is an auto-generated Django model module created by ogrinspect. @@ -584,7 +584,7 @@ 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 +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 Consortium (OGC). [#]_ Import the ``WorldBorders`` model, and perform a ``contains`` lookup using the ``pnt_wkt`` as the parameter:: @@ -611,7 +611,7 @@ available -- the :ref:`ref-gis-db-api` documentation has more. Automatic Spatial Transformations --------------------------------- -When querying the spatial database GeoDjango automatically transforms +When querying the spatial database 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`__, a coordinate system specific to south Texas **only** and in units of @@ -634,26 +634,26 @@ of abstraction:: ('SELECT "world_worldborders"."id", "world_worldborders"."name", "world_worldborders"."area", "world_worldborders"."pop2005", "world_worldborders"."fips", "world_worldborders"."iso2", "world_worldborders"."iso3", "world_worldborders"."un", "world_worldborders"."region", - "world_worldborders"."subregion", "world_worldborders"."lon", "world_worldborders"."lat", - "world_worldborders"."mpoly" FROM "world_worldborders" + "world_worldborders"."subregion", "world_worldborders"."lon", "world_worldborders"."lat", + "world_worldborders"."mpoly" FROM "world_worldborders" WHERE ST_Intersects("world_worldborders"."mpoly", ST_Transform(%s, 4326))', (,)) >>> qs # printing evaluates the queryset - [] + [] __ 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 `, -exposing powerful functionality, such as serialization properties for +access of the geometry field, GeoDjango creates a `GEOS geometry object `, +exposing powerful functionality, such as serialization properties for popular geospatial formats:: >>> sm = WorldBorders.objects.get(name='San Marino') >>> sm.mpoly - >>> sm.mpoly.wkt # WKT + >>> sm.mpoly.wkt # WKT MULTIPOLYGON (((12.4157980000000006 43.9579540000000009, 12.4505540000000003 43.9797209999999978, ... >>> sm.mpoly.wkb # WKB (as Python binary buffer) @@ -682,16 +682,16 @@ Google Geographic Admin ---------------- -GeoDjango extends :ref:`Django's admin application ` to -enable support for editing geometry fields. +GeoDjango extends :doc:`Django's admin application ` +to enable support for editing geometry fields. Basics ^^^^^^ -GeoDjango also supplements the Django admin by allowing users to create +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 +Let's dive in again -- create a file called ``admin.py`` inside the ``world`` application, and insert the following:: from django.contrib.gis import admin diff --git a/docs/ref/contrib/humanize.txt b/docs/ref/contrib/humanize.txt index 07a62a7fbe86..17db3c2535ad 100644 --- a/docs/ref/contrib/humanize.txt +++ b/docs/ref/contrib/humanize.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-humanize: - ======================== django.contrib.humanize ======================== diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt index bb470e3041c5..89680150ff76 100644 --- a/docs/ref/contrib/index.txt +++ b/docs/ref/contrib/index.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-index: - ==================== ``contrib`` packages ==================== @@ -46,8 +44,8 @@ admin ===== The automatic Django administrative interface. For more information, see -:ref:`Tutorial 2 ` and the -:ref:`admin documentation `. +:doc:`Tutorial 2 ` and the +:doc:`admin documentation `. Requires the auth_ and contenttypes_ contrib packages to be installed. @@ -56,16 +54,16 @@ auth Django's authentication framework. -See :ref:`topics-auth`. +See :doc:`/topics/auth`. comments ======== .. versionchanged:: 1.0 - The comments application has been rewriten. See :ref:`ref-contrib-comments-upgrade` + The comments application has been rewriten. See :doc:`/ref/contrib/comments/upgrade` for information on howto upgrade. -A simple yet flexible comments system. See :ref:`ref-contrib-comments-index`. +A simple yet flexible comments system. See :doc:`/ref/contrib/comments/index`. contenttypes ============ @@ -73,21 +71,21 @@ contenttypes A light framework for hooking into "types" of content, where each installed Django model is a separate content type. -See the :ref:`contenttypes documentation `. +See the :doc:`contenttypes documentation `. csrf ==== A middleware for preventing Cross Site Request Forgeries -See the :ref:`csrf documentation `. +See the :doc:`csrf documentation `. flatpages ========= A framework for managing simple "flat" HTML content in a database. -See the :ref:`flatpages documentation `. +See the :doc:`flatpages documentation `. Requires the sites_ contrib package to be installed as well. @@ -103,14 +101,14 @@ An abstraction of the following workflow: "Display an HTML form, force a preview, then do something with the submission." -See the :ref:`form preview documentation `. +See the :doc:`form preview documentation `. django.contrib.formtools.wizard -------------------------------- Splits forms across multiple Web pages. -See the :ref:`form wizard documentation `. +See the :doc:`form wizard documentation `. gis ==== @@ -118,14 +116,14 @@ gis A world-class geospatial framework built on top of Django, that enables storage, manipulation and display of spatial data. -See the :ref:`ref-contrib-gis` documentation for more. +See the :doc:`/ref/contrib/gis/index` documentation for more. humanize ======== A set of Django template filters useful for adding a "human touch" to data. -See the :ref:`humanize documentation `. +See the :doc:`humanize documentation `. localflavor =========== @@ -134,7 +132,7 @@ A collection of various Django snippets that are useful only for a particular country or culture. For example, ``django.contrib.localflavor.us.forms`` contains a ``USZipCodeField`` that you can use to validate U.S. zip codes. -See the :ref:`localflavor documentation `. +See the :doc:`localflavor documentation `. .. _ref-contrib-markup: @@ -183,21 +181,21 @@ messages A framework for storing and retrieving temporary cookie- or session-based messages -See the :ref:`messages documentation `. +See the :doc:`messages documentation `. redirects ========= A framework for managing redirects. -See the :ref:`redirects documentation `. +See the :doc:`redirects documentation `. sessions ======== A framework for storing data in anonymous sessions. -See the :ref:`sessions documentation `. +See the :doc:`sessions documentation `. sites ===== @@ -206,21 +204,21 @@ A light framework that lets you operate multiple Web sites off of the same database and Django installation. It gives you hooks for associating objects to one or more sites. -See the :ref:`sites documentation `. +See the :doc:`sites documentation `. sitemaps ======== A framework for generating Google sitemap XML files. -See the :ref:`sitemaps documentation `. +See the :doc:`sitemaps documentation `. syndication =========== A framework for generating syndication feeds, in RSS and Atom, quite easily. -See the :ref:`syndication documentation `. +See the :doc:`syndication documentation `. webdesign ========= @@ -228,7 +226,7 @@ webdesign Helpers and utilities targeted primarily at Web *designers* rather than Web *developers*. -See the :ref:`Web design helpers documentation `. +See the :doc:`Web design helpers documentation `. Other add-ons ============= diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index 1c58e2d5e82b..48cfa6340a9a 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-localflavor: - ========================== The "local flavor" add-ons ========================== @@ -17,7 +15,7 @@ Inside that package, country- or culture-specific code is organized into subpackages, named using `ISO 3166 country codes`_. Most of the ``localflavor`` add-ons are localized form components deriving -from the :ref:`forms ` framework -- for example, a +from the :doc:`forms ` framework -- for example, a :class:`~django.contrib.localflavor.us.forms.USStateField` that knows how to validate U.S. state abbreviations, and a :class:`~django.contrib.localflavor.fi.forms.FISocialSecurityNumber` that @@ -74,7 +72,7 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are: The ``django.contrib.localflavor`` package also includes a ``generic`` subpackage, containing useful code that is not specific to one particular country or culture. Currently, it defines date, datetime and split datetime input fields based on -those from :ref:`forms `, but with non-US default formats. +those from :doc:`forms `, but with non-US default formats. Here's an example of how to use them:: from django import forms diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt index 554e70b1f208..3081f2718d8a 100644 --- a/docs/ref/contrib/messages.txt +++ b/docs/ref/contrib/messages.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-messages: - ====================== The messages framework ====================== @@ -20,8 +18,8 @@ with a specific ``level`` that determines its priority (e.g., ``info``, Enabling messages ================= -Messages are implemented through a :ref:`middleware ` -class and corresponding :ref:`context processor `. +Messages are implemented through a :doc:`middleware ` +class and corresponding :doc:`context processor `. To enable message functionality, do the following: @@ -29,7 +27,7 @@ To enable message functionality, do the following: it contains ``'django.contrib.messages.middleware.MessageMiddleware'``. If you are using a :ref:`storage backend ` that - relies on :ref:`sessions ` (the default), + relies on :doc:`sessions ` (the default), ``'django.contrib.sessions.middleware.SessionMiddleware'`` must be enabled and appear before ``MessageMiddleware`` in your :setting:`MIDDLEWARE_CLASSES`. @@ -106,7 +104,7 @@ LegacyFallbackStorage The ``LegacyFallbackStorage`` is a temporary tool to facilitate the transition from the deprecated ``user.message_set`` API and will be removed in Django 1.4 according to Django's standard deprecation policy. For more information, see -the full :ref:`release process documentation `. +the full :doc:`release process documentation `. In addition to the functionality in the ``FallbackStorage``, it adds a custom, read-only storage class that retrieves messages from the user ``Message`` @@ -300,7 +298,7 @@ example:: messages.info(request, 'Hello world.', fail_silently=True) Internally, Django uses this functionality in the create, update, and delete -:ref:`generic views ` so that they work even if the +:doc:`generic views ` so that they work even if the message framework is disabled. .. note:: @@ -343,7 +341,7 @@ window/tab will have its own browsing context. Settings ======== -A few :ref:`Django settings ` give you control over message +A few :doc:`Django settings ` give you control over message behavior: MESSAGE_LEVEL diff --git a/docs/ref/contrib/redirects.txt b/docs/ref/contrib/redirects.txt index 6f9c57c09db7..f1a58cb2073b 100644 --- a/docs/ref/contrib/redirects.txt +++ b/docs/ref/contrib/redirects.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-redirects: - ================= The redirects app ================= @@ -47,8 +45,8 @@ Note that the order of :setting:`MIDDLEWARE_CLASSES` matters. Generally, you can put ``RedirectFallbackMiddleware`` at the end of the list, because it's a last resort. -For more on middleware, read the :ref:`middleware docs -`. +For more on middleware, read the :doc:`middleware docs +`. How to add, change and delete redirects ======================================= @@ -65,8 +63,8 @@ Via the Python API .. class:: models.Redirect - Redirects are represented by a standard :ref:`Django model `, + Redirects are represented by a standard :doc:`Django model `, which lives in `django/contrib/redirects/models.py`_. You can access redirect - objects via the :ref:`Django database API `. + objects via the :doc:`Django database API `. .. _django/contrib/redirects/models.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/redirects/models.py diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index a71f19d786b5..113d4d35314a 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-sitemaps: - ===================== The sitemap framework ===================== @@ -23,10 +21,10 @@ site. The Django sitemap framework automates the creation of this XML file by letting you express this information in Python code. -It works much like Django's :ref:`syndication framework -`. To create a sitemap, just write a +It works much like Django's :doc:`syndication framework +`. To create a sitemap, just write a :class:`~django.contrib.sitemaps.Sitemap` class and point to it in your -:ref:`URLconf `. +:doc:`URLconf `. Installation ============ @@ -52,7 +50,7 @@ Initialization ============== To activate sitemap generation on your Django site, add this line to your -:ref:`URLconf `:: +:doc:`URLconf `:: (r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}) @@ -227,7 +225,7 @@ The sitemap framework provides a couple convenience classes for common cases: .. class:: GenericSitemap The :class:`django.contrib.sitemaps.GenericSitemap` class works with any - :ref:`generic views ` you already have. + :doc:`generic views ` you already have. To use it, create an instance, passing in the same :data:`info_dict` you pass to the generic views. The only requirement is that the dictionary have a :data:`queryset` entry. It may also have a :data:`date_field` entry that specifies a @@ -240,7 +238,7 @@ The sitemap framework provides a couple convenience classes for common cases: Example ------- -Here's an example of a :ref:`URLconf ` using both:: +Here's an example of a :doc:`URLconf ` using both:: from django.conf.urls.defaults import * from django.contrib.sitemaps import FlatPageSitemap, GenericSitemap diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index b7cb8d6a5852..516233b59624 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-sites: - ===================== The "sites" framework ===================== @@ -260,7 +258,7 @@ The ``CurrentSiteManager`` If :class:`~django.contrib.sites.models.Site` plays a key role in your application, consider using the helpful :class:`~django.contrib.sites.managers.CurrentSiteManager` in your -model(s). It's a model :ref:`manager ` that +model(s). It's a model :doc:`manager ` that automatically filters its queries to include only objects associated with the current :class:`~django.contrib.sites.models.Site`. @@ -322,7 +320,7 @@ and pass a field name that doesn't exist, Django will raise a :exc:`ValueError`. Finally, note that you'll probably want to keep a normal (non-site-specific) ``Manager`` on your model, even if you use :class:`~django.contrib.sites.managers.CurrentSiteManager`. As -explained in the :ref:`manager documentation `, if +explained in the :doc:`manager documentation `, if you define a manager manually, then Django won't create the automatic ``objects = models.Manager()`` manager for you. Also note that certain parts of Django -- namely, the Django admin site and generic views -- @@ -387,7 +385,7 @@ Here's how Django uses the sites framework: .. versionadded:: 1.0 -Some :ref:`django.contrib ` applications take advantage of +Some :doc:`django.contrib ` applications take advantage of the sites framework but are architected in a way that doesn't *require* the sites framework to be installed in your database. (Some people don't want to, or just aren't *able* to install the extra database table that the sites framework diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt index 7b47ef8a4ee1..a12d646a0816 100644 --- a/docs/ref/contrib/syndication.txt +++ b/docs/ref/contrib/syndication.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-syndication: - ============================== The syndication feed framework ============================== @@ -38,8 +36,8 @@ Overview The high-level feed-generating framework is supplied by the :class:`~django.contrib.syndication.views.Feed` class. To create a feed, write a :class:`~django.contrib.syndication.views.Feed` class -and point to an instance of it in your :ref:`URLconf -`. +and point to an instance of it in your :doc:`URLconf +`. Feed classes ------------ @@ -54,7 +52,7 @@ Feed classes subclass :class:`django.contrib.syndication.views.Feed`. They can live anywhere in your codebase. Instances of :class:`~django.contrib.syndication.views.Feed` classes -are views which can be used in your :ref:`URLconf `. +are views which can be used in your :doc:`URLconf `. A simple example ---------------- @@ -80,7 +78,7 @@ latest five news items:: return item.description To connect a URL to this feed, put an instance of the Feed object in -your :ref:`URLconf `. For example:: +your :doc:`URLconf `. For example:: from django.conf.urls.defaults import * from myproject.feeds import LatestEntriesFeed @@ -102,7 +100,7 @@ Note: * :meth:`items()` is, simply, a method that returns a list of objects that should be included in the feed as ```` elements. Although this example returns ``NewsItem`` objects using Django's - :ref:`object-relational mapper `, :meth:`items()` + :doc:`object-relational mapper `, :meth:`items()` doesn't have to return model instances. Although you get a few bits of functionality "for free" by using Django models, :meth:`items()` can return any type of object you want. @@ -123,7 +121,7 @@ into those elements. both. If you want to do any special formatting for either the title or - description, :ref:`Django templates ` can be used + description, :doc:`Django templates ` can be used instead. Their paths can be specified with the ``title_template`` and ``description_template`` attributes on the :class:`~django.contrib.syndication.views.Feed` class. The templates are @@ -167,7 +165,7 @@ police beat in Chicago. It'd be silly to create a separate :class:`~django.contrib.syndication.views.Feed` class for each police beat; that would violate the :ref:`DRY principle ` and would couple data to programming logic. Instead, the syndication framework lets you access the -arguments passed from your :ref:`URLconf ` so feeds can output +arguments passed from your :doc:`URLconf ` so feeds can output items based on information in the feed's URL. On chicagocrime.org, the police-beat feeds are accessible via URLs like this: @@ -175,7 +173,7 @@ On chicagocrime.org, the police-beat feeds are accessible via URLs like this: * :file:`/beats/613/rss/` -- Returns recent crimes for beat 613. * :file:`/beats/1424/rss/` -- Returns recent crimes for beat 1424. -These can be matched with a :ref:`URLconf ` line such as:: +These can be matched with a :doc:`URLconf ` line such as:: (r'^beats/(?P\d+)/rss/$', BeatFeed()), diff --git a/docs/ref/contrib/webdesign.txt b/docs/ref/contrib/webdesign.txt index e69ad492324a..d355d03565f0 100644 --- a/docs/ref/contrib/webdesign.txt +++ b/docs/ref/contrib/webdesign.txt @@ -1,5 +1,3 @@ -.. _ref-contrib-webdesign: - ======================== django.contrib.webdesign ======================== @@ -9,13 +7,13 @@ django.contrib.webdesign rather than Web *developers*. The ``django.contrib.webdesign`` package, part of the -:ref:`"django.contrib" add-ons `, provides various Django +:doc:`"django.contrib" add-ons `, provides various Django helpers that are particularly useful to Web *designers* (as opposed to developers). At present, the package contains only a single template tag. If you have ideas for Web-designer-friendly functionality in Django, please -:ref:`suggest them `. +:doc:`suggest them `. Template tags ============= diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 3a7c3eaaca26..b71cc359be9a 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -1,5 +1,3 @@ -.. _ref-databases: - ========= Databases ========= @@ -34,7 +32,7 @@ aggregate with a database backend that falls within the affected release range. Transaction handling --------------------- -:ref:`By default `, Django starts a transaction when a +:doc:`By default `, Django starts a transaction when a database connection is first used and commits the result at the end of the request/response handling. The PostgreSQL backends normally operate the same as any other Django backend in this respect. @@ -247,7 +245,7 @@ table (usually called ``django_session``) and the Connecting to the database -------------------------- -Refer to the :ref:`settings documentation `. +Refer to the :doc:`settings documentation `. Connection settings are used in this order: diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 0918f5c1f4b2..0c77c32c7a72 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1,5 +1,3 @@ -.. _ref-django-admin: - ============================= django-admin.py and manage.py ============================= @@ -104,7 +102,7 @@ compilemessages Before 1.0 this was the "bin/compile-messages.py" command. Compiles .po files created with ``makemessages`` to .mo files for use with -the builtin gettext support. See :ref:`topics-i18n`. +the builtin gettext support. See :doc:`/topics/i18n/index`. Use the :djadminopt:`--locale`` option to specify the locale to process. If not provided, all locales are processed. @@ -119,7 +117,7 @@ createcachetable .. django-admin:: createcachetable Creates a cache table named ``tablename`` for use with the database cache -backend. See :ref:`topics-cache` for more information. +backend. See :doc:`/topics/cache` for more information. .. versionadded:: 1.2 @@ -151,8 +149,8 @@ using the ``--username`` and ``--email`` arguments on the command line. If either of those is not supplied, ``createsuperuser`` will prompt for it when running interactively. -This command is only available if Django's :ref:`authentication system -` (``django.contrib.auth``) is installed. +This command is only available if Django's :doc:`authentication system +` (``django.contrib.auth``) is installed. dbshell ------- @@ -524,8 +522,8 @@ runfcgi [options] .. django-admin:: runfcgi Starts a set of FastCGI processes suitable for use with any Web server that -supports the FastCGI protocol. See the :ref:`FastCGI deployment documentation -` for details. Requires the Python FastCGI module from +supports the FastCGI protocol. See the :doc:`FastCGI deployment documentation +` for details. Requires the Python FastCGI module from `flup`_. .. _flup: http://www.saddi.com/software/flup/ @@ -611,7 +609,7 @@ Serving static files with the development server By default, the development server doesn't serve any static files for your site (such as CSS files, images, things under ``MEDIA_URL`` and so forth). If -you want to configure Django to serve static media, read :ref:`howto-static-files`. +you want to configure Django to serve static media, read :doc:`/howto/static-files`. shell ----- @@ -819,7 +817,7 @@ test .. django-admin:: test -Runs tests for all installed models. See :ref:`topics-testing` for more +Runs tests for all installed models. See :doc:`/topics/testing` for more information. .. versionadded:: 1.2 @@ -844,7 +842,7 @@ For example, this command:: ...would perform the following steps: - 1. Create a test database, as described in :ref:`topics-testing`. + 1. Create a test database, as described in :doc:`/topics/testing`. 2. Populate the test database with fixture data from the given fixtures. (For more on fixtures, see the documentation for ``loaddata`` above.) 3. Runs the Django development server (as in ``runserver``), pointed at @@ -852,7 +850,7 @@ For example, this command:: This is useful in a number of ways: - * When you're writing :ref:`unit tests ` of how your views + * When you're writing :doc:`unit tests ` of how your views act with certain fixture data, you can use ``testserver`` to interact with the views in a Web browser, manually. @@ -1107,4 +1105,4 @@ distribution. It enables tab-completion of ``django-admin.py`` and with ``sql``. -See :ref:`howto-custom-management-commands` for how to add customized actions. +See :doc:`/howto/custom-management-commands` for how to add customized actions. diff --git a/docs/ref/exceptions.txt b/docs/ref/exceptions.txt index 1fc9b177d492..4a4384376bf2 100644 --- a/docs/ref/exceptions.txt +++ b/docs/ref/exceptions.txt @@ -1,5 +1,3 @@ -.. _ref-exceptions: - ================= Django Exceptions ================= diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt index 1ea5865ea7f6..f4ae59f241e0 100644 --- a/docs/ref/files/file.txt +++ b/docs/ref/files/file.txt @@ -1,5 +1,3 @@ -.. _ref-files-file: - The ``File`` object =================== @@ -20,14 +18,14 @@ Django's ``File`` has the following attributes and methods: The absolute path to the file's location on a local filesystem. - :ref:`Custom file storage systems ` may not store + :doc:`Custom file storage systems ` may not store files locally; files stored on these systems will have a ``path`` of ``None``. .. attribute:: File.url The URL where the file can be retrieved. This is often useful in - :ref:`templates `; for example, a bit of a template for + :doc:`templates `; for example, a bit of a template for displaying a ``Car`` (see above) might look like: .. code-block:: html+django diff --git a/docs/ref/files/index.txt b/docs/ref/files/index.txt index 1d59d5fa233d..171fcc639130 100644 --- a/docs/ref/files/index.txt +++ b/docs/ref/files/index.txt @@ -1,5 +1,3 @@ -.. _ref-files-index: - ============= File handling ============= diff --git a/docs/ref/files/storage.txt b/docs/ref/files/storage.txt index c8aafa8626f6..2b055bb60be1 100644 --- a/docs/ref/files/storage.txt +++ b/docs/ref/files/storage.txt @@ -1,5 +1,3 @@ -.. _ref-files-storage: - File storage API ================ diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 0d174ea4af0b..613d7544a9e5 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -1,5 +1,3 @@ -.. _ref-forms-api: - ============= The Forms API ============= @@ -11,7 +9,7 @@ The Forms API .. admonition:: About this document This document covers the gritty details of Django's forms API. You should - read the :ref:`introduction to working with forms ` + read the :doc:`introduction to working with forms ` first. .. _ref-forms-api-bound-unbound: @@ -262,7 +260,7 @@ for each field in the "Built-in ``Field`` classes" section below. You can write code to perform validation for particular form fields (based on their name) or for the form as a whole (considering combinations of various -fields). More information about this is in :ref:`ref-forms-validation`. +fields). More information about this is in :doc:`/ref/forms/validation`. Outputting forms as HTML ------------------------ diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 396e51046e84..0dd9095a779a 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -1,5 +1,3 @@ -.. _ref-forms-fields: - =========== Form fields =========== @@ -192,7 +190,7 @@ The callable will be evaluated only when the unbound form is displayed, not when .. attribute:: Field.widget The ``widget`` argument lets you specify a ``Widget`` class to use when -rendering this ``Field``. See :ref:`ref-forms-widgets` for more information. +rendering this ``Field``. See :doc:`/ref/forms/widgets` for more information. ``help_text`` ~~~~~~~~~~~~~ @@ -267,7 +265,7 @@ error message keys it uses. The ``validators`` argument lets you provide a list of validation functions for this field. -See the :ref:`validators documentation ` for more information. +See the :doc:`validators documentation ` for more information. ``localize`` ~~~~~~~~~~~~ @@ -516,8 +514,8 @@ given length. * Validates that non-empty file data has been bound to the form. * Error message keys: ``required``, ``invalid``, ``missing``, ``empty`` -To learn more about the ``UploadedFile`` object, see the :ref:`file uploads -documentation `. +To learn more about the ``UploadedFile`` object, see the :doc:`file uploads +documentation `. When you use a ``FileField`` in a form, you must also remember to :ref:`bind the file data to the form `. diff --git a/docs/ref/forms/index.txt b/docs/ref/forms/index.txt index e310863c7a4c..610416a363fe 100644 --- a/docs/ref/forms/index.txt +++ b/docs/ref/forms/index.txt @@ -1,10 +1,8 @@ -.. _ref-forms-index: - ===== Forms ===== -Detailed form API reference. For introductory material, see :ref:`topics-forms-index`. +Detailed form API reference. For introductory material, see :doc:`/topics/forms/index`. .. toctree:: :maxdepth: 1 diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 911496c9ae7c..6cc3280e0606 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -1,5 +1,3 @@ -.. _ref-forms-validation: - Form and field validation ========================= diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 1fc2bfa85dd3..59847509437a 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -1,5 +1,3 @@ -.. _ref-forms-widgets: - ======= Widgets ======= diff --git a/docs/ref/generic-views.txt b/docs/ref/generic-views.txt index 574db88a6038..65f0d2eb3002 100644 --- a/docs/ref/generic-views.txt +++ b/docs/ref/generic-views.txt @@ -1,5 +1,3 @@ -.. _ref-generic-views: - ============= Generic views ============= @@ -9,8 +7,8 @@ again and again. In Django, the most common of these patterns have been abstracted into "generic views" that let you quickly provide common views of an object without actually needing to write any Python code. -A general introduction to generic views can be found in the :ref:`topic guide -`. +A general introduction to generic views can be found in the :doc:`topic guide +`. This reference contains details of Django's built-in generic views, along with a list of all keyword arguments that a generic view expects. Remember that @@ -18,7 +16,7 @@ arguments may either come from the URL pattern or from the ``extra_context`` additional-information dictionary. Most generic views require the ``queryset`` key, which is a ``QuerySet`` -instance; see :ref:`topics-db-queries` for more information about ``QuerySet`` +instance; see :doc:`/topics/db/queries` for more information about ``QuerySet`` objects. "Simple" generic views @@ -766,8 +764,8 @@ specify the page number in the URL in one of two ways: These values and lists are 1-based, not 0-based, so the first page would be represented as page ``1``. -For more on pagination, read the :ref:`pagination documentation -`. +For more on pagination, read the :doc:`pagination documentation +`. .. versionadded:: 1.0 @@ -858,8 +856,8 @@ for creating, editing and deleting objects. .. versionchanged:: 1.0 ``django.views.generic.create_update.create_object`` and -``django.views.generic.create_update.update_object`` now use the new :ref:`forms -library ` to build and display the form. +``django.views.generic.create_update.update_object`` now use the new :doc:`forms +library ` to build and display the form. ``django.views.generic.create_update.create_object`` ---------------------------------------------------- @@ -875,7 +873,7 @@ validation errors (if there are any) and saving the object. If you provide ``form_class``, it should be a ``django.forms.ModelForm`` subclass. Use this argument when you need to customize the model's form. - See the :ref:`ModelForm docs ` for more + See the :doc:`ModelForm docs ` for more information. Otherwise, ``model`` should be a Django model class and the form used @@ -892,7 +890,7 @@ validation errors (if there are any) and saving the object. * ``login_required``: A boolean that designates whether a user must be logged in, in order to see the page and save changes. This hooks into the - Django :ref:`authentication system `. By default, this is + Django :doc:`authentication system `. By default, this is ``False``. If this is ``True``, and a non-logged-in user attempts to visit this page @@ -932,7 +930,7 @@ In addition to ``extra_context``, the template's context will be:

        {{ form.address.label_tag }} {{ form.address }}

        - See the :ref:`forms documentation ` for more + See the :doc:`forms documentation ` for more information about using ``Form`` objects in templates. ``django.views.generic.create_update.update_object`` @@ -951,7 +949,7 @@ model class. If you provide ``form_class``, it should be a ``django.forms.ModelForm`` subclass. Use this argument when you need to customize the model's form. - See the :ref:`ModelForm docs ` for more + See the :doc:`ModelForm docs ` for more information. Otherwise, ``model`` should be a Django model class and the form used @@ -977,7 +975,7 @@ model class. * ``login_required``: A boolean that designates whether a user must be logged in, in order to see the page and save changes. This hooks into the - Django :ref:`authentication system `. By default, this is + Django :doc:`authentication system `. By default, this is ``False``. If this is ``True``, and a non-logged-in user attempts to visit this page @@ -1020,7 +1018,7 @@ In addition to ``extra_context``, the template's context will be:

        {{ form.address.label_tag }} {{ form.address }}

        - See the :ref:`forms documentation ` for more + See the :doc:`forms documentation ` for more information about using ``Form`` objects in templates. * ``object``: The original object being edited. This variable's name @@ -1059,7 +1057,7 @@ contain a form that POSTs to the same URL. * ``login_required``: A boolean that designates whether a user must be logged in, in order to see the page and save changes. This hooks into the - Django :ref:`authentication system `. By default, this is + Django :doc:`authentication system `. By default, this is ``False``. If this is ``True``, and a non-logged-in user attempts to visit this page diff --git a/docs/ref/index.txt b/docs/ref/index.txt index a2088ed6ce83..09194178afc5 100644 --- a/docs/ref/index.txt +++ b/docs/ref/index.txt @@ -1,5 +1,3 @@ -.. _ref-index: - ============= API Reference ============= diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 2afd79038f87..290ea2736da0 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -1,5 +1,3 @@ -.. _ref-middleware: - ========== Middleware ========== @@ -9,7 +7,7 @@ Middleware This document explains all middleware components that come with Django. For information on how how to use them and how to write your own middleware, see -the :ref:`middleware usage guide `. +the :doc:`middleware usage guide `. Available middleware ==================== @@ -26,7 +24,7 @@ Cache middleware Enable the site-wide cache. If these are enabled, each Django-powered page will be cached for as long as the :setting:`CACHE_MIDDLEWARE_SECONDS` setting -defines. See the :ref:`cache documentation `. +defines. See the :doc:`cache documentation `. "Common" middleware ------------------- @@ -136,8 +134,8 @@ Locale middleware .. class:: django.middleware.locale.LocaleMiddleware Enables language selection based on data from the request. It customizes -content for each user. See the :ref:`internationalization documentation -`. +content for each user. See the :doc:`internationalization documentation +`. Message middleware ------------------ @@ -151,7 +149,7 @@ Message middleware ``MessageMiddleware`` was added. Enables cookie- and session-based message support. See the -:ref:`messages documentation `. +:doc:`messages documentation `. Session middleware ------------------ @@ -161,8 +159,8 @@ Session middleware .. class:: django.contrib.sessions.middleware.SessionMiddleware -Enables session support. See the :ref:`session documentation -`. +Enables session support. See the :doc:`session documentation +`. Authentication middleware ------------------------- @@ -173,8 +171,8 @@ Authentication middleware .. class:: django.contrib.auth.middleware.AuthenticationMiddleware Adds the ``user`` attribute, representing the currently-logged-in user, to -every incoming ``HttpRequest`` object. See :ref:`Authentication in Web requests -`. +every incoming ``HttpRequest`` object. See :doc:`Authentication in Web requests +`. CSRF protection middleware -------------------------- @@ -189,7 +187,7 @@ CSRF protection middleware Adds protection against Cross Site Request Forgeries by adding hidden form fields to POST forms and checking requests for the correct value. See the -:ref:`Cross Site Request Forgery protection documentation `. +:doc:`Cross Site Request Forgery protection documentation `. Transaction middleware ---------------------- @@ -208,4 +206,4 @@ running outside of it run with commit-on-save - the default Django behavior. Middleware modules running inside it (coming later in the stack) will be under the same transaction control as the view functions. -See the :ref:`transaction management documentation `. +See the :doc:`transaction management documentation `. diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 3a0066987f41..da0b24622a38 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1,5 +1,3 @@ -.. _ref-models-fields: - ===================== Model field reference ===================== @@ -14,8 +12,8 @@ This document contains all the gory details about all the `field options`_ and .. seealso:: - If the built-in fields don't do the trick, you can easily :ref:`write your - own custom model fields `. + If the built-in fields don't do the trick, you can easily :doc:`write your + own custom model fields `. .. note:: @@ -302,8 +300,8 @@ underscores to spaces. See :ref:`Verbose field names `. .. attribute:: Field.validators -A list of validators to run for this field.See the :ref:`validators -documentation ` for more information. +A list of validators to run for this field.See the :doc:`validators +documentation ` for more information. Field types @@ -370,8 +368,8 @@ The admin represents this as an ```` (a single-line input). If you are writing an application that must be portable to multiple database backends, you should be aware that there are restrictions on - ``max_length`` for some backends. Refer to the :ref:`database backend - notes ` for details. + ``max_length`` for some backends. Refer to the :doc:`database backend + notes ` for details. .. admonition:: MySQL users @@ -518,7 +516,7 @@ Also has one optional argument: .. versionadded:: 1.0 Optional. A storage object, which handles the storage and retrieval of your - files. See :ref:`topics-files` for details on how to provide this object. + files. See :doc:`/topics/files` for details on how to provide this object. The admin represents this field as an ```` (a file-upload widget). @@ -553,7 +551,7 @@ day. If you upload a file on Jan. 15, 2007, it will be saved in the directory If you want to retrieve the upload file's on-disk filename, or a URL that refers to that file, or the file's size, you can use the :attr:`~django.core.files.File.name`, :attr:`~django.core.files.File.url` -and :attr:`~django.core.files.File.size` attributes; see :ref:`topics-files`. +and :attr:`~django.core.files.File.size` attributes; see :doc:`/topics/files`. Note that whenever you deal with uploaded files, you should pay close attention to where you're uploading them and what type of files they are, to avoid @@ -903,7 +901,7 @@ define the details of how the relation works. .. attribute:: ForeignKey.limit_choices_to - A dictionary of lookup arguments and values (see :ref:`topics-db-queries`) + A dictionary of lookup arguments and values (see :doc:`/topics/db/queries`) that limit the available admin choices for this object. Use this with functions from the Python ``datetime`` module to limit choices of objects by date. For example:: diff --git a/docs/ref/models/index.txt b/docs/ref/models/index.txt index 64b47b26cc8d..b5896c35ed39 100644 --- a/docs/ref/models/index.txt +++ b/docs/ref/models/index.txt @@ -1,10 +1,8 @@ -.. _ref-models-index: - ====== Models ====== -Model API reference. For introductory material, see :ref:`topics-db-models`. +Model API reference. For introductory material, see :doc:`/topics/db/models`. .. toctree:: :maxdepth: 1 diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 1e72e0c662e3..d0076593017a 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -1,5 +1,3 @@ -.. _ref-models-instances: - ======================== Model instance reference ======================== @@ -7,13 +5,13 @@ Model instance reference .. currentmodule:: django.db.models This document describes the details of the ``Model`` API. It builds on the -material presented in the :ref:`model ` and :ref:`database -query ` guides, so you'll probably want to read and +material presented in the :doc:`model ` and :doc:`database +query ` guides, so you'll probably want to read and understand those documents before reading this one. Throughout this reference we'll use the :ref:`example weblog models -` presented in the :ref:`database query guide -`. +` presented in the :doc:`database query guide +`. Creating objects ================ @@ -45,8 +43,8 @@ All three steps are performed when you call by a model's When you use a ``ModelForm``, the call to ``is_valid()`` will perform these validation steps for all the fields that are included on the -form. (See the :ref:`ModelForm documentation -` for more information.) You should only need +form. (See the :doc:`ModelForm documentation +` for more information.) You should only need to call a model's ``full_clean()`` method if you plan to handle validation errors yourself, or if you have excluded fields from the ModelForm that require validation. @@ -215,7 +213,7 @@ What happens when you save? When you save an object, Django performs the following steps: - 1. **Emit a pre-save signal.** The :ref:`signal ` + 1. **Emit a pre-save signal.** The :doc:`signal ` :attr:`django.db.models.signals.pre_save` is sent, allowing any functions listening for that signal to take some customized action. @@ -426,8 +424,8 @@ Django uses this in its admin interface. If an object defines link that will jump you directly to the object's public view, according to ``get_absolute_url()``. -Also, a couple of other bits of Django, such as the :ref:`syndication feed -framework `, use ``get_absolute_url()`` as a +Also, a couple of other bits of Django, such as the :doc:`syndication feed +framework `, use ``get_absolute_url()`` as a convenience to reward people who've defined the method. It's good practice to use ``get_absolute_url()`` in templates, instead of @@ -523,8 +521,8 @@ of the view name:: def get_absolute_url(self): return ('people_view', [str(self.id)]) -More details on named URL patterns are in the :ref:`URL dispatch documentation -`. +More details on named URL patterns are in the :doc:`URL dispatch documentation +`. Extra instance methods ====================== diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index f3e7363e3640..3dfcdfffbced 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -1,5 +1,3 @@ -.. _ref-models-options: - ====================== Model ``Meta`` options ====================== diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 91d14150435d..02d105d3cb59 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1,5 +1,3 @@ -.. _ref-models-querysets: - ====================== QuerySet API reference ====================== @@ -7,13 +5,13 @@ QuerySet API reference .. currentmodule:: django.db.models.QuerySet This document describes the details of the ``QuerySet`` API. It builds on the -material presented in the :ref:`model ` and :ref:`database -query ` guides, so you'll probably want to read and +material presented in the :doc:`model ` and :doc:`database +query ` guides, so you'll probably want to read and understand those documents before reading this one. Throughout this reference we'll use the :ref:`example weblog models -` presented in the :ref:`database query guide -`. +` presented in the :doc:`database query guide +`. .. _when-querysets-are-evaluated: @@ -223,8 +221,8 @@ control the name of the annotation:: >>> q[0].number_of_entries 42 -For an in-depth discussion of aggregation, see :ref:`the topic guide on -Aggregation `. +For an in-depth discussion of aggregation, see :doc:`the topic guide on +Aggregation `. ``order_by(*fields)`` ~~~~~~~~~~~~~~~~~~~~~ @@ -1205,8 +1203,8 @@ control the name of the aggregation value that is returned:: >>> q = Blog.objects.aggregate(number_of_entries=Count('entry')) {'number_of_entries': 16} -For an in-depth discussion of aggregation, see :ref:`the topic guide on -Aggregation `. +For an in-depth discussion of aggregation, see :doc:`the topic guide on +Aggregation `. ``exists()`` ~~~~~~~~~~~~ @@ -1266,7 +1264,7 @@ SQL equivalents:: a Django setting. It's possible to configure your MySQL tables to use case-sensitive comparisons, but some trade-offs are involved. For more information about this, see the :ref:`collation section ` - in the :ref:`databases ` documentation. + in the :doc:`databases ` documentation. .. fieldlookup:: iexact @@ -1725,7 +1723,7 @@ Aggregation Functions Django provides the following aggregation functions in the ``django.db.models`` module. For details on how to use these aggregate functions, see -:ref:`the topic guide on aggregation `. +:doc:`the topic guide on aggregation `. ``Avg`` ~~~~~~~ diff --git a/docs/ref/models/relations.txt b/docs/ref/models/relations.txt index f58cfe7301c2..0481644d7aea 100644 --- a/docs/ref/models/relations.txt +++ b/docs/ref/models/relations.txt @@ -1,5 +1,3 @@ -.. _ref-models-relations: - ========================= Related objects reference ========================= diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index d111e4c127c2..bf984f4fa8b3 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -1,5 +1,3 @@ -.. _ref-request-response: - ============================ Request and response objects ============================ @@ -106,7 +104,7 @@ All attributes except ``session`` should be considered read-only. * ``chunks(chunk_size=None)`` -- A generator that yields sequential chunks of data. - See :ref:`topics-files` for more information. + See :doc:`/topics/files` for more information. Note that ``FILES`` will only contain data if the request method was POST and the ``
        `` that posted to the request had @@ -165,14 +163,14 @@ All attributes except ``session`` should be considered read-only. ``user`` is only available if your Django installation has the ``AuthenticationMiddleware`` activated. For more, see - :ref:`topics-auth`. + :doc:`/topics/auth`. .. attribute:: HttpRequest.session A readable-and-writable, dictionary-like object that represents the current session. This is only available if your Django installation has session - support activated. See the :ref:`session documentation - ` for full details. + support activated. See the :doc:`session documentation + ` for full details. .. attribute:: HttpRequest.raw_post_data diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index f3c5656a9ab3..834f6112a990 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1,5 +1,3 @@ -.. _ref-settings: - ======== Settings ======== @@ -74,7 +72,7 @@ of (Full name, e-mail address). Example:: (('John', 'john@example.com'), ('Mary', 'mary@example.com')) Note that Django will e-mail *all* of these people whenever an error happens. -See :ref:`howto-error-reporting` for more information. +See :doc:`/howto/error-reporting` for more information. .. setting:: ALLOWED_INCLUDE_ROOTS @@ -99,7 +97,7 @@ APPEND_SLASH Default: ``True`` Whether to append trailing slashes to URLs. This is only used if -``CommonMiddleware`` is installed (see :ref:`topics-http-middleware`). See also +``CommonMiddleware`` is installed (see :doc:`/topics/http/middleware`). See also ``PREPEND_WWW``. .. setting:: AUTHENTICATION_BACKENDS @@ -110,8 +108,8 @@ AUTHENTICATION_BACKENDS Default: ``('django.contrib.auth.backends.ModelBackend',)`` A tuple of authentication backend classes (as strings) to use when attempting to -authenticate a user. See the :ref:`authentication backends documentation -` for details. +authenticate a user. See the :doc:`authentication backends documentation +` for details. .. setting:: AUTH_PROFILE_MODULE @@ -130,7 +128,7 @@ CACHE_BACKEND Default: ``'locmem://'`` -The cache backend to use. See :ref:`topics-cache`. +The cache backend to use. See :doc:`/topics/cache`. .. setting:: CACHE_MIDDLEWARE_KEY_PREFIX @@ -140,7 +138,7 @@ CACHE_MIDDLEWARE_KEY_PREFIX Default: ``''`` (Empty string) The cache key prefix that the cache middleware should use. See -:ref:`topics-cache`. +:doc:`/topics/cache`. .. setting:: CACHE_MIDDLEWARE_SECONDS @@ -177,7 +175,7 @@ CSRF_COOKIE_NAME Default: ``'csrftoken'`` The name of the cookie to use for the CSRF authentication token. This can be whatever you -want. See :ref:`ref-contrib-csrf`. +want. See :doc:`/ref/contrib/csrf`. .. setting:: CSRF_FAILURE_VIEW @@ -195,7 +193,7 @@ is rejected by the CSRF protection. The function should have this signature:: where ``reason`` is a short message (intended for developers or logging, not for end users) indicating the reason the request was rejected. See -:ref:`ref-contrib-csrf`. +:doc:`/ref/contrib/csrf`. .. setting:: DATABASES @@ -385,7 +383,7 @@ If the default value (``None``) is used with the SQLite database engine, the tests will use a memory resident database. For all other database engines the test database will use the name ``'test_' + DATABASE_NAME``. -See :ref:`topics-testing`. +See :doc:`/topics/testing`. .. setting:: DATABASE_ROUTERS @@ -563,7 +561,7 @@ DEFAULT_FILE_STORAGE Default: ``'django.core.files.storage.FileSystemStorage'`` Default file storage class to be used for any file-related operations that don't -specify a particular storage system. See :ref:`topics-files`. +specify a particular storage system. See :doc:`/topics/files`. DEFAULT_FROM_EMAIL ------------------ @@ -607,7 +605,7 @@ Default: ``()`` (Empty tuple) List of compiled regular expression objects representing User-Agent strings that are not allowed to visit any page, systemwide. Use this for bad robots/crawlers. This is only used if ``CommonMiddleware`` is installed (see -:ref:`topics-http-middleware`). +:doc:`/topics/http/middleware`). .. setting:: EMAIL_BACKEND @@ -619,7 +617,7 @@ EMAIL_BACKEND Default: ``'django.core.mail.backends.smtp.EmailBackend'`` The backend to use for sending emails. For the list of available backends see -:ref:`topics-email`. +:doc:`/topics/email`. .. setting:: EMAIL_FILE_PATH @@ -724,7 +722,7 @@ Default:: ("django.core.files.uploadhandler.MemoryFileUploadHandler", "django.core.files.uploadhandler.TemporaryFileUploadHandler",) -A tuple of handlers to use for uploading. See :ref:`topics-files` for details. +A tuple of handlers to use for uploading. See :doc:`/topics/files` for details. .. setting:: FILE_UPLOAD_MAX_MEMORY_SIZE @@ -736,7 +734,7 @@ FILE_UPLOAD_MAX_MEMORY_SIZE Default: ``2621440`` (i.e. 2.5 MB). The maximum size (in bytes) that an upload will be before it gets streamed to -the file system. See :ref:`topics-files` for details. +the file system. See :doc:`/topics/files` for details. .. setting:: FILE_UPLOAD_PERMISSIONS @@ -779,7 +777,7 @@ The directory to store data temporarily while uploading files. If ``None``, Django will use the standard temporary directory for the operating system. For example, this will default to '/tmp' on \*nix-style operating systems. -See :ref:`topics-files` for details. +See :doc:`/topics/files` for details. .. setting:: FIRST_DAY_OF_WEEK @@ -807,7 +805,7 @@ Default: ``()`` (Empty tuple) List of locations of the fixture data files, in search order. Note that these paths should use Unix-style forward slashes, even on Windows. See -:ref:`topics-testing`. +:doc:`/topics/testing`. FORCE_SCRIPT_NAME ------------------ @@ -867,7 +865,7 @@ Default: ``('/cgi-bin/', '/_vti_bin', '/_vti_inf')`` A tuple of strings that specify beginnings of URLs that should be ignored by the 404 e-mailer. See ``SEND_BROKEN_LINK_EMAILS``, ``IGNORABLE_404_ENDS`` and -the :ref:`howto-error-reporting`. +the :doc:`/howto/error-reporting`. .. setting:: INSTALLED_APPS @@ -900,7 +898,7 @@ A tuple of IP addresses, as strings, that: * See debug comments, when ``DEBUG`` is ``True`` * Receive X headers if the ``XViewMiddleware`` is installed (see - :ref:`topics-http-middleware`) + :doc:`/topics/http/middleware`) .. setting:: LANGUAGE_CODE @@ -911,7 +909,7 @@ Default: ``'en-us'`` A string representing the language code for this installation. This should be in standard :term:`language format`. For example, U.S. English is -``"en-us"``. See :ref:`topics-i18n`. +``"en-us"``. See :doc:`/topics/i18n/index`. .. setting:: LANGUAGE_COOKIE_NAME @@ -924,7 +922,7 @@ Default: ``'django_language'`` The name of the cookie to use for the language cookie. This can be whatever you want (but should be different from ``SESSION_COOKIE_NAME``). See -:ref:`topics-i18n`. +:doc:`/topics/i18n/index`. .. setting:: LANGUAGES @@ -942,7 +940,7 @@ The list is a tuple of two-tuples in the format ``(language code, language name)``, the ``language code`` part should be a :term:`language name` -- for example, ``('ja', 'Japanese')``. This specifies which languages are available for language selection. See -:ref:`topics-i18n`. +:doc:`/topics/i18n/index`. Generally, the default value should suffice. Only set this setting if you want to restrict language selection to a subset of the Django-provided languages. @@ -1062,7 +1060,7 @@ MESSAGE_LEVEL Default: `messages.INFO` Sets the minimum message level that will be recorded by the messages -framework. See the :ref:`messages documentation ` for +framework. See the :doc:`messages documentation ` for more details. MESSAGE_STORAGE @@ -1073,7 +1071,7 @@ MESSAGE_STORAGE Default: ``'django.contrib.messages.storage.user_messages.LegacyFallbackStorage'`` Controls where Django stores message data. See the -:ref:`messages documentation ` for more details. +:doc:`messages documentation ` for more details. MESSAGE_TAGS ------------ @@ -1089,7 +1087,7 @@ Default:: messages.ERROR: 'error',} Sets the mapping of message levels to message tags. See the -:ref:`messages documentation ` for more details. +:doc:`messages documentation ` for more details. MIDDLEWARE_CLASSES ------------------ @@ -1102,12 +1100,12 @@ Default:: 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',) -A tuple of middleware classes to use. See :ref:`topics-http-middleware`. +A tuple of middleware classes to use. See :doc:`/topics/http/middleware`. .. versionchanged:: 1.2 ``'django.contrib.messages.middleware.MessageMiddleware'`` was added to the - default. For more information, see the :ref:`messages documentation - `. + default. For more information, see the :doc:`messages documentation + `. .. setting:: MONTH_DAY_FORMAT @@ -1153,7 +1151,7 @@ PREPEND_WWW Default: ``False`` Whether to prepend the "www." subdomain to URLs that don't have it. This is only -used if ``CommonMiddleware`` is installed (see :ref:`topics-http-middleware`). +used if ``CommonMiddleware`` is installed (see :doc:`/topics/http/middleware`). See also ``APPEND_SLASH``. .. setting:: PROFANITIES_LIST @@ -1201,8 +1199,8 @@ Default: ``False`` Whether to send an e-mail to the ``MANAGERS`` each time somebody visits a Django-powered page that is 404ed with a non-empty referer (i.e., a broken link). This is only used if ``CommonMiddleware`` is installed (see -:ref:`topics-http-middleware`. See also ``IGNORABLE_404_STARTS``, -``IGNORABLE_404_ENDS`` and :ref:`howto-error-reporting`. +:doc:`/topics/http/middleware`. See also ``IGNORABLE_404_STARTS``, +``IGNORABLE_404_ENDS`` and :doc:`/howto/error-reporting`. .. setting:: SERIALIZATION_MODULES @@ -1234,7 +1232,7 @@ SESSION_COOKIE_AGE Default: ``1209600`` (2 weeks, in seconds) -The age of session cookies, in seconds. See :ref:`topics-http-sessions`. +The age of session cookies, in seconds. See :doc:`/topics/http/sessions`. .. setting:: SESSION_COOKIE_DOMAIN @@ -1245,7 +1243,7 @@ Default: ``None`` The domain to use for session cookies. Set this to a string such as ``".lawrence.com"`` for cross-domain cookies, or use ``None`` for a standard -domain cookie. See the :ref:`topics-http-sessions`. +domain cookie. See the :doc:`/topics/http/sessions`. .. setting:: SESSION_COOKIE_NAME @@ -1255,7 +1253,7 @@ SESSION_COOKIE_NAME Default: ``'sessionid'`` The name of the cookie to use for sessions. This can be whatever you want (but -should be different from ``LANGUAGE_COOKIE_NAME``). See the :ref:`topics-http-sessions`. +should be different from ``LANGUAGE_COOKIE_NAME``). See the :doc:`/topics/http/sessions`. .. setting:: SESSION_COOKIE_PATH @@ -1283,7 +1281,7 @@ Default: ``False`` Whether to use a secure cookie for the session cookie. If this is set to ``True``, the cookie will be marked as "secure," which means browsers may ensure that the cookie is only sent under an HTTPS connection. -See the :ref:`topics-http-sessions`. +See the :doc:`/topics/http/sessions`. .. setting:: SESSION_ENGINE @@ -1304,7 +1302,7 @@ Controls where Django stores session data. Valid values are: * ``'django.contrib.sessions.backends.cache'`` * ``'django.contrib.sessions.backends.cached_db'`` -See :ref:`topics-http-sessions`. +See :doc:`/topics/http/sessions`. .. setting:: SESSION_EXPIRE_AT_BROWSER_CLOSE @@ -1314,7 +1312,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE Default: ``False`` Whether to expire the session when the user closes his or her browser. -See the :ref:`topics-http-sessions`. +See the :doc:`/topics/http/sessions`. .. setting:: SESSION_FILE_PATH @@ -1326,7 +1324,7 @@ SESSION_FILE_PATH Default: ``None`` If you're using file-based session storage, this sets the directory in -which Django will store session data. See :ref:`topics-http-sessions`. When +which Django will store session data. See :doc:`/topics/http/sessions`. When the default value (``None``) is used, Django will use the standard temporary directory for the system. @@ -1338,7 +1336,7 @@ SESSION_SAVE_EVERY_REQUEST Default: ``False`` Whether to save the session data on every request. See -:ref:`topics-http-sessions`. +:doc:`/topics/http/sessions`. .. setting:: SHORT_DATE_FORMAT @@ -1383,7 +1381,7 @@ The ID, as an integer, of the current site in the ``django_site`` database table. This is used so that application data can hook into specific site(s) and a single database can manage content for multiple sites. -See :ref:`ref-contrib-sites`. +See :doc:`/ref/contrib/sites`. .. _site framework docs: ../sites/ @@ -1406,8 +1404,8 @@ of items to be merged into the context. .. versionchanged:: 1.2 ``"django.contrib.messages.context_processors.messages"`` was added to the - default. For more information, see the :ref:`messages documentation - `. + default. For more information, see the :doc:`messages documentation + `. .. versionchanged:: 1.2 The auth context processor was moved in this release from its old location @@ -1441,7 +1439,7 @@ Default: ``()`` (Empty tuple) List of locations of the template source files, in search order. Note that these paths should use Unix-style forward slashes, even on Windows. -See :ref:`topics-templates`.. +See :doc:`/topics/templates`. .. setting:: TEMPLATE_LOADERS @@ -1457,7 +1455,7 @@ A tuple of template loader classes, specified as strings. Each ``Loader`` class knows how to import templates from a particular source. Optionally, a tuple can be used instead of a string. The first item in the tuple should be the ``Loader``'s module, subsequent items are passed to the ``Loader`` during initialization. See -:ref:`ref-templates-api`. +:doc:`/ref/templates/api`. .. setting:: TEMPLATE_STRING_IF_INVALID @@ -1480,7 +1478,7 @@ Default: ``'django.test.simple.DjangoTestSuiteRunner'`` Prior to 1.2, test runners were a function, not a class. The name of the class to use for starting the test suite. See -:ref:`topics-testing`. +:doc:`/topics/testing`. .. _Testing Django Applications: ../testing/ @@ -1599,7 +1597,7 @@ Default: ``False`` A boolean that specifies whether to output the "Etag" header. This saves bandwidth but slows down performance. This is only used if ``CommonMiddleware`` -is installed (see :ref:`topics-http-middleware`). +is installed (see :doc:`/topics/http/middleware`). .. setting:: USE_I18N diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 12372dd0f154..adce5d217714 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -1,5 +1,3 @@ -.. _ref-signals: - ======= Signals ======= @@ -8,11 +6,11 @@ A list of all the signals that Django sends. .. seealso:: - See the documentation on the :ref:`signal dispatcher ` for + See the documentation on the :doc:`signal dispatcher ` for information regarding how to register for and receive signals. - The :ref:`comment framework ` sends a :ref:`set - of comment-related signals `. + The :doc:`comment framework ` sends a :doc:`set + of comment-related signals `. Model signals ============= @@ -61,7 +59,7 @@ Arguments sent with this signal: A dictionary of keyword arguments passed to :meth:`~django.db.models.Model.__init__`:. -For example, the :ref:`tutorial ` has this line: +For example, the :doc:`tutorial ` has this line: .. code-block:: python @@ -313,7 +311,7 @@ Arguments that are sent with this signal: Management signals ================== -Signals sent by :ref:`django-admin `. +Signals sent by :doc:`django-admin `. post_syncdb ----------- @@ -416,7 +414,7 @@ Test signals .. module:: django.test.signals :synopsis: Signals sent during testing. -Signals only sent when :ref:`running tests `. +Signals only sent when :doc:`running tests `. template_rendered ----------------- diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 3e267531de93..3c4e3b34e435 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -1,12 +1,10 @@ -.. _ref-templates-api: - ==================================================== The Django template language: For Python programmers ==================================================== This document explains the Django template system from a technical perspective -- how it works and how to extend it. If you're just looking for -reference on the language syntax, see :ref:`topics-templates`. +reference on the language syntax, see :doc:`/topics/templates`. If you're looking to use the Django template system as part of another application -- i.e., without the rest of the framework -- make sure to read @@ -323,7 +321,7 @@ and return a dictionary of items to be merged into the context. By default, .. versionadded:: 1.2 The ``'messages'`` context processor was added. For more information, see - the :ref:`messages documentation `. + the :doc:`messages documentation `. .. versionchanged:: 1.2 The auth context processor was moved in this release from its old location @@ -384,7 +382,7 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every logged in). * ``messages`` -- A list of messages (as strings) that have been set - via the :ref:`messages framework `. + via the :doc:`messages framework `. * ``perms`` -- An instance of ``django.core.context_processors.PermWrapper``, representing the @@ -397,7 +395,7 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every .. versionchanged:: 1.2 Prior to version 1.2, the ``messages`` variable was a lazy accessor for ``user.get_and_delete_messages()``. It has been changed to include any - messages added via the :ref:`messages framework `. + messages added via the :doc:`messages framework `. django.core.context_processors.debug ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -423,7 +421,7 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every * ``LANGUAGE_CODE`` -- ``request.LANGUAGE_CODE``, if it exists. Otherwise, the value of the :setting:`LANGUAGE_CODE` setting. -See :ref:`topics-i18n` for more. +See :doc:`/topics/i18n/index` for more. django.core.context_processors.media ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -440,7 +438,7 @@ django.core.context_processors.csrf .. versionadded:: 1.2 This processor adds a token that is needed by the ``csrf_token`` template tag -for protection against :ref:`Cross Site Request Forgeries `. +for protection against :doc:`Cross Site Request Forgeries `. django.core.context_processors.request ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -458,7 +456,7 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every * ``messages`` -- A list of messages (as strings) that have been set via the user model (using ``user.message_set.create``) or through - the :ref:`messages framework `. + the :doc:`messages framework `. .. versionadded:: 1.2 This template context variable was previously supplied by the ``'auth'`` @@ -716,7 +714,7 @@ settings you wish to specify. You might want to consider setting at least :setting:`TEMPLATE_DIRS` (if you're going to use template loaders), :setting:`DEFAULT_CHARSET` (although the default of ``utf-8`` is probably fine) and :setting:`TEMPLATE_DEBUG`. All available settings are described in the -:ref:`settings documentation `, and any setting starting with +:doc:`settings documentation `, and any setting starting with ``TEMPLATE_`` is of obvious interest. .. _topic-template-alternate-language: diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 0cf445cab7ab..ac7fa576a15d 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1,5 +1,3 @@ -.. _ref-templates-builtins: - ================================== Built-in template tags and filters ================================== @@ -60,8 +58,8 @@ csrf_token In the Django 1.1.X series, this is a no-op tag that returns an empty string for future compatibility purposes. In Django 1.2 and later, it is used for CSRF -protection, as described in the documentation for :ref:`Cross Site Request -Forgeries `. +protection, as described in the documentation for :doc:`Cross Site Request +Forgeries `. .. templatetag:: cycle @@ -633,7 +631,7 @@ load Load a custom template tag set. -See :ref:`Custom tag and filter libraries ` for more information. +See :doc:`Custom tag and filter libraries ` for more information. .. templatetag:: now @@ -2062,7 +2060,7 @@ django.contrib.humanize ~~~~~~~~~~~~~~~~~~~~~~~ A set of Django template filters useful for adding a "human touch" to data. See -:ref:`ref-contrib-humanize`. +:doc:`/ref/contrib/humanize`. django.contrib.markup ~~~~~~~~~~~~~~~~~~~~~ @@ -2079,7 +2077,7 @@ django.contrib.webdesign ~~~~~~~~~~~~~~~~~~~~~~~~ A collection of template tags that can be useful while designing a website, -such as a generator of Lorem Ipsum text. See :ref:`ref-contrib-webdesign`. +such as a generator of Lorem Ipsum text. See :doc:`/ref/contrib/webdesign`. i18n ~~~~ diff --git a/docs/ref/templates/index.txt b/docs/ref/templates/index.txt index 6655b3da18ea..0aa4798a9414 100644 --- a/docs/ref/templates/index.txt +++ b/docs/ref/templates/index.txt @@ -1,5 +1,3 @@ -.. _ref-templates-index: - ========= Templates ========= @@ -18,4 +16,4 @@ an understanding of HTML; no knowledge of Python is required. .. seealso:: For information on writing your own custom tags and filters, see - :ref:`howto-custom-template-tags`. + :doc:`/howto/custom-template-tags`. diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt index a6149119bfc9..8e110af5d5db 100644 --- a/docs/ref/unicode.txt +++ b/docs/ref/unicode.txt @@ -1,5 +1,3 @@ -.. _ref-unicode: - ============ Unicode data ============ @@ -95,7 +93,7 @@ Calling ``unicode()`` with the lazy translation as the argument will generate a Unicode string in the current locale. For more details about lazy translation objects, refer to the -:ref:`internationalization ` documentation. +:doc:`internationalization ` documentation. Useful utility functions ------------------------ diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index e64b3868b9c0..39b01df0becf 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -1,5 +1,3 @@ -.. _ref-utils: - ============ Django Utils ============ @@ -32,7 +30,7 @@ into account when building its cache key. Requests with the same path but different header content for headers named in ``Vary`` need to get different cache keys to prevent delivery of wrong content. -For example, :ref:`internationalization ` middleware would need +For example, :doc:`internationalization ` middleware would need to distinguish caches by the ``Accept-language`` header. .. function:: patch_cache_control(response, **kwargs) @@ -395,7 +393,7 @@ applied once). :synopsis: Internationalization support. For a complete discussion on the usage of the following see the -:ref:`Internationalization documentation `. +:doc:`Internationalization documentation `. .. function:: gettext(message) diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index 916c388f0889..4937512e463d 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -1,5 +1,3 @@ -.. _ref-validators: - ========== Validators ========== @@ -42,12 +40,12 @@ use the same validator with forms:: How validators are run ====================== -See the :ref:`form validation ` for more information on +See the :doc:`form validation ` for more information on how validators are run in forms, and :ref:`Validating objects ` for how they're run in models. Note that validators will not be run automatically when you save a model, but if you are using a ``ModelForm``, it will run your validators on any fields that are included in -your form. See the :ref:`ModelForm documentation ` +your form. See the :doc:`ModelForm documentation ` for information on how model validation interacts with forms. Built-in validators diff --git a/docs/releases/0.95.txt b/docs/releases/0.95.txt index b74160128bfa..7409bff1c02e 100644 --- a/docs/releases/0.95.txt +++ b/docs/releases/0.95.txt @@ -1,5 +1,3 @@ -.. _releases-0.95: - ================================= Django version 0.95 release notes ================================= @@ -100,7 +98,7 @@ Problem reports and getting help ================================ Need help resolving a problem with Django? The documentation in the distribution -is also available online_ at the `Django Web site`_. The :ref:`FAQ ` +is also available online_ at the `Django Web site`_. The :doc:`FAQ ` document is especially recommended, as it contains a number of issues that come up time and again. diff --git a/docs/releases/0.96.txt b/docs/releases/0.96.txt index 8d795acebd58..1224360e3f50 100644 --- a/docs/releases/0.96.txt +++ b/docs/releases/0.96.txt @@ -1,5 +1,3 @@ -.. _releases-0.96: - ================================= Django version 0.96 release notes ================================= diff --git a/docs/releases/1.0-alpha-1.txt b/docs/releases/1.0-alpha-1.txt index caee575cb2f0..82846be44ab6 100644 --- a/docs/releases/1.0-alpha-1.txt +++ b/docs/releases/1.0-alpha-1.txt @@ -1,5 +1,3 @@ -.. _releases-1.0-alpha-1: - ================================ Django 1.0 alpha release notes ================================ @@ -34,7 +32,7 @@ Refactored admin application (newforms-admin) documentation for the admin application is available online in the official Django documentation: - :ref:`admin reference ` + :doc:`admin reference ` Improved Unicode handling Django's internals have been refactored to use Unicode throughout; @@ -45,7 +43,7 @@ Improved Unicode handling Unicode gracefully. Details are available in Django's Unicode-handling documentation: - :ref:`unicode reference ` + :doc:`unicode reference ` An improved Django ORM Django's object-relational mapper -- the component which provides @@ -156,7 +154,7 @@ to join the discussions there. Django's online documentation also includes pointers on how to contribute to Django: - :ref:`contributing to Django ` + :doc:`contributing to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed diff --git a/docs/releases/1.0-alpha-2.txt b/docs/releases/1.0-alpha-2.txt index 5cbd77720480..83e2e2e14ac5 100644 --- a/docs/releases/1.0-alpha-2.txt +++ b/docs/releases/1.0-alpha-2.txt @@ -1,5 +1,3 @@ -.. _releases-1.0-alpha-2: - ================================ Django 1.0 alpha 2 release notes ================================ @@ -21,8 +19,8 @@ What's new in Django 1.0 alpha 2 Django's development trunk has been the site of nearly constant activity over the past year, with several major new features landing since the 0.96 release. -For features which were new as of Django 1.0 alpha 1, see :ref:`the 1.0 alpha 1 -release notes `. Since the 1.0 alpha 1 release several new features have landed, including: ``django.contrib.gis`` (`GeoDjango`_) @@ -37,8 +35,8 @@ features have landed, including: Pluggable file storage Django's built-in ``FileField`` and ``ImageField`` now can take advantage of pluggable file-storage backends, allowing extensive customization of where - and how uploaded files get stored by Django. For details, see :ref:`the - files documentation `; big thanks go to Marty Alchin for + and how uploaded files get stored by Django. For details, see :doc:`the + files documentation `; big thanks go to Marty Alchin for putting in the hard work to get this completed. Jython compatibility @@ -51,11 +49,11 @@ Jython compatibility There are many other new features and improvements in this release, including two major performance boosts: strings marked for translation using -:ref:`Django's internationalization system ` now consume far less +:doc:`Django's internationalization system ` now consume far less memory, and Django's internal dispatcher -- which is invoked frequently during request/response processing and when working with Django's object-relational mapper -- is now significantly faster. - + .. _GeoDjango: http://geodjango.org/ .. _Geographic Information Systems: http://en.wikipedia.org/wiki/Geographic_information_system .. _Its documentation: http://geodjango.org/docs/ @@ -131,7 +129,7 @@ to join the discussions there. Django's online documentation also includes pointers on how to contribute to Django: - :ref:`contributing to Django ` + :doc:`contributing to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed diff --git a/docs/releases/1.0-beta-2.txt b/docs/releases/1.0-beta-2.txt index 89b9233576aa..eabd6b744b4e 100644 --- a/docs/releases/1.0-beta-2.txt +++ b/docs/releases/1.0-beta-2.txt @@ -1,5 +1,3 @@ -.. _releases-1.0-beta-2: - =============================== Django 1.0 beta 2 release notes =============================== @@ -21,11 +19,11 @@ What's new in Django 1.0 beta 2 Django's development trunk has been the site of nearly constant activity over the past year, with several major new features landing since the 0.96 release. For features which were new as of Django 1.0 -alpha 1, see :ref:`the 1.0 alpha 1 release notes -`. For features which were new as of Django 1.0 -alpha 2, see :ref:`the 1.0 alpha 2 release notes -`. For features which were new as of Django 1.0 -beta 1, see :ref:`the 1.0 beta 1 release notes `. +alpha 1, see :doc:`the 1.0 alpha 1 release notes +`. For features which were new as of Django 1.0 +alpha 2, see :doc:`the 1.0 alpha 2 release notes +`. For features which were new as of Django 1.0 +beta 1, see :doc:`the 1.0 beta 1 release notes `. This beta release includes two major features: @@ -33,9 +31,9 @@ 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. :ref:`Full documentation - ` is available, as well as :ref:`an - upgrade guide ` if you were using + customizability. :doc:`Full documentation + ` is available, as well as :doc:`an + upgrade guide ` if you were using the previous incarnation of the comments application.. Refactored documentation @@ -59,8 +57,8 @@ Also, as part of its ongoing deprecation process, Django's old form-handling system has been removed; this means ``django.oldforms`` no longer exists, and its various API hooks (such as automatic manipulators) are no longer present in Django. This system has been -completely replaced by :ref:`the new form-handling system -` in ``django.forms``. +completely replaced by :doc:`the new form-handling system +` in ``django.forms``. The Django 1.0 roadmap @@ -114,7 +112,7 @@ to join the discussions there. Django's online documentation also includes pointers on how to contribute to Django: - :ref:`contributing to Django ` + :doc:`contributing to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed diff --git a/docs/releases/1.0-beta.txt b/docs/releases/1.0-beta.txt index c1957a75a431..9e07e6c03fc8 100644 --- a/docs/releases/1.0-beta.txt +++ b/docs/releases/1.0-beta.txt @@ -1,5 +1,3 @@ -.. _releases-1.0-beta: - =============================== Django 1.0 beta 1 release notes =============================== @@ -20,9 +18,9 @@ What's new in Django 1.0 beta 1 Django's development trunk has been the site of nearly constant activity over the past year, with several major new features landing since the 0.96 release. -For features which were new as of Django 1.0 alpha 1, see :ref:`the 1.0 alpha 1 -release notes `. For features which were new as of Django -1.0 alpha 2, see :ref:`the 1.0 alpha 2 release notes `. +For features which were new as of Django 1.0 alpha 1, see :doc:`the 1.0 alpha 1 +release notes `. For features which were new as of Django +1.0 alpha 2, see :doc:`the 1.0 alpha 2 release notes `. This beta release does not contain any major new features, but does include several smaller updates and improvements to Django: @@ -38,7 +36,7 @@ Improved flexibility in the admin interface (``django.contrib.admin``), introduced in Django 1.0 alpha 1, two new hooks have been added to allow customized pre- and post-save handling of model instances in the admin. Full - details are in :ref:`the admin documentation `. + details are in :doc:`the admin documentation `. ``INSERT``/``UPDATE`` distinction Although Django's default behavior of having a model's ``save()`` @@ -59,7 +57,7 @@ Split ``CacheMiddleware`` flexibility for situations where combining these functions into a single middleware posed problems. Full details, including updated notes on appropriate use, are in - :ref:`the caching documentation `. + :doc:`the caching documentation `. Removal of deprecated features A number of features and methods which had previously been marked @@ -148,7 +146,7 @@ to join the discussions there. Django's online documentation also includes pointers on how to contribute to Django: - :ref:`contributing to Django ` + :doc:`contributing to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed diff --git a/docs/releases/1.0-porting-guide.txt b/docs/releases/1.0-porting-guide.txt index f87da1c8d05d..95b9efe255a4 100644 --- a/docs/releases/1.0-porting-guide.txt +++ b/docs/releases/1.0-porting-guide.txt @@ -13,7 +13,7 @@ Changes`_ for a list of a bunch of less-common compatibility issues. .. seealso:: - The :ref:`1.0 release notes `. That document explains the new + The :doc:`1.0 release notes `. That document explains the new features in 1.0 more deeply; the porting guide is more concerned with helping you quickly update your code. @@ -31,7 +31,7 @@ now uses Unicode strings throughout. In most places, raw strings will continue to work, but updating to use Unicode literals will prevent some obscure problems. -See :ref:`ref-unicode` for full details. +See :doc:`/ref/unicode` for full details. Models ------ @@ -211,7 +211,7 @@ New (1.0):: can be found on the `NewformsAdminBranch wiki page`__ * The new admin comes with a ton of new features; you can read about them in - the :ref:`admin documentation `. + the :doc:`admin documentation `. __ http://code.djangoproject.com/wiki/NewformsAdminBranch @@ -271,7 +271,7 @@ New:: If you're using the old forms system (formerly known as ``django.forms`` and ``django.oldforms``), you'll have to rewrite your forms. A good place to start -is the :ref:`forms documentation ` +is the :doc:`forms documentation ` Handle uploaded files using the new API ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -318,7 +318,7 @@ Old (0.96) New (1.0) Note that the ``width`` and ``height`` attributes only make sense for :class:`~django.db.models.ImageField` fields. More details can be found in the -:ref:`model API ` documentation. +:doc:`model API ` documentation. Use ``Paginator`` instead of ``ObjectPaginator`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -392,7 +392,7 @@ 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 -:ref:`ref-contrib-comments-upgrade` for details. +:doc:`/ref/contrib/comments/upgrade` for details. Template tags ------------- diff --git a/docs/releases/1.0.1.txt b/docs/releases/1.0.1.txt index ce101bed0336..780dc53c1f94 100644 --- a/docs/releases/1.0.1.txt +++ b/docs/releases/1.0.1.txt @@ -1,5 +1,3 @@ -.. _releases-1.0.1: - ========================== Django 1.0.1 release notes ========================== diff --git a/docs/releases/1.0.2.txt b/docs/releases/1.0.2.txt index 136a833f05b5..b34522a08dfb 100644 --- a/docs/releases/1.0.2.txt +++ b/docs/releases/1.0.2.txt @@ -1,5 +1,3 @@ -.. _releases-1.0.2: - ========================== Django 1.0.2 release notes ========================== @@ -9,7 +7,7 @@ Welcome to Django 1.0.2! This is the second "bugfix" release in the Django 1.0 series, improving the stability and performance of the Django 1.0 codebase. As such, Django 1.0.2 contains no new features (and, pursuant to -:ref:`our compatibility policy `, maintains backwards compatibility with Django +:doc:`our compatibility policy `, maintains backwards compatibility with Django 1.0.0), but does contain a number of fixes and other improvements. Django 1.0.2 is a recommended upgrade for any development or deployment currently using or targeting Django 1.0. @@ -27,7 +25,7 @@ Django's unit-test suite. Django 1.0.2 contains updated packaging scripts, and the release package contains the directories omitted from Django 1.0.1. As such, this release contains all of the fixes and improvements from Django -1.0.1; see :ref:`the Django 1.0.1 release notes ` for +1.0.1; see :doc:`the Django 1.0.1 release notes ` for details. Additionally, in the period since Django 1.0.1 was released: diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt index 6827a62cc89e..359490aad3a7 100644 --- a/docs/releases/1.0.txt +++ b/docs/releases/1.0.txt @@ -1,5 +1,3 @@ -.. _releases-1.0: - ======================== Django 1.0 release notes ======================== @@ -24,12 +22,12 @@ contributions overtake those made privately. Stability and forwards-compatibility ==================================== -:ref:`The release of Django 1.0 ` comes with a promise of API +:doc:`The release of Django 1.0 ` comes with a promise of API stability and forwards-compatibility. In a nutshell, this means that code you develop against Django 1.0 will continue to work against 1.1 unchanged, and you should need to make only minor changes for any 1.X release. -See the :ref:`API stability guide ` for full details. +See the :doc:`API stability guide ` for full details. Backwards-incompatible changes ============================== @@ -42,7 +40,7 @@ detailed porting guide: :maxdepth: 1 1.0-porting-guide - + A complete list of backwards-incompatible changes can be found at http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges. @@ -60,9 +58,9 @@ In fact, new documentation is one of our favorite features of Django 1.0, so we might as well start there. First, there's a new documentation site: http://docs.djangoproject.com/ - + The documentation has been greatly improved, cleaned up, and generally made -awesome. There's now dedicated search, indexes, and more. +awesome. There's now dedicated search, indexes, and more. We can't possibly document everything that's new in 1.0, but the documentation will be your definitive guide. Anywhere you see something like: @@ -85,7 +83,7 @@ Django's new form-handling library (introduced in the 0.96 release as redesigned with extensibility and customization in mind. Full documentation for the admin application is available online in the official Django documentation: -See the :ref:`admin reference ` for details +See the :doc:`admin reference ` for details Improved Unicode handling ------------------------- @@ -97,7 +95,7 @@ interoperability with third-party libraries and systems which may or may not handle Unicode gracefully. Details are available in Django's Unicode-handling documentation. -See :ref:`ref-unicode`. +See :doc:`/ref/unicode`. An improved ORM --------------- @@ -142,8 +140,8 @@ Pluggable file storage Django's built-in ``FileField`` and ``ImageField`` now can take advantage of pluggable file-storage backends, allowing extensive customization of where and -how uploaded files get stored by Django. For details, see :ref:`the files -documentation `; big thanks go to Marty Alchin for putting in the +how uploaded files get stored by Django. For details, see :doc:`the files +documentation `; big thanks go to Marty Alchin for putting in the hard work to get this completed. Jython compatibility @@ -155,7 +153,7 @@ Django's codebase has been refactored to remove incompatibilities with on the Java Virtual Machine. Django is now compatible with the forthcoming Jython 2.5 release. -See :ref:`howto-jython`. +See :doc:`/howto/jython`. .. _Jython: http://www.jython.org/ @@ -187,17 +185,17 @@ handle the two parts of caching (inserting into and reading from the cache) separately, offering additional flexibility for situations where combining these functions into a single middleware posed problems. -Full details, including updated notes on appropriate use, are in :ref:`the -caching documentation `. +Full details, including updated notes on appropriate use, are in :doc:`the +caching documentation `. 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. :ref:`Full documentation -` is available, as well as :ref:`an upgrade guide -` if you were using the previous incarnation of +increasing its flexibility and customizability. :doc:`Full documentation +` is available, as well as :doc:`an upgrade guide +` if you were using the previous incarnation of the comments application. Removal of deprecated features @@ -240,7 +238,7 @@ Caveats with support of certain databases ----------------------------------------- Django attempts to support as many features as possible on all database -backends. However, not all database backends are alike, and in particular many of the supported database differ greatly from version to version. It's a good idea to checkout our :ref:`notes on supported database `: +backends. However, not all database backends are alike, and in particular many of the supported database differ greatly from version to version. It's a good idea to checkout our :doc:`notes on supported database `: - :ref:`mysql-notes` - :ref:`sqlite-notes` diff --git a/docs/releases/1.1-alpha-1.txt b/docs/releases/1.1-alpha-1.txt index 664c3545614f..b15a2a423c4d 100644 --- a/docs/releases/1.1-alpha-1.txt +++ b/docs/releases/1.1-alpha-1.txt @@ -1,5 +1,3 @@ -.. _releases-1.1-alpha-1: - ================================ Django 1.1 alpha 1 release notes ================================ @@ -37,8 +35,8 @@ results of the aggregate directly, or else annotate the objects in a :class:`QuerySet` with the results of the aggregate query. This feature is available as new :meth:`QuerySet.aggregate()`` and -:meth:`QuerySet.annotate()`` methods, and is covered in detail in :ref:`the ORM -aggregation documentation ` +:meth:`QuerySet.annotate()`` methods, and is covered in detail in :doc:`the ORM +aggregation documentation ` Query expressions ~~~~~~~~~~~~~~~~~ @@ -53,7 +51,7 @@ Performance improvements .. currentmodule:: django.test -Tests written using Django's :ref:`testing framework ` now run +Tests written using Django's :doc:`testing framework ` now run dramatically faster (as much as 10 times faster in many cases). This was accomplished through the introduction of transaction-based tests: when @@ -68,7 +66,7 @@ Other improvements Other new features and changes introduced since Django 1.0 include: -* The :ref:`CSRF protection middleware ` has been split into +* The :doc:`CSRF protection middleware ` has been split into two classes -- ``CsrfViewMiddleware`` checks incoming requests, and ``CsrfResponseMiddleware`` processes outgoing responses. The combined ``CsrfMiddleware`` class (which does both) remains for @@ -85,13 +83,13 @@ Other new features and changes introduced since Django 1.0 include: * The ``include()`` function in Django URLconf modules can now accept sequences of URL patterns (generated by ``patterns()``) in addition to module names. -* Instances of Django forms (see :ref:`the forms overview `) +* Instances of Django forms (see :doc:`the forms overview `) now have two additional methods, ``hidden_fields()`` and ``visible_fields()``, which return the list of hidden -- i.e., ```` -- and visible fields on the form, respectively. -* The ``redirect_to`` generic view (see :ref:`the generic views documentation - `) now accepts an additional keyword argument +* The ``redirect_to`` generic view (see :doc:`the generic views documentation + `) now accepts an additional keyword argument ``permanent``. If ``permanent`` is ``True``, the view will emit an HTTP permanent redirect (status code 301). If ``False``, the view will emit an HTTP temporary redirect (status code 302). @@ -104,8 +102,8 @@ Other new features and changes introduced since Django 1.0 include: * The ``{% for %}`` tag in Django's template language now accepts an optional ``{% empty %}`` clause, to be displayed when ``{% for %}`` is asked to loop - over an empty sequence. See :ref:`the list of built-in template tags - ` for examples of this. + over an empty sequence. See :doc:`the list of built-in template tags + ` for examples of this. The Django 1.1 roadmap ====================== @@ -153,7 +151,7 @@ discussions there. Django's online documentation also includes pointers on how to contribute to Django: - * :ref:`How to contribute to Django ` + * :doc:`How to contribute to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed bugfixes -- are always welcome and diff --git a/docs/releases/1.1-beta-1.txt b/docs/releases/1.1-beta-1.txt index e7dcb4633ddd..83423962b316 100644 --- a/docs/releases/1.1-beta-1.txt +++ b/docs/releases/1.1-beta-1.txt @@ -1,5 +1,3 @@ -.. _releases-1.1-beta-1: - =============================== Django 1.1 beta 1 release notes =============================== @@ -22,7 +20,7 @@ What's new in Django 1.1 beta 1 .. seealso:: - The :ref:`1.1 alpha release notes `, which has a + The :doc:`1.1 alpha release notes `, which has a list of everything new between Django 1.0 and Django 1.1 alpha. Model improvements @@ -91,7 +89,7 @@ up as form widgets on the list pages, and can be edited and saved in bulk. Admin "actions" ~~~~~~~~~~~~~~~ -You can now define :ref:`admin actions ` that can perform +You can now define :doc:`admin actions ` that can perform some action to a group of models in bulk. Users will be able to select objects on the change list page and then apply these bulk actions to all selected objects. @@ -104,7 +102,7 @@ Testing improvements .. currentmodule:: django.test.client A couple of small but very useful improvements have been made to the -:ref:`testing framework `: +:doc:`testing framework `: * The test :class:`Client` now can automatically follow redirects with the ``follow`` argument to :meth:`Client.get` and :meth:`Client.post`. This @@ -119,8 +117,8 @@ A couple of small but very useful improvements have been made to the Conditional view processing --------------------------- -Django now has much better support for :ref:`conditional view processing -` using the standard ``ETag`` and +Django now has much better support for :doc:`conditional view processing +` using the standard ``ETag`` and ``Last-Modified`` HTTP headers. This means you can now easily short-circuit view processing by testing less-expensive conditions. For many views this can lead to a serious improvement in speed and reduction in bandwidth. @@ -138,14 +136,14 @@ release, including: * There's a new :tfilter:`safeseq` template filter which works just like :tfilter:`safe` for lists, marking each item in the list as safe. - * :ref:`Cache backends ` now support ``incr()`` and + * :doc:`Cache backends ` now support ``incr()`` and ``decr()`` commands to increment and decrement the value of a cache key. On cache backends that support atomic increment/decrement -- most notably, the memcached backend -- these operations will be atomic, and quite fast. - * Django now can :ref:`easily delegate authentication to the web server - ` via a new authentication backend that supports + * Django now can :doc:`easily delegate authentication to the web server + ` via a new authentication backend that supports the standard ``REMOTE_USER`` environment variable used for this purpose. * There's a new :func:`django.shortcuts.redirect` function that makes it @@ -198,7 +196,7 @@ discussions there. Django's online documentation also includes pointers on how to contribute to Django: - * :ref:`How to contribute to Django ` + * :doc:`How to contribute to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed bugfixes -- are always welcome and diff --git a/docs/releases/1.1-rc-1.txt b/docs/releases/1.1-rc-1.txt index bda424800e78..f74444f1f95b 100644 --- a/docs/releases/1.1-rc-1.txt +++ b/docs/releases/1.1-rc-1.txt @@ -1,5 +1,3 @@ -.. _releases-1.1-rc-1: - ============================= Django 1.1 RC 1 release notes ============================= @@ -30,8 +28,8 @@ contains only one new feature (see below); work leading up to this release candidate has instead been focused on bugfixing, particularly on the new features introduced prior to the 1.1 beta. -For an overview of those features, consult :ref:`the Django 1.1 beta -release notes `. +For an overview of those features, consult :doc:`the Django 1.1 beta +release notes `. URL namespaces @@ -104,7 +102,7 @@ discussions there. Django's online documentation also includes pointers on how to contribute to Django: - * :ref:`How to contribute to Django ` + * :doc:`How to contribute to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed bugfixes -- are always welcome and diff --git a/docs/releases/1.1.2.txt b/docs/releases/1.1.2.txt index 3e5355f2318c..90a69759bf51 100644 --- a/docs/releases/1.1.2.txt +++ b/docs/releases/1.1.2.txt @@ -1,5 +1,3 @@ -.. _releases-1.1.2: - ========================== Django 1.1.2 release notes ========================== @@ -15,7 +13,7 @@ improvements. Django 1.1.2 is a recommended upgrade for any development or deployment currently using or targeting Django 1.1. For full details on the new features, backwards incompatibilities, and -deprecated features in the 1.1 branch, see the :ref:`releases-1.1`. +deprecated features in the 1.1 branch, see the :doc:`/releases/1.1`. Backwards-incompatible changes in 1.1.2 ======================================= diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 30ef9197c75d..39cb0ab2b0bd 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -1,5 +1,3 @@ -.. _releases-1.1: - ======================== Django 1.1 release notes ======================== @@ -19,7 +17,7 @@ fixes, and an easy upgrade path from Django 1.0. Backwards-incompatible changes in 1.1 ===================================== -Django has a policy of :ref:`API stability `. This means +Django has a policy of :doc:`API stability `. This means that, in general, code you develop against Django 1.0 should continue to work against 1.1 unchanged. However, we do sometimes make backwards-incompatible changes if they're necessary to resolve bugs, and there are a handful of such @@ -176,7 +174,7 @@ be upgraded to a ``DeprecationWarning``, which will be displayed loudly. Django 1.3 will remove ``AdminSite.root()`` entirely. For more details on our deprecation policies and strategy, see -:ref:`internals-release-process`. +:doc:`/internals/release-process`. What's new in Django 1.1 ======================== @@ -203,8 +201,8 @@ results of the aggregate directly, or else annotate the objects in a :class:`QuerySet` with the results of the aggregate query. This feature is available as new :meth:`QuerySet.aggregate()`` and -:meth:`QuerySet.annotate()`` methods, and is covered in detail in :ref:`the ORM -aggregation documentation `. +:meth:`QuerySet.annotate()`` methods, and is covered in detail in :doc:`the ORM +aggregation documentation `. Query expressions ~~~~~~~~~~~~~~~~~ @@ -265,15 +263,15 @@ You'll do this with the new queryset methods Testing improvements -------------------- -A few notable improvements have been made to the :ref:`testing framework -`. +A few notable improvements have been made to the :doc:`testing framework +`. Test performance improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. currentmodule:: django.test -Tests written using Django's :ref:`testing framework ` now run +Tests written using Django's :doc:`testing framework ` now run dramatically faster (as much as 10 times faster in many cases). This was accomplished through the introduction of transaction-based tests: when @@ -316,7 +314,7 @@ up as form widgets on the list pages, and can be edited and saved in bulk. Admin "actions" ~~~~~~~~~~~~~~~ -You can now define :ref:`admin actions ` that can +You can now define :doc:`admin actions ` that can perform some action to a group of models in bulk. Users will be able to select objects on the change list page and then apply these bulk actions to all selected objects. @@ -327,8 +325,8 @@ one fell swoop. Conditional view processing --------------------------- -Django now has much better support for :ref:`conditional view processing -` using the standard ``ETag`` and +Django now has much better support for :doc:`conditional view processing +` using the standard ``ETag`` and ``Last-Modified`` HTTP headers. This means you can now easily short-circuit view processing by testing less-expensive conditions. For many views this can lead to a serious improvement in speed and reduction in bandwidth. @@ -376,7 +374,7 @@ Other improvements Other new features and changes introduced since Django 1.0 include: -* The :ref:`CSRF protection middleware ` has been split into +* The :doc:`CSRF protection middleware ` has been split into two classes -- ``CsrfViewMiddleware`` checks incoming requests, and ``CsrfResponseMiddleware`` processes outgoing responses. The combined ``CsrfMiddleware`` class (which does both) remains for @@ -393,13 +391,13 @@ Other new features and changes introduced since Django 1.0 include: * The ``include()`` function in Django URLconf modules can now accept sequences of URL patterns (generated by ``patterns()``) in addition to module names. -* Instances of Django forms (see :ref:`the forms overview `) +* Instances of Django forms (see :doc:`the forms overview `) now have two additional methods, ``hidden_fields()`` and ``visible_fields()``, which return the list of hidden -- i.e., ```` -- and visible fields on the form, respectively. -* The ``redirect_to`` generic view (see :ref:`the generic views documentation - `) now accepts an additional keyword argument +* The ``redirect_to`` generic view (see :doc:`the generic views documentation + `) now accepts an additional keyword argument ``permanent``. If ``permanent`` is ``True``, the view will emit an HTTP permanent redirect (status code 301). If ``False``, the view will emit an HTTP temporary redirect (status code 302). @@ -412,8 +410,8 @@ Other new features and changes introduced since Django 1.0 include: * The ``{% for %}`` tag in Django's template language now accepts an optional ``{% empty %}`` clause, to be displayed when ``{% for %}`` is asked to loop - over an empty sequence. See :ref:`the list of built-in template tags - ` for examples of this. + over an empty sequence. See :doc:`the list of built-in template tags + ` for examples of this. * The :djadmin:`dumpdata` management command now accepts individual model names as arguments, allowing you to export the data just from @@ -422,14 +420,14 @@ Other new features and changes introduced since Django 1.0 include: * There's a new :tfilter:`safeseq` template filter which works just like :tfilter:`safe` for lists, marking each item in the list as safe. -* :ref:`Cache backends ` now support ``incr()`` and +* :doc:`Cache backends ` now support ``incr()`` and ``decr()`` commands to increment and decrement the value of a cache key. On cache backends that support atomic increment/decrement -- most notably, the memcached backend -- these operations will be atomic, and quite fast. -* Django now can :ref:`easily delegate authentication to the web server - ` via a new authentication backend that supports +* Django now can :doc:`easily delegate authentication to the web server + ` via a new authentication backend that supports the standard ``REMOTE_USER`` environment variable used for this purpose. * There's a new :func:`django.shortcuts.redirect` function that makes it @@ -456,7 +454,7 @@ join the discussions! Django's online documentation also includes pointers on how to contribute to Django: - * :ref:`How to contribute to Django ` + * :doc:`How to contribute to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed bugfixes -- are always welcome and diff --git a/docs/releases/1.2-alpha-1.txt b/docs/releases/1.2-alpha-1.txt index 1e9d4422ceb4..4144a9a3a93c 100644 --- a/docs/releases/1.2-alpha-1.txt +++ b/docs/releases/1.2-alpha-1.txt @@ -1,5 +1,3 @@ -.. _releases-1.2-alpha-1: - ================================ Django 1.2 alpha 1 release notes ================================ @@ -25,7 +23,7 @@ CSRF Protection --------------- There have been large changes to the way that CSRF protection works, detailed in -:ref:`the CSRF documentaton `. The following are the major +:doc:`the CSRF documentaton `. The following are the major changes that developers must be aware of: * ``CsrfResponseMiddleware`` and ``CsrfMiddleware`` have been deprecated, and @@ -303,7 +301,7 @@ User Messages API The API for storing messages in the user ``Message`` model (via ``user.message_set.create``) is now deprecated and will be removed in Django -1.4 according to the standard :ref:`release process `. +1.4 according to the standard :doc:`release process `. To upgrade your code, you need to replace any instances of:: @@ -327,7 +325,7 @@ with:: ... For more information, see the full -:ref:`messages documentation `. You should begin to +:doc:`messages documentation `. You should begin to update your code to use the new API immediately. Date format helper functions @@ -378,8 +376,8 @@ release cycle. Some minor features will continue development until the CSRF support ------------ -Django now has much improved protection against :ref:`Cross-Site -Request Forgery (CSRF) attacks`. This type of attack +Django now has much improved protection against :doc:`Cross-Site +Request Forgery (CSRF) attacks`. This type of attack occurs when a malicious Web site contains a link, a form button or some javascript that is intended to perform some action on your Web site, using the credentials of a logged-in user who visits the @@ -395,7 +393,7 @@ You can now :ref:`configure the way that Django sends e-mail can now choose a configurable e-mail backend to send messages. If your hosting provider uses a sandbox or some other non-SMTP technique for sending mail, you can now construct an e-mail backend that will allow -Django's standard :ref:`mail sending methods` to use +Django's standard :doc:`mail sending methods` to use those facilities. This also makes it easier to debug mail sending - Django ships with @@ -408,8 +406,8 @@ e-mail to be :ref:`thrown away`. Messages Framework ------------------ -Django now includes a robust and configurable :ref:`messages framework -` with built-in support for cookie- and session-based +Django now includes a robust and configurable :doc:`messages framework +` with built-in support for cookie- and session-based messaging, for both anonymous and authenticated clients. The messages framework replaces the deprecated user message API and allows you to temporarily store messages in one request and retrieve them for display in a subsequent request @@ -418,8 +416,8 @@ messages in one request and retrieve them for display in a subsequent request Support for multiple databases ------------------------------ -Django 1.2 adds the ability to use :ref:`more than one database -` in your Django project. Queries can be +Django 1.2 adds the ability to use :doc:`more than one database +` in your Django project. Queries can be issued at a specific database with the `using()` method on querysets; individual objects can be saved to a specific database by providing a ``using`` argument when you save the instance. @@ -500,7 +498,7 @@ from the test run that reports details of the tests run before the interruption. Improved localization --------------------- -Django's :ref:`internationalization framework ` has been +Django's :doc:`internationalization framework ` has been expanded by locale aware formatting and form processing. That means, if enabled, dates and numbers on templates will be displayed using the format specified for the current locale. Django will also use localized formats @@ -568,7 +566,7 @@ discussions there. Django's online documentation also includes pointers on how to contribute to Django: - * :ref:`How to contribute to Django ` + * :doc:`How to contribute to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed bugfixes -- are always welcome and diff --git a/docs/releases/1.2-beta-1.txt b/docs/releases/1.2-beta-1.txt index 650971de2b46..2a12ef33bbb7 100644 --- a/docs/releases/1.2-beta-1.txt +++ b/docs/releases/1.2-beta-1.txt @@ -1,5 +1,3 @@ -.. _releases-1.2-beta-1: - =============================== Django 1.2 beta 1 release notes =============================== @@ -19,7 +17,7 @@ As such, this release is *not* intended for production use, and any such use is discouraged. This document covers changes since the Django 1.2 alpha release; the -:ref:`1.2 alpha release notes ` cover new and +:doc:`1.2 alpha release notes ` cover new and updated features in Django between 1.1 and 1.2 alpha. @@ -28,7 +26,7 @@ Deprecations and other changes in 1.2 beta This beta release deprecates two portions of public API, and introduces a potentially backwards-incompatible change to -another. Under :ref:`our API stability policy `, +another. Under :doc:`our API stability policy `, deprecation proceeds over multiple release cycles: initially, the deprecated API will raise ``PendingDeprecationWarning``, followed by raising ``DeprecationWarning`` in the next release, and finally @@ -58,8 +56,8 @@ Also, in accordance with `RSS best practices`_, RSS feeds will now include an ``atom:link`` element. You may need to update your tests to take this into account. -For more information, see the full :ref:`syndication framework -documentation `. +For more information, see the full :doc:`syndication framework +documentation `. .. _RSS best practices: http://www.rssboard.org/rss-profile @@ -93,7 +91,7 @@ added in Django 1.2 alpha but not documented with the alpha release. The default authentication backends shipped with Django do not currently make use of this, but third-party authentication backends -are free to do so. See the :ref:`authentication docs ` +are free to do so. See the :doc:`authentication docs ` for more information. @@ -106,7 +104,7 @@ class will check the backend for permissions, just as the normal ``User`` does. This is intended to help centralize permission handling; apps can always delegate the question of whether something is allowed or not to the authorization/authentication system. See the -:ref:`authentication docs ` for more details. +:doc:`authentication docs ` for more details. ``select_related()`` improvements @@ -163,7 +161,7 @@ discussions there. Django's online documentation also includes pointers on how to contribute to Django: - * :ref:`How to contribute to Django ` + * :doc:`How to contribute to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed bugfixes -- diff --git a/docs/releases/1.2-rc-1.txt b/docs/releases/1.2-rc-1.txt index c627612a6e63..b599dcca1e0e 100644 --- a/docs/releases/1.2-rc-1.txt +++ b/docs/releases/1.2-rc-1.txt @@ -1,5 +1,3 @@ -.. _releases-1.2-rc-1: - ============================= Django 1.2 RC 1 release notes ============================= @@ -21,8 +19,8 @@ such use is discouraged. Django has been feature frozen since the 1.2 beta release, so this release candidate contains no new features, only bugfixes; for a -summary of features new to Django 1.2, consult the :ref:`1.2 alpha -` and :ref:`1.2 beta ` +summary of features new to Django 1.2, consult the :doc:`1.2 alpha +` and :doc:`1.2 beta ` release notes. @@ -42,7 +40,7 @@ This change should affect only a small number of Django users, as most operating-system vendors today are shipping Python 2.4 or newer as their default version. If you're still using Python 2.3, however, you'll need to stick to Django 1.1 until you can upgrade; per -:ref:`our support policy `, Django 1.1 will +:doc:`our support policy `, Django 1.1 will continue to receive security support until the release of Django 1.3. A roadmap for Django's overall 2.x Python support, and eventual @@ -96,7 +94,7 @@ discussions there. Django's online documentation also includes pointers on how to contribute to Django: - * :ref:`How to contribute to Django ` + * :doc:`How to contribute to Django ` Contributions on any level -- developing code, writing documentation or simply triaging tickets and helping to test proposed bugfixes -- are always welcome and diff --git a/docs/releases/1.2.txt b/docs/releases/1.2.txt index 91fa84324589..f46c19b8e81c 100644 --- a/docs/releases/1.2.txt +++ b/docs/releases/1.2.txt @@ -1,5 +1,3 @@ -.. _releases-1.2: - ======================== Django 1.2 release notes ======================== @@ -55,7 +53,7 @@ be found below`_. .. _django advent: http://djangoadvent.com/ Wherever possible these features have been introduced in a backwards-compatible -manner per :ref:`our API stability policy ` policy. +manner per :doc:`our API stability policy ` policy. However, a handful of features *have* changed in ways that, for some users, will be backwards-incompatible. The big changes are: @@ -110,7 +108,7 @@ This change should affect only a small number of Django users, as most operating-system vendors today are shipping Python 2.4 or newer as their default version. If you're still using Python 2.3, however, you'll need to stick to Django 1.1 until you can upgrade; per -:ref:`our support policy `, Django 1.1 will +:doc:`our support policy `, Django 1.1 will continue to receive security support until the release of Django 1.3. A roadmap for Django's overall 2.x Python support, and eventual @@ -123,8 +121,8 @@ What's new in Django 1.2 Support for multiple databases ------------------------------ -Django 1.2 adds the ability to use :ref:`more than one database -` in your Django project. Queries can be issued at a +Django 1.2 adds the ability to use :doc:`more than one database +` in your Django project. Queries can be issued at a specific database with the `using()` method on ``QuerySet`` objects. Individual objects can be saved to a specific database by providing a ``using`` argument when you call ``save()``. @@ -134,7 +132,7 @@ Model validation Model instances now have support for :ref:`validating their own data `, and both model and form fields now accept configurable -lists of :ref:`validators ` specifying reusable, encapsulated +lists of :doc:`validators ` specifying reusable, encapsulated validation behavior. Note, however, that validation must still be performed explicitly. Simply invoking a model instance's ``save()`` method will not perform any validation of the instance's data. @@ -142,8 +140,8 @@ perform any validation of the instance's data. Improved CSRF protection ------------------------ -Django now has much improved protection against :ref:`Cross-Site Request Forgery -(CSRF) attacks`. This type of attack occurs when a malicious +Django now has much improved protection against :doc:`Cross-Site Request Forgery +(CSRF) attacks`. This type of attack occurs when a malicious Web site contains a link, a form button or some JavaScript that is intended to perform some action on your Web site, using the credentials of a logged-in user who visits the malicious site in their browser. A related type of attack, "login @@ -153,8 +151,8 @@ with someone else's credentials, is also covered. Messages framework ------------------ -Django now includes a robust and configurable :ref:`messages framework -` with built-in support for cookie- and session-based +Django now includes a robust and configurable :doc:`messages framework +` with built-in support for cookie- and session-based messaging, for both anonymous and authenticated clients. The messages framework replaces the deprecated user message API and allows you to temporarily store messages in one request and retrieve them for display in a subsequent request @@ -166,8 +164,8 @@ Object-level permissions A foundation for specifying permissions at the per-object level has been added. Although there is no implementation of this in core, a custom authentication backend can provide this implementation and it will be used by -:class:`django.contrib.auth.models.User`. See the :ref:`authentication docs -` for more information. +:class:`django.contrib.auth.models.User`. See the :doc:`authentication docs +` for more information. Permissions for anonymous users ------------------------------- @@ -176,8 +174,8 @@ If you provide a custom auth backend with ``supports_anonymous_user`` set to ``True``, AnonymousUser will check the backend for permissions, just like User already did. This is useful for centralizing permission handling - apps can always delegate the question of whether something is allowed or not to -the authorization/authentication backend. See the :ref:`authentication -docs ` for more details. +the authorization/authentication backend. See the :doc:`authentication +docs ` for more details. Relaxed requirements for usernames ---------------------------------- @@ -194,7 +192,7 @@ You can now :ref:`configure the way that Django sends e-mail can now choose a configurable e-mail backend to send messages. If your hosting provider uses a sandbox or some other non-SMTP technique for sending mail, you can now construct an e-mail backend that will allow -Django's standard :ref:`mail sending methods` to use +Django's standard :doc:`mail sending methods` to use those facilities. This also makes it easier to debug mail sending. Django ships with @@ -286,7 +284,7 @@ Models can now use a 64-bit :class:`~django.db.models.BigIntegerField` type. Improved localization --------------------- -Django's :ref:`internationalization framework ` has been expanded +Django's :doc:`internationalization framework ` has been expanded with locale-aware formatting and form processing. That means, if enabled, dates and numbers on templates will be displayed using the format specified for the current locale. Django will also use localized formats when parsing data in @@ -309,8 +307,8 @@ the colors used by ``django-admin.py`` to provide :ref:`syntax highlighting Syndication feeds as views -------------------------- -:ref:`Syndication feeds ` can now be used directly as -views in your :ref:`URLconf `. This means that you can +:doc:`Syndication feeds ` can now be used directly as +views in your :doc:`URLconf `. This means that you can maintain complete control over the URL structure of your feeds. Like any other view, feeds views are passed a ``request`` object, so you can do anything you would normally do with a view, like user based access control, or making a feed @@ -319,7 +317,7 @@ a named URL. GeoDjango --------- -The most significant new feature for :ref:`GeoDjango ` +The most significant new feature for :doc:`GeoDjango ` in 1.2 is support for multiple spatial databases. As a result, the following :ref:`spatial database backends ` are now included: @@ -357,7 +355,7 @@ set a :attr:`~django.contrib.gis.gdal.Layer.spatial_filter` on the features returned when iterating over a :class:`~django.contrib.gis.gdal.Layer`. -Finally, :ref:`GeoDjango's documentation ` is now +Finally, :doc:`GeoDjango's documentation ` is now included with Django's and is no longer hosted separately at `geodjango.org `_. @@ -391,8 +389,8 @@ Backwards-incompatible changes in 1.2 ===================================== Wherever possible the new features above have been introduced in a -backwards-compatible manner per :ref:`our API stability policy -` policy. This means that practically all existing +backwards-compatible manner per :doc:`our API stability policy +` policy. This means that practically all existing code which worked with Django 1.1 will continue to work with Django 1.2; such code will, however, begin issuing warnings (see below for details). @@ -405,7 +403,7 @@ CSRF Protection --------------- We've made large changes to the way CSRF protection works, detailed in -:ref:`the CSRF documentaton `. Here are the major changes you +:doc:`the CSRF documentaton `. Here are the major changes you should be aware of: * ``CsrfResponseMiddleware`` and ``CsrfMiddleware`` have been deprecated and @@ -742,9 +740,9 @@ be removed entirely. .. seealso:: - For more details, see the documentation :ref:`Django's release process - ` and our :ref:`deprecation timeline - `.` + For more details, see the documentation :doc:`Django's release process + ` and our :doc:`deprecation timeline + `.` .. _specifying-databases: @@ -877,7 +875,7 @@ User Messages API The API for storing messages in the user ``Message`` model (via ``user.message_set.create``) is now deprecated and will be removed in Django -1.4 according to the standard :ref:`release process `. +1.4 according to the standard :doc:`release process `. To upgrade your code, you need to replace any instances of this:: @@ -901,7 +899,7 @@ following:: ... For more information, see the full -:ref:`messages documentation `. You should begin to +:doc:`messages documentation `. You should begin to update your code to use the new API immediately. Date format helper functions @@ -965,7 +963,7 @@ Django 1.4. The new class has an almost identical API, but allows instances to be used as views. For example, consider the use of the old framework in -the following :ref:`URLconf `:: +the following :doc:`URLconf `:: from django.conf.urls.defaults import * from myproject.feeds import LatestEntries, LatestEntriesByCategory @@ -1034,8 +1032,8 @@ In accordance with `RSS best practices`_, RSS feeds will now include an ``atom:link`` element. You may need to update your tests to take this into account. -For more information, see the full :ref:`syndication framework -documentation `. +For more information, see the full :doc:`syndication framework +documentation `. .. _RSS best practices: http://www.rssboard.org/rss-profile diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 676ee67519c1..25c06e8f03ff 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -1,5 +1,3 @@ -.. _releases-index: - ============= Release notes ============= diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 480509961b0c..d8712d52c887 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1,5 +1,3 @@ -.. _topics-auth: - ============================= User authentication in Django ============================= @@ -138,8 +136,8 @@ Methods :class:`~django.contrib.auth.models.User` objects have two many-to-many fields: models.User. ``groups`` and ``user_permissions``. :class:`~django.contrib.auth.models.User` objects can access their related - objects in the same way as any other :ref:`Django model - `: + objects in the same way as any other :doc:`Django model + `: .. code-block:: python @@ -537,7 +535,7 @@ First, install the :class:`~django.contrib.sessions.middleware.SessionMiddleware` and :class:`~django.contrib.auth.middleware.AuthenticationMiddleware` middlewares by adding them to your :setting:`MIDDLEWARE_CLASSES` setting. See -the :ref:`session documentation ` for more information. +the :doc:`session documentation ` for more information. Once you have those middlewares installed, you'll be able to access :attr:`request.user ` in views. @@ -554,7 +552,7 @@ section). You can tell them apart with else: # Do something for anonymous users. -.. _howtologauserin: +.. _how-to-log-a-user-in: How to log a user in -------------------- @@ -753,7 +751,7 @@ the following line to your URLconf:: template context variables: * ``form``: A :class:`~django.forms.Form` object representing the login - form. See the :ref:`forms documentation ` for + form. See the :doc:`forms documentation ` for more on ``Form`` objects. * ``next``: The URL to redirect to after successful login. This may @@ -769,7 +767,7 @@ the following line to your URLconf:: * ``site_name``: An alias for ``site.name``. If you don't have the site framework installed, this will be set to the value of :attr:`request.META['SERVER_NAME'] `. - For more on sites, see :ref:`ref-contrib-sites`. + For more on sites, see :doc:`/ref/contrib/sites`. If you'd prefer not to call the template :file:`registration/login.html`, you can pass the ``template_name`` parameter via the extra arguments to @@ -1111,7 +1109,7 @@ The permission_required decorator Limiting access to generic views -------------------------------- -To limit access to a :ref:`generic view `, write a thin +To limit access to a :doc:`generic view `, write a thin wrapper around the view, and point your URLconf to your wrapper instead of the generic view itself. For example:: @@ -1228,13 +1226,13 @@ Methods ~~~~~~~ :class:`~django.contrib.auth.models.Permission` objects have the standard -data-access methods like any other :ref:`Django model `. +data-access methods like any other :doc:`Django model `. Authentication data in templates ================================ The currently logged-in user and his/her permissions are made available in the -:ref:`template context ` when you use +:doc:`template context ` when you use :class:`~django.template.context.RequestContext`. .. admonition:: Technicality @@ -1323,7 +1321,7 @@ Messages .. deprecated:: 1.2 This functionality will be removed in Django 1.4. You should use the - :ref:`messages framework ` for all new projects and + :doc:`messages framework ` for all new projects and begin to update your existing code immediately. The message system is a lightweight way to queue messages for given users. @@ -1358,7 +1356,7 @@ a playlist:: When you use :class:`~django.template.context.RequestContext`, the currently logged-in user and his/her messages are made available in the -:ref:`template context ` as the template variable +:doc:`template context ` as the template variable ``{{ messages }}``. Here's an example of template code that displays messages: .. code-block:: html+django @@ -1373,14 +1371,14 @@ logged-in user and his/her messages are made available in the .. versionchanged:: 1.2 The ``messages`` template variable uses a backwards compatible method in the - :ref:`messages framework ` to retrieve messages from + :doc:`messages framework ` to retrieve messages from both the user ``Message`` model and from the new framework. Unlike in previous revisions, the messages will not be erased unless they are actually displayed. Finally, note that this messages framework only works with users in the user database. To send messages to anonymous users, use the -:ref:`messages framework `. +:doc:`messages framework `. .. _authentication-backends: @@ -1401,7 +1399,7 @@ plug in other authentication sources. You can override Django's default database-based scheme, or you can use the default system in tandem with other systems. -See the :ref:`authentication backend reference ` +See the :doc:`authentication backend reference ` for information on the authentication backends included with Django. Specifying authentication backends @@ -1410,9 +1408,9 @@ Specifying authentication backends Behind the scenes, Django maintains a list of "authentication backends" that it checks for authentication. When somebody calls :func:`django.contrib.auth.authenticate()` -- as described in :ref:`How to log -a user in` above -- Django tries authenticating across all of its -authentication backends. If the first authentication method fails, Django tries -the second one, and so on, until all backends have been attempted. +a user in ` above -- Django tries authenticating across +all of its authentication backends. If the first authentication method fails, +Django tries the second one, and so on, until all backends have been attempted. The list of authentication backends to use is specified in the :setting:`AUTHENTICATION_BACKENDS` setting. This should be a tuple of Python @@ -1592,7 +1590,7 @@ object permissions will always return ``False`` or an empty list (depending on the check performed). To enable object permissions in your own -:ref:`authentication backend ` you'll just have +:doc:`authentication backend ` you'll just have to allow passing an ``obj`` parameter to the permission methods and set the ``supports_object_permissions`` class attribute to ``True``. diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 5e263aa543bc..e5ca52649476 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -1,5 +1,3 @@ -.. _topics-cache: - ======================== Django's cache framework ======================== @@ -344,7 +342,7 @@ Additionally, the cache middleware automatically sets a few headers in each * Sets the ``Cache-Control`` header to give a max age for the page -- again, from the ``CACHE_MIDDLEWARE_SECONDS`` setting. -See :ref:`topics-http-middleware` for more on middleware. +See :doc:`/topics/http/middleware` for more on middleware. .. versionadded:: 1.0 @@ -365,7 +363,7 @@ include the name of the active :term:`language`. This allows you to easily cache multilingual sites without having to create the cache key yourself. -See :ref:`topics-i18n-deployment` for more on how Django discovers the active +See :doc:`/topics/i18n/deployment` for more on how Django discovers the active language. __ `Controlling cache: Using other headers`_ diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt index 1ce3c3bb9244..b5da14082776 100644 --- a/docs/topics/conditional-view-processing.txt +++ b/docs/topics/conditional-view-processing.txt @@ -1,5 +1,3 @@ -.. _topics-conditional-processing: - =========================== Conditional View Processing =========================== diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index 41580c94b6be..eb2102103841 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -1,5 +1,3 @@ -.. _topics-db-aggregation: - =========== Aggregation =========== @@ -8,7 +6,7 @@ Aggregation .. currentmodule:: django.db.models -The topic guide on :ref:`Django's database-abstraction API ` +The topic guide on :doc:`Django's database-abstraction API ` described the way that you can use Django queries that create, retrieve, update and delete individual objects. However, sometimes you will need to retrieve values that are derived by summarizing or *aggregating* a @@ -363,7 +361,7 @@ you also select in a ``values()`` call. for you. The main reason is consistency with ``distinct()`` and other places: Django **never** removes ordering constraints that you have specified (and we can't change those other methods' behavior, as that - would violate our :ref:`misc-api-stability` policy). + would violate our :doc:`/misc/api-stability` policy). Aggregating annotations ----------------------- diff --git a/docs/topics/db/index.txt b/docs/topics/db/index.txt index 3eb62b70ca4d..c49f158e64b6 100644 --- a/docs/topics/db/index.txt +++ b/docs/topics/db/index.txt @@ -1,5 +1,3 @@ -.. _topics-db-index: - Models and databases ==================== diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt index aa47e5dd1522..5ebe0b1b947d 100644 --- a/docs/topics/db/managers.txt +++ b/docs/topics/db/managers.txt @@ -1,5 +1,3 @@ -.. _topics-db-managers: - ======== Managers ======== @@ -12,7 +10,7 @@ A ``Manager`` is the interface through which database query operations are provided to Django models. At least one ``Manager`` exists for every model in a Django application. -The way ``Manager`` classes work is documented in :ref:`topics-db-queries`; +The way ``Manager`` classes work is documented in :doc:`/topics/db/queries`; this document specifically touches on model options that customize ``Manager`` behavior. @@ -325,7 +323,7 @@ it will use :class:`django.db.models.Manager`. attribute only controlled the type of manager used for related field access, which is where the name came from. As it became clear the concept was more broadly useful, the name hasn't been changed. This is primarily - so that existing code will :ref:`continue to work ` in + so that existing code will :doc:`continue to work ` in future Django versions. Writing Correct Managers For Use In Automatic Manager Instances diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 78c578d61a7b..0ff34ea0e12c 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -1,5 +1,3 @@ -.. _topics-db-models: - ====== Models ====== @@ -18,7 +16,7 @@ The basics: * Each attribute of the model represents a database field. * With all of this, Django gives you an automatically-generated - database-access API; see :ref:`topics-db-queries`. + database-access API; see :doc:`/topics/db/queries`. .. seealso:: @@ -64,7 +62,7 @@ Some technical notes: * The ``CREATE TABLE`` SQL in this example is formatted using PostgreSQL syntax, but it's worth noting Django uses SQL tailored to the database - backend specified in your :ref:`settings file `. + backend specified in your :doc:`settings file `. Using models ============ @@ -126,7 +124,7 @@ determine a few things: Django ships with dozens of built-in field types; you can find the complete list in the :ref:`model field reference `. You can easily write your own fields if Django's built-in ones don't do the trick; see -:ref:`howto-custom-model-fields`. +:doc:`/howto/custom-model-fields`. Field options ------------- @@ -612,7 +610,7 @@ Custom field types If one of the existing model fields cannot be used to fit your purposes, or if you wish to take advantage of some less common database column types, you can create your own field class. Full coverage of creating your own fields is -provided in :ref:`howto-custom-model-fields`. +provided in :doc:`/howto/custom-model-fields`. .. _meta-options: @@ -634,8 +632,8 @@ human-readable singular and plural names (:attr:`~Options.verbose_name` and :attr:`~Options.verbose_name_plural`). None are required, and adding ``class Meta`` to a model is completely optional. -A complete list of all possible ``Meta`` options can be found in the :ref:`model -option reference `. +A complete list of all possible ``Meta`` options can be found in the :doc:`model +option reference `. .. _model-methods: @@ -684,7 +682,7 @@ properties`_. .. _Read more about properties: http://www.python.org/download/releases/2.2/descrintro/#property -The :ref:`model instance reference ` has a complete list +The :doc:`model instance reference ` has a complete list of :ref:`methods automatically given to each model `. You can override most of these -- see `overriding predefined model methods`_, below -- but there are a couple that you'll almost always want to define: @@ -763,7 +761,7 @@ Executing custom SQL Another common pattern is writing custom SQL statements in model methods and module-level methods. For more details on using raw SQL, see the documentation -on :ref:`using raw SQL`. +on :doc:`using raw SQL`. .. _model-inheritance: diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index e3f62ea71ba9..c6070504b97f 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -1,5 +1,3 @@ -.. _topics-db-multi-db: - ================== Multiple databases ================== diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index bb40139f23d6..baf8cfa26852 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -1,5 +1,3 @@ -.. _topics-db-optimization: - ============================ Database access optimization ============================ @@ -45,13 +43,13 @@ Use standard DB optimization techniques We will assume you have done the obvious things above. The rest of this document focuses on how to use Django in such a way that you are not doing unnecessary work. This document also does not address other optimization techniques that -apply to all expensive operations, such as :ref:`general purpose caching -`. +apply to all expensive operations, such as :doc:`general purpose caching +`. Understand QuerySets ==================== -Understanding :ref:`QuerySets ` is vital to getting good +Understanding :doc:`QuerySets ` is vital to getting good performance with simple code. In particular: Understand QuerySet evaluation @@ -114,7 +112,7 @@ For instance: * Use :ref:`F() object query expressions ` to do filtering against other fields within the same model. -* Use :ref:`annotate to do aggregation in the database `. +* Use :doc:`annotate to do aggregation in the database `. If these aren't enough to generate the SQL you need: @@ -128,8 +126,8 @@ explicitly added to the query. If that still isn't powerful enough: Use raw SQL ----------- -Write your own :ref:`custom SQL to retrieve data or populate models -`. Use ``django.db.connection.queries`` to find out what Django +Write your own :doc:`custom SQL to retrieve data or populate models +`. Use ``django.db.connection.queries`` to find out what Django is writing for you and start from there. Retrieve everything at once if you know you will need it @@ -148,7 +146,7 @@ Understand :ref:`QuerySet.select_related() ` thoroughly, and use * in view code, -* and in :ref:`managers and default managers ` where +* and in :doc:`managers and default managers ` where appropriate. Be aware when your manager is and is not used; sometimes this is tricky so don't make assumptions. @@ -243,7 +241,7 @@ individual, use a bulk SQL UPDATE statement, via :ref:`QuerySet.update() Note, however, that these bulk update methods cannot call the ``save()`` or ``delete()`` methods of individual instances, which means that any custom behaviour you have added for these methods will not be executed, including anything driven from the -normal database object :ref:`signals `. +normal database object :doc:`signals `. Use foreign key values directly ------------------------------- diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 981d727f4f81..5c4941fbf2fd 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -1,15 +1,13 @@ -.. _topics-db-queries: - ============== Making queries ============== .. currentmodule:: django.db.models -Once you've created your :ref:`data models `, Django +Once you've created your :doc:`data models `, Django automatically gives you a database-abstraction API that lets you create, retrieve, update and delete objects. This document explains how to use this -API. Refer to the :ref:`data model reference ` for full +API. Refer to the :doc:`data model reference ` for full details of all the various model lookup options. Throughout this guide (and in the reference), we'll refer to the following @@ -937,7 +935,7 @@ be accessed from an instance:: In addition to the ``QuerySet`` methods defined in "Retrieving objects" above, the ``ForeignKey`` ``Manager`` has additional methods used to handle the set of related objects. A synopsis of each is below, and complete details can be found -in the :ref:`related objects reference `. +in the :doc:`related objects reference `. ``add(obj1, obj2, ...)`` Adds the specified model objects to the related object set. @@ -1067,7 +1065,7 @@ Falling back to raw SQL If you find yourself needing to write an SQL query that is too complex for Django's database-mapper to handle, you can fall back on writing SQL by hand. Django has a couple of options for writing raw SQL queries; see -:ref:`topics-db-sql`. +:doc:`/topics/db/sql`. Finally, it's important to note that the Django database layer is merely an interface to your database. You can access your database via other tools, diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index c3272da7571d..9f2be7a2efb8 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -1,12 +1,10 @@ -.. _topics-db-sql: - ========================== Performing raw SQL queries ========================== .. currentmodule:: django.db.models -When the :ref:`model query APIs ` don't go far enough, you +When the :doc:`model query APIs ` don't go far enough, you can fall back to writing raw SQL. Django gives you two ways of performing raw SQL queries: you can use :meth:`Manager.raw()` to `perform raw queries and return model instances`__, or you can avoid the model layer entirely and @@ -269,7 +267,7 @@ Connections and cursors ----------------------- ``connection`` and ``cursor`` mostly implement the standard `Python DB-API`_ -(except when it comes to :ref:`transaction handling `). +(except when it comes to :doc:`transaction handling `). If you're not familiar with the Python DB-API, note that the SQL statement in ``cursor.execute()`` uses placeholders, ``"%s"``, rather than adding parameters directly within the SQL. If you use this technique, the underlying database diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index fb6e3f86462e..2d99c17a323c 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -1,5 +1,3 @@ -.. _topics-db-transactions: - ============================== Managing database transactions ============================== @@ -306,7 +304,7 @@ Database-level autocommit .. versionadded:: 1.1 With PostgreSQL 8.2 or later, there is an advanced option to run PostgreSQL -with :ref:`database-level autocommit `. If you use this option, +with :doc:`database-level autocommit `. If you use this option, there is no constantly open transaction, so it is always possible to continue after catching an exception. For example:: diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 8ea64dac1fbb..31092b0aaaef 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -1,5 +1,3 @@ -.. _topics-email: - ============== Sending e-mail ============== diff --git a/docs/topics/files.txt b/docs/topics/files.txt index 45aca5488a5c..26114cb50bda 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -1,5 +1,3 @@ -.. _topics-files: - ============== Managing files ============== @@ -70,7 +68,7 @@ using a Python built-in ``file`` object:: >>> myfile = File(f) Now you can use any of the ``File`` attributes and methods documented in -:ref:`ref-files-file`. +:doc:`/ref/files/file`. File storage ============ @@ -84,7 +82,7 @@ setting; if you don't explicitly provide a storage system, this is the one that will be used. See below for details of the built-in default file storage system, and see -:ref:`howto-custom-file-storage` for information on writing your own file +:doc:`/howto/custom-file-storage` for information on writing your own file storage system. Storage objects @@ -111,7 +109,7 @@ useful -- you can use the global default storage system:: >>> default_storage.exists(path) False -See :ref:`ref-files-storage` for the file storage API. +See :doc:`/ref/files/storage` for the file storage API. The built-in filesystem storage class ------------------------------------- @@ -143,5 +141,5 @@ For example, the following code will store uploaded files under ... photo = models.ImageField(storage=fs) -:ref:`Custom storage systems ` work the same way: you +:doc:`Custom storage systems ` work the same way: you can pass them in as the ``storage`` argument to a ``FileField``. diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 732fd93de11b..e7b09dc409b7 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -1,4 +1,3 @@ -.. _topics-forms-formsets: .. _formsets: Formsets diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index 119e943889ad..cef322a02ffd 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -1,5 +1,3 @@ -.. _topics-forms-index: - ================== Working with forms ================== @@ -7,8 +5,8 @@ Working with forms .. admonition:: About this document This document provides an introduction to Django's form handling features. - For a more detailed look at the forms API, see :ref:`ref-forms-api`. For - documentation of the available field types, see :ref:`ref-forms-fields`. + For a more detailed look at the forms API, see :doc:`/ref/forms/api`. For + documentation of the available field types, see :doc:`/ref/forms/fields`. .. highlightlang:: html+django @@ -77,10 +75,10 @@ personal Web site: A form is composed of ``Field`` objects. In this case, our form has four fields: ``subject``, ``message``, ``sender`` and ``cc_myself``. ``CharField``, ``EmailField`` and ``BooleanField`` are just three of the available field types; -a full list can be found in :ref:`ref-forms-fields`. +a full list can be found in :doc:`/ref/forms/fields`. If your form is going to be used to directly add or edit a Django model, you can -use a :ref:`ModelForm ` to avoid duplicating your model +use a :doc:`ModelForm ` to avoid duplicating your model description. Using a form in a view @@ -163,7 +161,7 @@ Extending the above example, here's how the form data could be processed: send_mail(subject, message, sender, recipients) return HttpResponseRedirect('/thanks/') # Redirect after POST -For more on sending e-mail from Django, see :ref:`topics-email`. +For more on sending e-mail from Django, see :doc:`/topics/email`. Displaying a form using a template ---------------------------------- @@ -397,4 +395,4 @@ This covers the basics, but forms can do a whole lot more: .. seealso:: - The :ref:`form API reference `. + The :doc:`form API reference `. diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt index 663b0d311370..64cf50743dd4 100644 --- a/docs/topics/forms/media.txt +++ b/docs/topics/forms/media.txt @@ -1,5 +1,3 @@ -.. _topics-forms-media: - Form Media ========== diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 02cce34fbc03..2cdd2bfa7471 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -1,5 +1,3 @@ -.. _topics-forms-modelforms: - ========================== Creating forms from models ========================== @@ -446,7 +444,7 @@ parameter when declaring the form field:: class Meta: model = Article - See the :ref:`form field documentation ` for more information + See the :doc:`form field documentation ` for more information on fields and their arguments. Changing the order of fields @@ -540,7 +538,7 @@ Interaction with model validation As part of its validation process, ``ModelForm`` will call the ``clean()`` method of each field on your model that has a corresponding field on your form. If you have excluded any model fields, validation will not be run on those -fields. See the :ref:`form validation ` documentation +fields. See the :doc:`form validation ` documentation for more on how field cleaning and validation work. Also, your model's ``clean()`` method will be called before any uniqueness checks are made. See :ref:`Validating objects ` for more information on the @@ -551,7 +549,7 @@ model's ``clean()`` hook. Model formsets ============== -Like :ref:`regular formsets `, Django provides a couple +Like :doc:`regular formsets `, Django provides a couple of enhanced formset classes that make it easy to work with Django models. Let's reuse the ``Author`` model from above:: diff --git a/docs/topics/generic-views.txt b/docs/topics/generic-views.txt index f70bb43f3855..f90745d45138 100644 --- a/docs/topics/generic-views.txt +++ b/docs/topics/generic-views.txt @@ -1,5 +1,3 @@ -.. _topics-generic-views: - ============= Generic views ============= @@ -192,7 +190,7 @@ might look like the following:: That's really all there is to it. All the cool features of generic views come from changing the "info" dictionary passed to the generic view. The -:ref:`generic views reference` documents all the generic +:doc:`generic views reference` documents all the generic views and all their options in detail; the rest of this document will consider some of the common ways you might customize and extend generic views. @@ -315,9 +313,9 @@ Viewing subsets of objects Now let's take a closer look at this ``queryset`` key we've been using all along. Most generic views take one of these ``queryset`` arguments -- it's how -the view knows which set of objects to display (see :ref:`topics-db-queries` for +the view knows which set of objects to display (see :doc:`/topics/db/queries` for more information about ``QuerySet`` objects, and see the -:ref:`generic views reference` for the complete details). +:doc:`generic views reference` for the complete details). To pick a simple example, we might want to order a list of books by publication date, with the most recent first: @@ -365,7 +363,7 @@ We'll deal with this problem in the next section. If you get a 404 when requesting ``/books/acme/``, check to ensure you actually have a Publisher with the name 'ACME Publishing'. Generic views have an ``allow_empty`` parameter for this case. See the - :ref:`generic views reference` for more details. + :doc:`generic views reference` for more details. Complex filtering with wrapper functions ---------------------------------------- diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt index ab8277599cc6..6b0a4d57228d 100644 --- a/docs/topics/http/file-uploads.txt +++ b/docs/topics/http/file-uploads.txt @@ -1,5 +1,3 @@ -.. _topics-http-file-uploads: - ============ File Uploads ============ @@ -10,8 +8,8 @@ File Uploads When Django handles a file upload, the file data ends up placed in :attr:`request.FILES ` (for more on the -``request`` object see the documentation for :ref:`request and response objects -`). This document explains how files are stored on disk +``request`` object see the documentation for :doc:`request and response objects +`). This document explains how files are stored on disk and in memory, and how to customize the default behavior. Basic file uploads diff --git a/docs/topics/http/generic-views.txt b/docs/topics/http/generic-views.txt index 5aa2c48ea57a..15f895ea787c 100644 --- a/docs/topics/http/generic-views.txt +++ b/docs/topics/http/generic-views.txt @@ -1,7 +1,5 @@ -.. _topics-http-generic-views: - ============= Generic views ============= -See :ref:`ref-generic-views`. \ No newline at end of file +See :doc:`/ref/generic-views`. diff --git a/docs/topics/http/index.txt b/docs/topics/http/index.txt index ae73c2c29b3e..5ef776dd7ba1 100644 --- a/docs/topics/http/index.txt +++ b/docs/topics/http/index.txt @@ -1,5 +1,3 @@ -.. _topics-http-index: - Handling HTTP requests ====================== diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index 215a4ec12c91..eee398a3dc9f 100644 --- a/docs/topics/http/middleware.txt +++ b/docs/topics/http/middleware.txt @@ -1,5 +1,3 @@ -.. _topics-http-middleware: - ========== Middleware ========== @@ -14,8 +12,8 @@ an ``"X-View"`` HTTP header to every response to a ``HEAD`` request. 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 :ref:`built-in -middleware reference `. +you can use right out of the box; they're documented in the :doc:`built-in +middleware reference `. Activating middleware ===================== @@ -173,9 +171,9 @@ Guidelines cares about is that the :setting:`MIDDLEWARE_CLASSES` setting includes the path to it. - * Feel free to look at :ref:`Django's available middleware - ` for examples. + * Feel free to look at :doc:`Django's available middleware + ` for examples. * If you write a middleware component that you think would be useful to - other people, contribute to the community! :ref:`Let us know - `, and we'll consider adding it to Django. + other people, contribute to the community! :doc:`Let us know + `, and we'll consider adding it to Django. diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 68ead03b6503..8a0f0d4b7259 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -1,5 +1,3 @@ -.. _topics-http-sessions: - =================== How to use sessions =================== @@ -15,7 +13,7 @@ Cookies contain a session ID -- not the data itself. Enabling sessions ================= -Sessions are implemented via a piece of :ref:`middleware `. +Sessions are implemented via a piece of :doc:`middleware `. To enable session functionality, do the following: @@ -56,8 +54,8 @@ For better performance, you may want to use a cache-based session backend. Django 1.0 did not include the ``cached_db`` session backend. To store session data using Django's cache system, you'll first need to make -sure you've configured your cache; see the :ref:`cache documentation -` for details. +sure you've configured your cache; see the :doc:`cache documentation +` for details. .. warning:: @@ -412,7 +410,7 @@ in the past -- but your application may have different requirements. Settings ======== -A few :ref:`Django settings ` give you control over session behavior: +A few :doc:`Django settings ` give you control over session behavior: SESSION_ENGINE -------------- diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 551061335525..6bd30589412d 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -1,5 +1,3 @@ -.. _topics-http-shortcuts: - ========================= Django shortcut functions ========================= diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 5a2980f9d256..1a152ade5d76 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -1,5 +1,3 @@ -.. _topics-http-urls: - ============== URL dispatcher ============== @@ -335,7 +333,7 @@ The view prefix You can specify a common prefix in your ``patterns()`` call, to cut down on code duplication. -Here's the example URLconf from the :ref:`Django overview `:: +Here's the example URLconf from the :doc:`Django overview `:: from django.conf.urls.defaults import * @@ -537,8 +535,8 @@ In this example, for a request to ``/blog/2005/``, Django will call the year='2005', foo='bar' -This technique is used in :ref:`generic views ` and in the -:ref:`syndication framework ` to pass metadata and +This technique is used in :doc:`generic views ` and in the +:doc:`syndication framework ` to pass metadata and options to views. .. admonition:: Dealing with conflicts diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt index 7d8743fe0664..399e6b6ad14d 100644 --- a/docs/topics/http/views.txt +++ b/docs/topics/http/views.txt @@ -1,5 +1,3 @@ -.. _topics-http-views: - ============= Writing Views ============= @@ -59,7 +57,7 @@ Mapping URLs to Views So, to recap, this view function returns an HTML page that includes the current date and time. To display this view at a particular URL, you'll need to create a -*URLconf*; see :ref:`topics-http-urls` for instructions. +*URLconf*; see :doc:`/topics/http/urls` for instructions. Returning errors ================ diff --git a/docs/topics/i18n/deployment.txt b/docs/topics/i18n/deployment.txt index 1a4f5fa4d5c3..f06227e0f699 100644 --- a/docs/topics/i18n/deployment.txt +++ b/docs/topics/i18n/deployment.txt @@ -1,5 +1,3 @@ -.. _topics-i18n-deployment: - ========================== Deployment of translations ========================== @@ -72,8 +70,8 @@ For example, your ``MIDDLEWARE_CLASSES`` might look like this:: 'django.middleware.common.CommonMiddleware', ) -(For more on middleware, see the :ref:`middleware documentation -`.) +(For more on middleware, see the :doc:`middleware documentation +`.) ``LocaleMiddleware`` tries to determine the user's language preference by following this algorithm: diff --git a/docs/topics/i18n/index.txt b/docs/topics/i18n/index.txt index e39a75067a36..9c2519250418 100644 --- a/docs/topics/i18n/index.txt +++ b/docs/topics/i18n/index.txt @@ -1,5 +1,3 @@ -.. _topics-i18n: - ===================================== Internationalization and localization ===================================== @@ -23,10 +21,10 @@ associated with each of these tasks (although it's perfectly normal if you find yourself performing more than one of these roles): * For application authors wishing to make sure their Django apps can be - used in different locales: :ref:`topics-i18n-internationalization`. - * For translators wanting to translate Django apps: :ref:`topics-i18n-localization`. + used in different locales: :doc:`/topics/i18n/internationalization`. + * For translators wanting to translate Django apps: :doc:`/topics/i18n/localization`. * For system administrators/final users setting up internationalized apps or - developers integrating third party apps: :ref:`topics-i18n-deployment`. + developers integrating third party apps: :doc:`/topics/i18n/deployment`. .. toctree:: :hidden: diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 35e76c3d62ac..879c7739fbf0 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -1,5 +1,3 @@ -.. _topics-i18n-internationalization: - ==================== Internationalization ==================== @@ -242,7 +240,7 @@ If you don't like the verbose name ``ugettext_lazy``, you can just alias it as class MyThing(models.Model): name = models.CharField(help_text=_('This is the help text')) -Always use lazy translations in :ref:`Django models `. +Always use lazy translations in :doc:`Django models `. Field names and table names should be marked for translation (otherwise, they won't be translated in the admin interface). This means writing explicit ``verbose_name`` and ``verbose_name_plural`` options in the ``Meta`` class, @@ -332,7 +330,7 @@ Specifying translation strings: In template code .. highlightlang:: html+django -Translations in :ref:`Django templates ` uses two template +Translations in :doc:`Django templates ` uses two template tags and a slightly different syntax than in Python code. To give your template access to these tags, put ``{% load i18n %}`` toward the top of your template. diff --git a/docs/topics/i18n/localization.txt b/docs/topics/i18n/localization.txt index ff8715571ad2..8ba1e1ecdc90 100644 --- a/docs/topics/i18n/localization.txt +++ b/docs/topics/i18n/localization.txt @@ -1,5 +1,3 @@ -.. _topics-i18n-localization: - ============ Localization ============ @@ -12,7 +10,7 @@ files`_ and `locale aware date, time and numbers input/output in forms`_ .. seealso:: - The :ref:`howto-i18n` document included with the Django HOW-TO documents collection. + The :doc:`/howto/i18n` document included with the Django HOW-TO documents collection. .. _how-to-create-language-files: diff --git a/docs/topics/index.txt b/docs/topics/index.txt index 33f425a03ea3..4c6b7fc6854a 100644 --- a/docs/topics/index.txt +++ b/docs/topics/index.txt @@ -1,5 +1,3 @@ -.. _topics-index: - Using Django ============ diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 2147a989310c..3114517f62b2 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -1,5 +1,3 @@ -.. _topics-install: - ===================== How to install Django ===================== @@ -13,7 +11,7 @@ Being a Python Web framework, Django requires Python. It works with any Python version from 2.4 to 2.7 (due to backwards incompatibilities in Python 3.0, Django does not currently work with -Python 3.0; see :ref:`the Django FAQ ` for more +Python 3.0; see :doc:`the Django FAQ ` for more information on supported Python versions and the 3.0 transition). Get Python at http://www.python.org. If you're running Linux or Mac OS X, you @@ -22,7 +20,7 @@ probably already have it installed. .. admonition:: Django on Jython If you use Jython_ (a Python implementation for the Java platform), you'll - need to follow a few additional steps. See :ref:`howto-jython` for details. + need to follow a few additional steps. See :doc:`/howto/jython` for details. .. _jython: http://jython.org/ @@ -41,12 +39,12 @@ other server arrangements. Make sure you have Apache installed, with the mod_wsgi module activated. Django will work with any version of Apache that supports mod_wsgi. -See :ref:`How to use Django with mod_wsgi ` for +See :doc:`How to use Django with mod_wsgi ` for information on how to configure mod_wsgi once you have it installed. If you can't use mod_wsgi for some reason, fear not: Django supports many other -deployment options. A great second choice is :ref:`mod_python -`, the predecessor to mod_wsgi. Additionally, Django +deployment options. A great second choice is :doc:`mod_python +`, the predecessor to mod_wsgi. Additionally, Django follows the WSGI_ spec, which allows it to run on a variety of server platforms. See the `server-arrangements wiki page`_ for specific installation instructions for each platform. @@ -90,8 +88,8 @@ database bindings are installed. If you're on Windows, check out the unofficial `compiled Windows version`_. * If you're using MySQL, you'll need MySQLdb_, version 1.2.1p2 or higher. You - will also want to read the database-specific notes for the :ref:`MySQL - backend `. + will also want to read the database-specific notes for the :doc:`MySQL + backend `. * If you're using SQLite and Python 2.4, you'll need pysqlite_. Use version 2.0.3 or higher. Python 2.5 ships with an SQLite wrapper in the standard @@ -115,7 +113,7 @@ can simply grant Django ``SELECT``, ``INSERT``, ``UPDATE`` and ``ALTER TABLE`` privileges during ``syncdb`` but won't issue ``ALTER TABLE`` statements on a table once ``syncdb`` has created it. -If you're using Django's :ref:`testing framework` to test database queries, +If you're using Django's :doc:`testing framework` to test database queries, Django will need permission to create a test database. .. _PostgreSQL: http://www.postgresql.org/ @@ -177,7 +175,7 @@ It's easy, no matter which way you choose. Installing a distribution-specific package ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Check the :ref:`distribution specific notes ` to see if your +Check the :doc:`distribution specific notes ` to see if your platform/distribution provides official Django packages/installers. Distribution-provided packages will typically allow for automatic installation of dependencies and easy upgrade paths. @@ -265,7 +263,7 @@ latest bug fixes and improvements, follow these instructions: Apache configuration file. More information about deployment is available, of course, in our - :ref:`How to use Django with mod_python ` + :doc:`How to use Django with mod_python ` documentation. 4. On Unix-like systems, create a symbolic link to the file diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt index 70f087bd84bc..ee8a43379a0f 100644 --- a/docs/topics/pagination.txt +++ b/docs/topics/pagination.txt @@ -1,5 +1,3 @@ -.. _topics-pagination: - ========== Pagination ========== diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index ef799be6db4e..c8acc8539ec2 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -1,5 +1,3 @@ -.. _topics-serialization: - ========================== Serializing Django objects ========================== diff --git a/docs/topics/settings.txt b/docs/topics/settings.txt index 9e6a6895881d..59c02c302155 100644 --- a/docs/topics/settings.txt +++ b/docs/topics/settings.txt @@ -1,5 +1,3 @@ -.. _topics-settings: - =============== Django settings =============== @@ -46,7 +44,7 @@ Python `import search path`_. The django-admin.py utility --------------------------- -When using :ref:`django-admin.py `, you can either set the +When using :doc:`django-admin.py `, you can either set the environment variable once, or explicitly pass in the settings module each time you run the utility. @@ -78,7 +76,7 @@ settings file to use. Do that with ``SetEnv``:: SetEnv DJANGO_SETTINGS_MODULE mysite.settings -Read the :ref:`Django mod_python documentation ` for +Read the :doc:`Django mod_python documentation ` for more information. Default settings @@ -151,7 +149,7 @@ read it. This is especially important in a shared-hosting environment. Available settings ================== -For a full list of available settings, see the :ref:`settings reference `. +For a full list of available settings, see the :doc:`settings reference `. Creating your own settings ========================== diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index eb1a5fd82579..35dc3f4c0910 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -1,5 +1,3 @@ -.. _topics-signals: - ======= Signals ======= @@ -13,7 +11,7 @@ signals allow certain *senders* to notify a set of *receivers* that some action has taken place. They're especially useful when many pieces of code may be interested in the same events. -Django provides a :ref:`set of built-in signals ` that let user +Django provides a :doc:`set of built-in signals ` that let user code get notified by Django itself of certain actions. These include some useful notifications: @@ -38,7 +36,7 @@ notifications: Sent when Django starts or finishes an HTTP request. -See the :ref:`built-in signal documentation ` for a complete list, +See the :doc:`built-in signal documentation ` for a complete list, and a complete explanation of each signal. You can also `define and send your own custom signals`_; see below. @@ -128,7 +126,7 @@ The ``my_handler`` function will only be called when an instance of ``MyModel`` is saved. Different signals use different objects as their senders; you'll need to consult -the :ref:`built-in signal documentation ` for details of each +the :doc:`built-in signal documentation ` for details of each particular signal. Defining and sending signals diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index 9fa6c44dc99a..5586ed8c1246 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -1,5 +1,3 @@ -.. _topics-templates: - ============================ The Django template language ============================ @@ -8,7 +6,7 @@ The Django template language This document explains the language syntax of the Django template system. If you're looking for a more technical perspective on how it works and how to - extend it, see :ref:`ref-templates-api`. + extend it, see :doc:`/ref/templates/api`. Django's template language is designed to strike a balance between power and ease. It's designed to feel comfortable to those used to working with HTML. If @@ -28,8 +26,8 @@ or CheetahTemplate_, you should feel right at home with Django's templates. tag for looping, etc. -- but these are not simply executed as the corresponding Python code, and the template system will not execute arbitrary Python expressions. Only the tags, filters and syntax listed below - are supported by default (although you can add :ref:`your own extensions - ` to the template language as needed). + are supported by default (although you can add :doc:`your own extensions + ` to the template language as needed). .. _`The Django template language: For Python programmers`: ../templates_python/ .. _Smarty: http://smarty.php.net/ @@ -140,7 +138,7 @@ template filters: If ``value`` isn't provided or is empty, the above will display "``nothing``". - + :tfilter:`length` Returns the length of the value. This works for both strings and lists; for example:: @@ -148,7 +146,7 @@ template filters: {{ value|length }} If ``value`` is ``['a', 'b', 'c', 'd']``, the output will be ``4``. - + :tfilter:`striptags` Strips all [X]HTML tags. For example:: @@ -161,7 +159,7 @@ Again, these are just a few examples; see the :ref:`built-in filter reference ` for the complete list. You can also create your own custom template filters; see -:ref:`howto-custom-template-tags`. +:doc:`/howto/custom-template-tags`. Tags ==== @@ -217,7 +215,7 @@ Again, the above is only a selection of the whole list; see the :ref:`built-in tag reference ` for the complete list. You can also create your own custom template tags; see -:ref:`howto-custom-template-tags`. +:doc:`/howto/custom-template-tags`. Comments ======== @@ -634,8 +632,8 @@ The ``{% load %}`` tag can take multiple library names, separated by spaces. Example:: {% load comments i18n %} - -See :ref:`howto-custom-template-tags` for information on writing your own custom + +See :doc:`/howto/custom-template-tags` for information on writing your own custom template libraries. Custom libraries and template inheritance diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 9b23d546dfce..58b460810795 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1,5 +1,3 @@ -.. _topics-testing: - =========================== Testing Django applications =========================== @@ -342,7 +340,7 @@ For fine-grained control over the character encoding of your test database, use the :setting:`TEST_CHARSET` option. If you're using MySQL, you can also use the :setting:`TEST_COLLATION` option to control the particular collation used by the test database. See the -:ref:`settings documentation ` for details of these +:doc:`settings documentation ` for details of these advanced settings. .. _topics-testing-masterslave: @@ -751,7 +749,7 @@ arguments at time of construction: .. versionadded:: 1.0 - If your site uses Django's :ref:`authentication system` + If your site uses Django's :doc:`authentication system` and you deal with logging in users, you can use the test client's ``login()`` method to simulate the effect of a user logging into the site. @@ -797,7 +795,7 @@ arguments at time of construction: .. versionadded:: 1.0 - If your site uses Django's :ref:`authentication system`, + If your site uses Django's :doc:`authentication system`, the ``logout()`` method can be used to simulate the effect of a user logging out of your site. @@ -904,7 +902,7 @@ can access these properties as part of a test condition. .. attribute:: Client.session A dictionary-like object containing session information. See the - :ref:`session documentation` for full details. + :doc:`session documentation` for full details. .. _Cookie module documentation: http://docs.python.org/library/cookie.html @@ -1268,8 +1266,8 @@ E-mail services .. versionadded:: 1.0 -If any of your Django views send e-mail using :ref:`Django's e-mail -functionality `, you probably don't want to send e-mail each time +If any of your Django views send e-mail using :doc:`Django's e-mail +functionality `, you probably don't want to send e-mail each time you run a test using that view. For this reason, Django's test runner automatically redirects all Django-sent e-mail to a dummy outbox. This lets you test every aspect of sending e-mail -- from the number of messages sent to the From 1d291ff733587b96299ec2874fb2edb90fbfc11d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 20 Aug 2010 07:15:16 +0000 Subject: [PATCH 075/902] [1.2.X] Clarified some markup in the discussion of fixture loading in testcases. Backport of r13610 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13611 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 58b460810795..8efa29e69aaf 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1050,23 +1050,25 @@ A fixture is a collection of data that Django knows how to import into a database. For example, if your site has user accounts, you might set up a fixture of fake user accounts in order to populate your database during tests. -The most straightforward way of creating a fixture is to use the ``manage.py -dumpdata`` command. This assumes you already have some data in your database. -See the :djadmin:`dumpdata documentation` for more details. +The most straightforward way of creating a fixture is to use the +:djadmin:`manage.py dumpdata ` command. This assumes you +already have some data in your database. See the :djadmin:`dumpdata +documentation` for more details. .. note:: - If you've ever run ``manage.py syncdb``, you've already used a fixture - without even knowing it! When you call ``syncdb`` in the database for - the first time, Django installs a fixture called ``initial_data``. - This gives you a way of populating a new database with any initial data, - such as a default set of categories. + If you've ever run :djadmin:`manage.py syncdb`, you've + already used a fixture without even knowing it! When you call + :djadmin:`syncdb` in the database for the first time, Django + installs a fixture called ``initial_data``. This gives you a way + of populating a new database with any initial data, such as a + default set of categories. - Fixtures with other names can always be installed manually using the - ``manage.py loaddata`` command. + Fixtures with other names can always be installed manually using + the :djadmin:`manage.py loaddata` command. Once you've created a fixture and placed it in a ``fixtures`` directory in one of your :setting:`INSTALLED_APPS`, you can use it in your unit tests by -specifying a ``fixtures`` class attribute on your ``django.test.TestCase`` +specifying a ``fixtures`` class attribute on your :class:`django.test.TestCase` subclass:: from django.test import TestCase From 3fc9d7e3907f0f8506601ff34add9215fdc69b45 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 20 Aug 2010 14:07:02 +0000 Subject: [PATCH 076/902] [1.2.X] Fixed #14068 -- Corrected error handling in loaddata when an allow_syncdb method is defined on the router. Thanks to Xavier Ordoquy for the report. Backport of r13612 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13613 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/commands/loaddata.py | 27 ++++++++++++------ .../multiple_database/fixtures/pets.json | 18 ++++++++++++ .../multiple_database/tests.py | 28 +++++++++++++++++++ 3 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 tests/regressiontests/multiple_database/fixtures/pets.json diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index caf3b11b85a9..2bbd9c57b01a 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -47,7 +47,8 @@ def handle(self, *fixture_labels, **options): # Keep a count of the installed objects and fixtures fixture_count = 0 - object_count = 0 + loaded_object_count = 0 + fixture_object_count = 0 models = set() humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' @@ -114,7 +115,7 @@ def read(self): if verbosity > 1: self.stdout.write("Loading '%s' fixtures...\n" % fixture_name) else: - sys.stderr.write( + self.stderr.write( self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format.\n" % (fixture_name, format))) transaction.rollback(using=using) @@ -157,17 +158,20 @@ def read(self): else: fixture_count += 1 objects_in_fixture = 0 + loaded_objects_in_fixture = 0 if verbosity > 0: self.stdout.write("Installing %s fixture '%s' from %s.\n" % \ (format, fixture_name, humanize(fixture_dir))) try: objects = serializers.deserialize(format, fixture, using=using) for obj in objects: + objects_in_fixture += 1 if router.allow_syncdb(using, obj.object.__class__): - objects_in_fixture += 1 + loaded_objects_in_fixture += 1 models.add(obj.object.__class__) obj.save(using=using) - object_count += objects_in_fixture + loaded_object_count += loaded_objects_in_fixture + fixture_object_count += objects_in_fixture label_found = True except (SystemExit, KeyboardInterrupt): raise @@ -179,7 +183,7 @@ def read(self): if show_traceback: traceback.print_exc() else: - sys.stderr.write( + self.stderr.write( self.style.ERROR("Problem installing fixture '%s': %s\n" % (full_path, ''.join(traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback))))) @@ -189,7 +193,7 @@ def read(self): # If the fixture we loaded contains 0 objects, assume that an # error was encountered during fixture loading. if objects_in_fixture == 0: - sys.stderr.write( + self.stderr.write( self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)\n" % (fixture_name))) transaction.rollback(using=using) @@ -203,7 +207,7 @@ def read(self): # If we found even one object in a fixture, we need to reset the # database sequences. - if object_count > 0: + if loaded_object_count > 0: sequence_sql = connection.ops.sequence_reset_sql(self.style, models) if sequence_sql: if verbosity > 1: @@ -215,12 +219,17 @@ def read(self): transaction.commit(using=using) transaction.leave_transaction_management(using=using) - if object_count == 0: + if fixture_object_count == 0: if verbosity > 0: self.stdout.write("No fixtures found.\n") else: if verbosity > 0: - self.stdout.write("Installed %d object(s) from %d fixture(s)\n" % (object_count, fixture_count)) + if fixture_object_count == loaded_object_count: + self.stdout.write("Installed %d object(s) from %d fixture(s)\n" % ( + loaded_object_count, fixture_count)) + else: + self.stdout.write("Installed %d object(s) (of %d) from %d fixture(s)\n" % ( + loaded_object_count, fixture_object_count, fixture_count)) # Close the DB connection. This is required as a workaround for an # edge case in MySQL: if the same connection is used to diff --git a/tests/regressiontests/multiple_database/fixtures/pets.json b/tests/regressiontests/multiple_database/fixtures/pets.json new file mode 100644 index 000000000000..89756a3e5bc6 --- /dev/null +++ b/tests/regressiontests/multiple_database/fixtures/pets.json @@ -0,0 +1,18 @@ +[ + { + "pk": 1, + "model": "multiple_database.pet", + "fields": { + "name": "Mr Bigglesworth", + "owner": 1 + } + }, + { + "pk": 2, + "model": "multiple_database.pet", + "fields": { + "name": "Spot", + "owner": 2 + } + } +] \ No newline at end of file diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py index 6675fdcc6cd9..2547e1eae2d5 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/regressiontests/multiple_database/tests.py @@ -1569,11 +1569,31 @@ def test_user_profiles(self): self.assertEquals(alice.get_profile().flavor, 'chocolate') self.assertEquals(bob.get_profile().flavor, 'crunchy frog') +class AntiPetRouter(object): + # A router that only expresses an opinion on syncdb, + # passing pets to the 'other' database + + def allow_syncdb(self, db, model): + "Make sure the auth app only appears on the 'other' db" + if db == 'other': + return model._meta.object_name == 'Pet' + else: + return model._meta.object_name != 'Pet' + return None class FixtureTestCase(TestCase): multi_db = True fixtures = ['multidb-common', 'multidb'] + def setUp(self): + # Install the anti-pet router + self.old_routers = router.routers + router.routers = [AntiPetRouter()] + + def tearDown(self): + # Restore the 'other' database as an independent database + router.routers = self.old_routers + def test_fixture_loading(self): "Multi-db fixtures are loaded correctly" # Check that "Pro Django" exists on the default database, but not on other database @@ -1611,6 +1631,14 @@ def test_fixture_loading(self): except Book.DoesNotExist: self.fail('"The Definitive Guide to Django" should exist on both databases') + def test_pseudo_empty_fixtures(self): + "A fixture can contain entries, but lead to nothing in the database; this shouldn't raise an error (ref #14068)" + new_io = StringIO() + management.call_command('loaddata', 'pets', stdout=new_io, stderr=new_io) + command_output = new_io.getvalue().strip() + # No objects will actually be loaded + self.assertTrue("Installed 0 object(s) (of 2) from 1 fixture(s)" in command_output) + class PickleQuerySetTestCase(TestCase): multi_db = True From c3b13d8f03a70cb9f72dad274ac8e3026bb9aa6f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 20 Aug 2010 14:29:57 +0000 Subject: [PATCH 077/902] [1.2.X] Fixed #13895 -- Refactored aggregation_regress doctests. Thanks to Alex Gaynor for the patch. Backport of r13614 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13615 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/__init__.py | 1 + django/test/utils.py | 19 +- tests/modeltests/aggregation/tests.py | 15 +- .../aggregation_regress/models.py | 323 +-------- .../aggregation_regress/tests.py | 614 +++++++++++++++++- 5 files changed, 639 insertions(+), 333 deletions(-) diff --git a/django/test/__init__.py b/django/test/__init__.py index 957b293e12d8..c996ed49d632 100644 --- a/django/test/__init__.py +++ b/django/test/__init__.py @@ -4,3 +4,4 @@ from django.test.client import Client from django.test.testcases import TestCase, TransactionTestCase +from django.test.utils import Approximate diff --git a/django/test/utils.py b/django/test/utils.py index b6ab39901b4f..f38c60fd5077 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -1,4 +1,6 @@ -import sys, time, os +import sys +import time +import os from django.conf import settings from django.core import mail from django.core.mail.backends import locmem @@ -6,6 +8,21 @@ from django.template import Template from django.utils.translation import deactivate + +class Approximate(object): + def __init__(self, val, places=7): + self.val = val + self.places = places + + def __repr__(self): + return repr(self.val) + + def __eq__(self, other): + if self.val == other: + return True + return round(abs(self.val-other), self.places) == 0 + + class ContextList(list): """A wrapper that provides direct key access to context items contained in a list of context objects. diff --git a/tests/modeltests/aggregation/tests.py b/tests/modeltests/aggregation/tests.py index cb611439b9e4..c830368b9dfb 100644 --- a/tests/modeltests/aggregation/tests.py +++ b/tests/modeltests/aggregation/tests.py @@ -2,24 +2,11 @@ from decimal import Decimal from django.db.models import Avg, Sum, Count, Max, Min -from django.test import TestCase +from django.test import TestCase, Approximate from models import Author, Publisher, Book, Store -class Approximate(object): - def __init__(self, val, places=7): - self.val = val - self.places = places - - def __repr__(self): - return repr(self.val) - - def __eq__(self, other): - if self.val == other: - return True - return round(abs(self.val-other), self.places) == 0 - class BaseAggregateTestCase(TestCase): fixtures = ["initial_data.json"] diff --git a/tests/regressiontests/aggregation_regress/models.py b/tests/regressiontests/aggregation_regress/models.py index ba743575346c..783c21956a38 100644 --- a/tests/regressiontests/aggregation_regress/models.py +++ b/tests/regressiontests/aggregation_regress/models.py @@ -4,6 +4,7 @@ from django.db import connection, models, DEFAULT_DB_ALIAS from django.conf import settings + class Author(models.Model): name = models.CharField(max_length=100) age = models.IntegerField() @@ -12,6 +13,7 @@ class Author(models.Model): def __unicode__(self): return self.name + class Publisher(models.Model): name = models.CharField(max_length=255) num_awards = models.IntegerField() @@ -19,6 +21,7 @@ class Publisher(models.Model): def __unicode__(self): return self.name + class Book(models.Model): isbn = models.CharField(max_length=9) name = models.CharField(max_length=255) @@ -36,6 +39,7 @@ class Meta: def __unicode__(self): return self.name + class Store(models.Model): name = models.CharField(max_length=255) books = models.ManyToManyField(Book) @@ -45,334 +49,21 @@ class Store(models.Model): def __unicode__(self): return self.name + class Entries(models.Model): EntryID = models.AutoField(primary_key=True, db_column='Entry ID') Entry = models.CharField(unique=True, max_length=50) Exclude = models.BooleanField() + class Clues(models.Model): ID = models.AutoField(primary_key=True) EntryID = models.ForeignKey(Entries, verbose_name='Entry', db_column = 'Entry ID') Clue = models.CharField(max_length=150) + class HardbackBook(Book): weight = models.FloatField() def __unicode__(self): return "%s (hardback): %s" % (self.name, self.weight) - -__test__ = {'API_TESTS': """ ->>> from django.core import management ->>> from django.db.models import get_app, F - -# Reset the database representation of this app. -# This will return the database to a clean initial state. ->>> management.call_command('flush', verbosity=0, interactive=False) - ->>> from django.db.models import Avg, Sum, Count, Max, Min, StdDev, Variance - -# Ordering requests are ignored ->>> Author.objects.all().order_by('name').aggregate(Avg('age')) -{'age__avg': 37.4...} - -# Implicit ordering is also ignored ->>> Book.objects.all().aggregate(Sum('pages')) -{'pages__sum': 3703} - -# Baseline results ->>> Book.objects.all().aggregate(Sum('pages'), Avg('pages')) -{'pages__sum': 3703, 'pages__avg': 617.1...} - -# Empty values query doesn't affect grouping or results ->>> Book.objects.all().values().aggregate(Sum('pages'), Avg('pages')) -{'pages__sum': 3703, 'pages__avg': 617.1...} - -# Aggregate overrides extra selected column ->>> Book.objects.all().extra(select={'price_per_page' : 'price / pages'}).aggregate(Sum('pages')) -{'pages__sum': 3703} - -# Annotations get combined with extra select clauses ->>> sorted((k,v) for k,v in Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).__dict__.items() if k != '_state') -[('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)] - -# Order of the annotate/extra in the query doesn't matter ->>> sorted((k,v) for k,v in Book.objects.all().extra(select={'manufacture_cost' : 'price * .5'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=2).__dict__.items()if k != '_state') -[('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)] - -# Values queries can be combined with annotate and extra ->>> sorted((k,v) for k,v in Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2).items()if k != '_state') -[('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)] - -# The order of the (empty) values, annotate and extra clauses doesn't matter ->>> sorted((k,v) for k,v in Book.objects.all().values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2).items()if k != '_state') -[('contact_id', 3), ('id', 2), ('isbn', u'067232959'), ('manufacture_cost', ...11.545...), ('mean_auth_age', 45.0), ('name', u'Sams Teach Yourself Django in 24 Hours'), ('pages', 528), ('price', Decimal("23.09")), ('pubdate', datetime.date(2008, 3, 3)), ('publisher_id', 2), ('rating', 3.0)] - -# If the annotation precedes the values clause, it won't be included -# unless it is explicitly named ->>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name').get(pk=1).items()) -[('name', u'The Definitive Guide to Django: Web Development Done Right')] - ->>> sorted(Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name','mean_auth_age').get(pk=1).items()) -[('mean_auth_age', 34.5), ('name', u'The Definitive Guide to Django: Web Development Done Right')] - -# If an annotation isn't included in the values, it can still be used in a filter ->>> Book.objects.annotate(n_authors=Count('authors')).values('name').filter(n_authors__gt=2) -[{'name': u'Python Web Development with Django'}] - -# The annotations are added to values output if values() precedes annotate() ->>> sorted(Book.objects.all().values('name').annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1).items()) -[('mean_auth_age', 34.5), ('name', u'The Definitive Guide to Django: Web Development Done Right')] - -# Check that all of the objects are getting counted (allow_nulls) and that values respects the amount of objects ->>> len(Author.objects.all().annotate(Avg('friends__age')).values()) -9 - -# Check that consecutive calls to annotate accumulate in the query ->>> Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('oldest', 'price').annotate(Max('publisher__num_awards')) -[{'price': Decimal("30..."), 'oldest': 35, 'publisher__num_awards__max': 3}, {'price': Decimal("29.69"), 'oldest': 37, 'publisher__num_awards__max': 7}, {'price': Decimal("23.09"), 'oldest': 45, 'publisher__num_awards__max': 1}, {'price': Decimal("75..."), 'oldest': 57, 'publisher__num_awards__max': 9}, {'price': Decimal("82.8..."), 'oldest': 57, 'publisher__num_awards__max': 7}] - -# Aggregates can be composed over annotations. -# The return type is derived from the composed aggregate ->>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('pages'), Max('price'), Sum('num_authors'), Avg('num_authors')) -{'num_authors__sum': 10, 'num_authors__avg': 1.66..., 'pages__max': 1132, 'price__max': Decimal("82.80")} - -# Bad field requests in aggregates are caught and reported ->>> Book.objects.all().aggregate(num_authors=Count('foo')) -Traceback (most recent call last): -... -FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, contact, hardbackbook, id, isbn, name, pages, price, pubdate, publisher, rating, store - ->>> Book.objects.all().annotate(num_authors=Count('foo')) -Traceback (most recent call last): -... -FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, contact, hardbackbook, id, isbn, name, pages, price, pubdate, publisher, rating, store - ->>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('foo')) -Traceback (most recent call last): -... -FieldError: Cannot resolve keyword 'foo' into field. Choices are: authors, contact, hardbackbook, id, isbn, name, pages, price, pubdate, publisher, rating, store, num_authors - -# Old-style count aggregations can be mixed with new-style ->>> Book.objects.annotate(num_authors=Count('authors')).count() -6 - -# Non-ordinal, non-computed Aggregates over annotations correctly inherit -# the annotation's internal type if the annotation is ordinal or computed ->>> Book.objects.annotate(num_authors=Count('authors')).aggregate(Max('num_authors')) -{'num_authors__max': 3} - ->>> Publisher.objects.annotate(avg_price=Avg('book__price')).aggregate(Max('avg_price')) -{'avg_price__max': 75.0...} - -# Aliases are quoted to protected aliases that might be reserved names ->>> Book.objects.aggregate(number=Max('pages'), select=Max('pages')) -{'number': 1132, 'select': 1132} - -# Regression for #10064: select_related() plays nice with aggregates ->>> sorted(Book.objects.select_related('publisher').annotate(num_authors=Count('authors')).values()[0].iteritems()) -[('contact_id', 8), ('id', 5), ('isbn', u'013790395'), ('name', u'Artificial Intelligence: A Modern Approach'), ('num_authors', 2), ('pages', 1132), ('price', Decimal("82.8...")), ('pubdate', datetime.date(1995, 1, 15)), ('publisher_id', 3), ('rating', 4.0)] - -# Regression for #10010: exclude on an aggregate field is correctly negated ->>> len(Book.objects.annotate(num_authors=Count('authors'))) -6 ->>> len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=2)) -1 ->>> len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__gt=2)) -5 - ->>> len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__lt=3).exclude(num_authors__lt=2)) -2 ->>> len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__lt=2).filter(num_authors__lt=3)) -2 - -# Aggregates can be used with F() expressions -# ... where the F() is pushed into the HAVING clause ->>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards') -[{'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}] - ->>> Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards') -[{'num_books': 2, 'name': u'Apress', 'num_awards': 3}, {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}, {'num_books': 1, 'name': u'Sams', 'num_awards': 1}] - -# ... and where the F() references an aggregate ->>> Publisher.objects.annotate(num_books=Count('book')).filter(num_awards__gt=2*F('num_books')).order_by('name').values('name','num_books','num_awards') -[{'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}] - ->>> Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards') -[{'num_books': 2, 'name': u'Apress', 'num_awards': 3}, {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}, {'num_books': 1, 'name': u'Sams', 'num_awards': 1}] - -# Tests on fields with non-default table and column names. ->>> Clues.objects.values('EntryID__Entry').annotate(Appearances=Count('EntryID'), Distinct_Clues=Count('Clue', distinct=True)) -[] - ->>> Entries.objects.annotate(clue_count=Count('clues__ID')) -[] - -# Regression for #10089: Check handling of empty result sets with aggregates ->>> Book.objects.filter(id__in=[]).count() -0 - ->>> Book.objects.filter(id__in=[]).aggregate(num_authors=Count('authors'), avg_authors=Avg('authors'), max_authors=Max('authors'), max_price=Max('price'), max_rating=Max('rating')) -{'max_authors': None, 'max_rating': None, 'num_authors': 0, 'avg_authors': None, 'max_price': None} - ->>> list(Publisher.objects.filter(pk=5).annotate(num_authors=Count('book__authors'), avg_authors=Avg('book__authors'), max_authors=Max('book__authors'), max_price=Max('book__price'), max_rating=Max('book__rating')).values()) == [{'max_authors': None, 'name': u"Jonno's House of Books", 'num_awards': 0, 'max_price': None, 'num_authors': 0, 'max_rating': None, 'id': 5, 'avg_authors': None}] -True - -# Regression for #10113 - Fields mentioned in order_by() must be included in the GROUP BY. -# This only becomes a problem when the order_by introduces a new join. ->>> Book.objects.annotate(num_authors=Count('authors')).order_by('publisher__name', 'name') -[, , , , , ] - -# Regression for #10127 - Empty select_related() works with annotate ->>> books = Book.objects.all().filter(rating__lt=4.5).select_related().annotate(Avg('authors__age')) ->>> sorted([(b.name, b.authors__age__avg, b.publisher.name, b.contact.name) for b in books]) -[(u'Artificial Intelligence: A Modern Approach', 51.5, u'Prentice Hall', u'Peter Norvig'), (u'Practical Django Projects', 29.0, u'Apress', u'James Bennett'), (u'Python Web Development with Django', 30.3..., u'Prentice Hall', u'Jeffrey Forcier'), (u'Sams Teach Yourself Django in 24 Hours', 45.0, u'Sams', u'Brad Dayley')] - -# Regression for #10132 - If the values() clause only mentioned extra(select=) columns, those columns are used for grouping ->>> Book.objects.extra(select={'pub':'publisher_id'}).values('pub').annotate(Count('id')).order_by('pub') -[{'pub': 1, 'id__count': 2}, {'pub': 2, 'id__count': 1}, {'pub': 3, 'id__count': 2}, {'pub': 4, 'id__count': 1}] - ->>> Book.objects.extra(select={'pub':'publisher_id','foo':'pages'}).values('pub').annotate(Count('id')).order_by('pub') -[{'pub': 1, 'id__count': 2}, {'pub': 2, 'id__count': 1}, {'pub': 3, 'id__count': 2}, {'pub': 4, 'id__count': 1}] - -# Regression for #10182 - Queries with aggregate calls are correctly realiased when used in a subquery ->>> ids = Book.objects.filter(pages__gt=100).annotate(n_authors=Count('authors')).filter(n_authors__gt=2).order_by('n_authors') ->>> Book.objects.filter(id__in=ids) -[] - -# Regression for #10197 -- Queries with aggregates can be pickled. -# First check that pickling is possible at all. No crash = success ->>> qs = Book.objects.annotate(num_authors=Count('authors')) ->>> out = pickle.dumps(qs) - -# Then check that the round trip works. ->>> query = qs.query.get_compiler(qs.db).as_sql()[0] ->>> select_fields = qs.query.select_fields ->>> query2 = pickle.loads(pickle.dumps(qs)) ->>> query2.query.get_compiler(query2.db).as_sql()[0] == query -True ->>> query2.query.select_fields = select_fields - -# Regression for #10199 - Aggregate calls clone the original query so the original query can still be used ->>> books = Book.objects.all() ->>> _ = books.aggregate(Avg('authors__age')) ->>> books.all() -[, , , , , ] - -# Regression for #10248 - Annotations work with DateQuerySets ->>> Book.objects.annotate(num_authors=Count('authors')).filter(num_authors=2).dates('pubdate', 'day') -[datetime.datetime(1995, 1, 15, 0, 0), datetime.datetime(2007, 12, 6, 0, 0)] - -# Regression for #10290 - extra selects with parameters can be used for -# grouping. ->>> qs = Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'sheets' : '(pages + %s) / %s'}, select_params=[1, 2]).order_by('sheets').values('sheets') ->>> [int(x['sheets']) for x in qs] -[150, 175, 224, 264, 473, 566] - -# Regression for 10425 - annotations don't get in the way of a count() clause ->>> Book.objects.values('publisher').annotate(Count('publisher')).count() -4 - ->>> Book.objects.annotate(Count('publisher')).values('publisher').count() -6 - ->>> publishers = Publisher.objects.filter(id__in=(1,2)) ->>> publishers -[, ] - ->>> publishers = publishers.annotate(n_books=models.Count('book')) ->>> publishers[0].n_books -2 - ->>> publishers -[, ] - ->>> books = Book.objects.filter(publisher__in=publishers) ->>> books -[, , ] - ->>> publishers -[, ] - - -# Regression for 10666 - inherited fields work with annotations and aggregations ->>> HardbackBook.objects.aggregate(n_pages=Sum('book_ptr__pages')) -{'n_pages': 2078} - ->>> HardbackBook.objects.aggregate(n_pages=Sum('pages')) -{'n_pages': 2078} - ->>> HardbackBook.objects.annotate(n_authors=Count('book_ptr__authors')).values('name','n_authors') -[{'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'}, {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'}] - ->>> HardbackBook.objects.annotate(n_authors=Count('authors')).values('name','n_authors') -[{'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'}, {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'}] - -# Regression for #10766 - Shouldn't be able to reference an aggregate fields in an an aggregate() call. ->>> Book.objects.all().annotate(mean_age=Avg('authors__age')).annotate(Avg('mean_age')) -Traceback (most recent call last): -... -FieldError: Cannot compute Avg('mean_age'): 'mean_age' is an aggregate - -""" -} - -def run_stddev_tests(): - """Check to see if StdDev/Variance tests should be run. - - Stddev and Variance are not guaranteed to be available for SQLite, and - are not available for PostgreSQL before 8.2. - """ - if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.sqlite3': - return False - - class StdDevPop(object): - sql_function = 'STDDEV_POP' - - try: - connection.ops.check_aggregate_support(StdDevPop()) - except: - return False - return True - -if run_stddev_tests(): - __test__['API_TESTS'] += """ ->>> Book.objects.aggregate(StdDev('pages')) -{'pages__stddev': 311.46...} - ->>> Book.objects.aggregate(StdDev('rating')) -{'rating__stddev': 0.60...} - ->>> Book.objects.aggregate(StdDev('price')) -{'price__stddev': 24.16...} - - ->>> Book.objects.aggregate(StdDev('pages', sample=True)) -{'pages__stddev': 341.19...} - ->>> Book.objects.aggregate(StdDev('rating', sample=True)) -{'rating__stddev': 0.66...} - ->>> Book.objects.aggregate(StdDev('price', sample=True)) -{'price__stddev': 26.46...} - - ->>> Book.objects.aggregate(Variance('pages')) -{'pages__variance': 97010.80...} - ->>> Book.objects.aggregate(Variance('rating')) -{'rating__variance': 0.36...} - ->>> Book.objects.aggregate(Variance('price')) -{'price__variance': 583.77...} - - ->>> Book.objects.aggregate(Variance('pages', sample=True)) -{'pages__variance': 116412.96...} - ->>> Book.objects.aggregate(Variance('rating', sample=True)) -{'rating__variance': 0.44...} - ->>> Book.objects.aggregate(Variance('price', sample=True)) -{'price__variance': 700.53...} - -""" diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py index 3c4bdfa47dbf..b32d4b5842c7 100644 --- a/tests/regressiontests/aggregation_regress/tests.py +++ b/tests/regressiontests/aggregation_regress/tests.py @@ -1,12 +1,38 @@ +import datetime +from decimal import Decimal + +from django.core.exceptions import FieldError from django.conf import settings -from django.test import TestCase +from django.test import TestCase, Approximate from django.db import DEFAULT_DB_ALIAS -from django.db.models import Count, Max +from django.db.models import Count, Max, Avg, Sum, F from regressiontests.aggregation_regress.models import * +def run_stddev_tests(): + """Check to see if StdDev/Variance tests should be run. + + Stddev and Variance are not guaranteed to be available for SQLite, and + are not available for PostgreSQL before 8.2. + """ + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.sqlite3': + return False + + class StdDevPop(object): + sql_function = 'STDDEV_POP' + + try: + connection.ops.check_aggregate_support(StdDevPop()) + except: + return False + return True + + class AggregationTests(TestCase): + def assertObjectAttrs(self, obj, **kwargs): + for attr, value in kwargs.iteritems(): + self.assertEqual(getattr(obj, attr), value) def test_aggregates_in_where_clause(self): """ @@ -70,3 +96,587 @@ def test_annotate_with_extra(self): }).annotate(total_books=Count('book')) # force execution of the query list(qs) + + def test_aggregate(self): + # Ordering requests are ignored + self.assertEqual( + Author.objects.order_by("name").aggregate(Avg("age")), + {"age__avg": Approximate(37.444, places=1)} + ) + + # Implicit ordering is also ignored + self.assertEqual( + Book.objects.aggregate(Sum("pages")), + {"pages__sum": 3703}, + ) + + # Baseline results + self.assertEqual( + Book.objects.aggregate(Sum('pages'), Avg('pages')), + {'pages__sum': 3703, 'pages__avg': Approximate(617.166, places=2)} + ) + + # Empty values query doesn't affect grouping or results + self.assertEqual( + Book.objects.values().aggregate(Sum('pages'), Avg('pages')), + {'pages__sum': 3703, 'pages__avg': Approximate(617.166, places=2)} + ) + + # Aggregate overrides extra selected column + self.assertEqual( + Book.objects.extra(select={'price_per_page' : 'price / pages'}).aggregate(Sum('pages')), + {'pages__sum': 3703} + ) + + def test_annotation(self): + # Annotations get combined with extra select clauses + obj = Book.objects.annotate(mean_auth_age=Avg("authors__age")).extra(select={"manufacture_cost": "price * .5"}).get(pk=2) + self.assertObjectAttrs(obj, + contact_id=3, + id=2, + isbn=u'067232959', + manufacture_cost=11.545, + mean_auth_age=45.0, + name='Sams Teach Yourself Django in 24 Hours', + pages=528, + price=Decimal("23.09"), + pubdate=datetime.date(2008, 3, 3), + publisher_id=2, + rating=3.0 + ) + + # Order of the annotate/extra in the query doesn't matter + obj = Book.objects.extra(select={'manufacture_cost' : 'price * .5'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=2) + self.assertObjectAttrs(obj, + contact_id=3, + id=2, + isbn=u'067232959', + manufacture_cost=11.545, + mean_auth_age=45.0, + name=u'Sams Teach Yourself Django in 24 Hours', + pages=528, + price=Decimal("23.09"), + pubdate=datetime.date(2008, 3, 3), + publisher_id=2, + rating=3.0 + ) + + # Values queries can be combined with annotate and extra + obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2) + self.assertEqual(obj, { + "contact_id": 3, + "id": 2, + "isbn": u"067232959", + "manufacture_cost": 11.545, + "mean_auth_age": 45.0, + "name": u"Sams Teach Yourself Django in 24 Hours", + "pages": 528, + "price": Decimal("23.09"), + "pubdate": datetime.date(2008, 3, 3), + "publisher_id": 2, + "rating": 3.0, + }) + + # The order of the (empty) values, annotate and extra clauses doesn't + # matter + obj = Book.objects.values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2) + self.assertEqual(obj, { + 'contact_id': 3, + 'id': 2, + 'isbn': u'067232959', + 'manufacture_cost': 11.545, + 'mean_auth_age': 45.0, + 'name': u'Sams Teach Yourself Django in 24 Hours', + 'pages': 528, + 'price': Decimal("23.09"), + 'pubdate': datetime.date(2008, 3, 3), + 'publisher_id': 2, + 'rating': 3.0 + }) + + # If the annotation precedes the values clause, it won't be included + # unless it is explicitly named + obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name').get(pk=1) + self.assertEqual(obj, { + "name": u'The Definitive Guide to Django: Web Development Done Right', + }) + + obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name','mean_auth_age').get(pk=1) + self.assertEqual(obj, { + 'mean_auth_age': 34.5, + 'name': u'The Definitive Guide to Django: Web Development Done Right', + }) + + # If an annotation isn't included in the values, it can still be used + # in a filter + qs = Book.objects.annotate(n_authors=Count('authors')).values('name').filter(n_authors__gt=2) + self.assertQuerysetEqual( + qs, [ + {"name": u'Python Web Development with Django'} + ], + lambda b: b, + ) + + # The annotations are added to values output if values() precedes + # annotate() + obj = Book.objects.values('name').annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1) + self.assertEqual(obj, { + 'mean_auth_age': 34.5, + 'name': u'The Definitive Guide to Django: Web Development Done Right', + }) + + # Check that all of the objects are getting counted (allow_nulls) and + # that values respects the amount of objects + self.assertEqual( + len(Author.objects.annotate(Avg('friends__age')).values()), + 9 + ) + + # Check that consecutive calls to annotate accumulate in the query + qs = Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('oldest', 'price').annotate(Max('publisher__num_awards')) + self.assertQuerysetEqual( + qs, [ + {'price': Decimal("30"), 'oldest': 35, 'publisher__num_awards__max': 3}, + {'price': Decimal("29.69"), 'oldest': 37, 'publisher__num_awards__max': 7}, + {'price': Decimal("23.09"), 'oldest': 45, 'publisher__num_awards__max': 1}, + {'price': Decimal("75"), 'oldest': 57, 'publisher__num_awards__max': 9}, + {'price': Decimal("82.8"), 'oldest': 57, 'publisher__num_awards__max': 7} + ], + lambda b: b, + ) + + def test_aggrate_annotation(self): + # Aggregates can be composed over annotations. + # The return type is derived from the composed aggregate + vals = Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('pages'), Max('price'), Sum('num_authors'), Avg('num_authors')) + self.assertEqual(vals, { + 'num_authors__sum': 10, + 'num_authors__avg': Approximate(1.666, places=2), + 'pages__max': 1132, + 'price__max': Decimal("82.80") + }) + + def test_field_error(self): + # Bad field requests in aggregates are caught and reported + self.assertRaises( + FieldError, + lambda: Book.objects.all().aggregate(num_authors=Count('foo')) + ) + + self.assertRaises( + FieldError, + lambda: Book.objects.all().annotate(num_authors=Count('foo')) + ) + + self.assertRaises( + FieldError, + lambda: Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('foo')) + ) + + def test_more(self): + # Old-style count aggregations can be mixed with new-style + self.assertEqual( + Book.objects.annotate(num_authors=Count('authors')).count(), + 6 + ) + + # Non-ordinal, non-computed Aggregates over annotations correctly + # inherit the annotation's internal type if the annotation is ordinal + # or computed + vals = Book.objects.annotate(num_authors=Count('authors')).aggregate(Max('num_authors')) + self.assertEqual( + vals, + {'num_authors__max': 3} + ) + + vals = Publisher.objects.annotate(avg_price=Avg('book__price')).aggregate(Max('avg_price')) + self.assertEqual( + vals, + {'avg_price__max': 75.0} + ) + + # Aliases are quoted to protected aliases that might be reserved names + vals = Book.objects.aggregate(number=Max('pages'), select=Max('pages')) + self.assertEqual( + vals, + {'number': 1132, 'select': 1132} + ) + + # Regression for #10064: select_related() plays nice with aggregates + obj = Book.objects.select_related('publisher').annotate(num_authors=Count('authors')).values()[0] + self.assertEqual(obj, { + 'contact_id': 8, + 'id': 5, + 'isbn': u'013790395', + 'name': u'Artificial Intelligence: A Modern Approach', + 'num_authors': 2, + 'pages': 1132, + 'price': Decimal("82.8"), + 'pubdate': datetime.date(1995, 1, 15), + 'publisher_id': 3, + 'rating': 4.0, + }) + + # Regression for #10010: exclude on an aggregate field is correctly + # negated + self.assertEqual( + len(Book.objects.annotate(num_authors=Count('authors'))), + 6 + ) + self.assertEqual( + len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=2)), + 1 + ) + self.assertEqual( + len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__gt=2)), + 5 + ) + + self.assertEqual( + len(Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__lt=3).exclude(num_authors__lt=2)), + 2 + ) + self.assertEqual( + len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__lt=2).filter(num_authors__lt=3)), + 2 + ) + + def test_aggregate_fexpr(self): + # Aggregates can be used with F() expressions + # ... where the F() is pushed into the HAVING clause + qs = Publisher.objects.annotate(num_books=Count('book')).filter(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards') + self.assertQuerysetEqual( + qs, [ + {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, + {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7} + ], + lambda p: p, + ) + + qs = Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards') + self.assertQuerysetEqual( + qs, [ + {'num_books': 2, 'name': u'Apress', 'num_awards': 3}, + {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}, + {'num_books': 1, 'name': u'Sams', 'num_awards': 1} + ], + lambda p: p, + ) + + # ... and where the F() references an aggregate + qs = Publisher.objects.annotate(num_books=Count('book')).filter(num_awards__gt=2*F('num_books')).order_by('name').values('name','num_books','num_awards') + self.assertQuerysetEqual( + qs, [ + {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}, + {'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7} + ], + lambda p: p, + ) + + qs = Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).order_by('name').values('name','num_books','num_awards') + self.assertQuerysetEqual( + qs, [ + {'num_books': 2, 'name': u'Apress', 'num_awards': 3}, + {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}, + {'num_books': 1, 'name': u'Sams', 'num_awards': 1} + ], + lambda p: p, + ) + + def test_db_col_table(self): + # Tests on fields with non-default table and column names. + qs = Clues.objects.values('EntryID__Entry').annotate(Appearances=Count('EntryID'), Distinct_Clues=Count('Clue', distinct=True)) + self.assertQuerysetEqual(qs, []) + + qs = Entries.objects.annotate(clue_count=Count('clues__ID')) + self.assertQuerysetEqual(qs, []) + + def test_empty(self): + # Regression for #10089: Check handling of empty result sets with + # aggregates + self.assertEqual( + Book.objects.filter(id__in=[]).count(), + 0 + ) + + vals = Book.objects.filter(id__in=[]).aggregate(num_authors=Count('authors'), avg_authors=Avg('authors'), max_authors=Max('authors'), max_price=Max('price'), max_rating=Max('rating')) + self.assertEqual( + vals, + {'max_authors': None, 'max_rating': None, 'num_authors': 0, 'avg_authors': None, 'max_price': None} + ) + + qs = Publisher.objects.filter(pk=5).annotate(num_authors=Count('book__authors'), avg_authors=Avg('book__authors'), max_authors=Max('book__authors'), max_price=Max('book__price'), max_rating=Max('book__rating')).values() + self.assertQuerysetEqual( + qs, [ + {'max_authors': None, 'name': u"Jonno's House of Books", 'num_awards': 0, 'max_price': None, 'num_authors': 0, 'max_rating': None, 'id': 5, 'avg_authors': None} + ], + lambda p: p + ) + + def test_more_more(self): + # Regression for #10113 - Fields mentioned in order_by() must be + # included in the GROUP BY. This only becomes a problem when the + # order_by introduces a new join. + self.assertQuerysetEqual( + Book.objects.annotate(num_authors=Count('authors')).order_by('publisher__name', 'name'), [ + "Practical Django Projects", + "The Definitive Guide to Django: Web Development Done Right", + "Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp", + "Artificial Intelligence: A Modern Approach", + "Python Web Development with Django", + "Sams Teach Yourself Django in 24 Hours", + ], + lambda b: b.name + ) + + # Regression for #10127 - Empty select_related() works with annotate + qs = Book.objects.filter(rating__lt=4.5).select_related().annotate(Avg('authors__age')) + self.assertQuerysetEqual( + qs, [ + (u'Artificial Intelligence: A Modern Approach', 51.5, u'Prentice Hall', u'Peter Norvig'), + (u'Practical Django Projects', 29.0, u'Apress', u'James Bennett'), + (u'Python Web Development with Django', Approximate(30.333, places=2), u'Prentice Hall', u'Jeffrey Forcier'), + (u'Sams Teach Yourself Django in 24 Hours', 45.0, u'Sams', u'Brad Dayley') + ], + lambda b: (b.name, b.authors__age__avg, b.publisher.name, b.contact.name) + ) + + # Regression for #10132 - If the values() clause only mentioned extra + # (select=) columns, those columns are used for grouping + qs = Book.objects.extra(select={'pub':'publisher_id'}).values('pub').annotate(Count('id')).order_by('pub') + self.assertQuerysetEqual( + qs, [ + {'pub': 1, 'id__count': 2}, + {'pub': 2, 'id__count': 1}, + {'pub': 3, 'id__count': 2}, + {'pub': 4, 'id__count': 1} + ], + lambda b: b + ) + + qs = Book.objects.extra(select={'pub':'publisher_id', 'foo':'pages'}).values('pub').annotate(Count('id')).order_by('pub') + self.assertQuerysetEqual( + qs, [ + {'pub': 1, 'id__count': 2}, + {'pub': 2, 'id__count': 1}, + {'pub': 3, 'id__count': 2}, + {'pub': 4, 'id__count': 1} + ], + lambda b: b + ) + + # Regression for #10182 - Queries with aggregate calls are correctly + # realiased when used in a subquery + ids = Book.objects.filter(pages__gt=100).annotate(n_authors=Count('authors')).filter(n_authors__gt=2).order_by('n_authors') + self.assertQuerysetEqual( + Book.objects.filter(id__in=ids), [ + "Python Web Development with Django", + ], + lambda b: b.name + ) + + def test_pickle(self): + # Regression for #10197 -- Queries with aggregates can be pickled. + # First check that pickling is possible at all. No crash = success + qs = Book.objects.annotate(num_authors=Count('authors')) + out = pickle.dumps(qs) + + # Then check that the round trip works. + query = qs.query.get_compiler(qs.db).as_sql()[0] + qs2 = pickle.loads(pickle.dumps(qs)) + self.assertEqual( + qs2.query.get_compiler(qs2.db).as_sql()[0], + query, + ) + + def test_more_more_more(self): + # Regression for #10199 - Aggregate calls clone the original query so + # the original query can still be used + books = Book.objects.all() + books.aggregate(Avg("authors__age")) + self.assertQuerysetEqual( + books.all(), [ + u'Artificial Intelligence: A Modern Approach', + u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', + u'Practical Django Projects', + u'Python Web Development with Django', + u'Sams Teach Yourself Django in 24 Hours', + u'The Definitive Guide to Django: Web Development Done Right' + ], + lambda b: b.name + ) + + # Regression for #10248 - Annotations work with DateQuerySets + qs = Book.objects.annotate(num_authors=Count('authors')).filter(num_authors=2).dates('pubdate', 'day') + self.assertQuerysetEqual( + qs, [ + datetime.datetime(1995, 1, 15, 0, 0), + datetime.datetime(2007, 12, 6, 0, 0) + ], + lambda b: b + ) + + # Regression for #10290 - extra selects with parameters can be used for + # grouping. + qs = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'sheets' : '(pages + %s) / %s'}, select_params=[1, 2]).order_by('sheets').values('sheets') + self.assertQuerysetEqual( + qs, [ + 150, + 175, + 224, + 264, + 473, + 566 + ], + lambda b: int(b["sheets"]) + ) + + # Regression for 10425 - annotations don't get in the way of a count() + # clause + self.assertEqual( + Book.objects.values('publisher').annotate(Count('publisher')).count(), + 4 + ) + self.assertEqual( + Book.objects.annotate(Count('publisher')).values('publisher').count(), + 6 + ) + + publishers = Publisher.objects.filter(id__in=[1, 2]) + self.assertQuerysetEqual( + publishers, [ + "Apress", + "Sams" + ], + lambda p: p.name + ) + + publishers = publishers.annotate(n_books=Count("book")) + self.assertEqual( + publishers[0].n_books, + 2 + ) + + self.assertQuerysetEqual( + publishers, [ + "Apress", + "Sams", + ], + lambda p: p.name + ) + + books = Book.objects.filter(publisher__in=publishers) + self.assertQuerysetEqual( + books, [ + "Practical Django Projects", + "Sams Teach Yourself Django in 24 Hours", + "The Definitive Guide to Django: Web Development Done Right", + ], + lambda b: b.name + ) + self.assertQuerysetEqual( + publishers, [ + "Apress", + "Sams", + ], + lambda p: p.name + ) + + # Regression for 10666 - inherited fields work with annotations and + # aggregations + self.assertEqual( + HardbackBook.objects.aggregate(n_pages=Sum('book_ptr__pages')), + {'n_pages': 2078} + ) + + self.assertEqual( + HardbackBook.objects.aggregate(n_pages=Sum('pages')), + {'n_pages': 2078}, + ) + + qs = HardbackBook.objects.annotate(n_authors=Count('book_ptr__authors')).values('name', 'n_authors') + self.assertQuerysetEqual( + qs, [ + {'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'}, + {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'} + ], + lambda h: h + ) + + qs = HardbackBook.objects.annotate(n_authors=Count('authors')).values('name', 'n_authors') + self.assertQuerysetEqual( + qs, [ + {'n_authors': 2, 'name': u'Artificial Intelligence: A Modern Approach'}, + {'n_authors': 1, 'name': u'Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp'} + ], + lambda h: h, + ) + + # Regression for #10766 - Shouldn't be able to reference an aggregate + # fields in an an aggregate() call. + self.assertRaises( + FieldError, + lambda: Book.objects.annotate(mean_age=Avg('authors__age')).annotate(Avg('mean_age')) + ) + + if run_stddev_tests(): + def test_stddev(self): + self.assertEqual( + Book.objects.aggregate(StdDev('pages')), + {'pages__stddev': 311.46} + ) + + self.assertEqual( + Book.objects.aggregate(StdDev('rating')), + {'rating__stddev': 0.60} + ) + + self.assertEqual( + Book.objects.aggregate(StdDev('price')), + {'price__stddev': 24.16} + ) + + self.assertEqual( + Book.objects.aggregate(StdDev('pages', sample=True)), + {'pages__stddev': 341.19} + ) + + self.assertEqual( + Book.objects.aggregate(StdDev('rating', sample=True)), + {'rating__stddev': 0.66} + ) + + self.assertEqual( + Book.objects.aggregate(StdDev('price', sample=True)), + {'price__stddev': 26.46} + ) + + self.assertEqual( + Book.objects.aggregate(Variance('pages')), + {'pages__variance': 97010.80} + ) + + self.assertEqual( + Book.objects.aggregate(Variance('rating')), + {'rating__variance': 0.36} + ) + + self.assertEqual( + Book.objects.aggregate(Variance('price')), + {'price__variance': 583.77} + ) + + self.assertEqual( + Book.objects.aggregate(Variance('pages', sample=True)), + {'pages__variance': 116412.96} + ) + + self.assertEqual( + Book.objects.aggregate(Variance('rating', sample=True)), + {'rating__variance': 0.44} + ) + + self.assertEqual( + Book.objects.aggregate(Variance('price', sample=True)), + {'price__variance': 700.53} + ) From 1c3e3d3dba0a833981ed39da42e0bed015400d40 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 20 Aug 2010 15:06:09 +0000 Subject: [PATCH 078/902] [1.2.X] Fixed #12574 -- Removed an unnecessary exception catch from the system runtest script, which could hide failing tests. Thanks to CarlFK for the report, and Ramiro Morales for the polish. Backport of r13616 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13617 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/runtests.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/tests/runtests.py b/tests/runtests.py index cd60cabc930e..794f0dca9491 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -122,22 +122,19 @@ def django_tests(verbosity, interactive, failfast, test_labels): get_apps() # Load all the test model apps. + test_labels_set = set([label.split('.')[0] for label in test_labels]) for model_dir, model_name in get_test_models(): model_label = '.'.join([model_dir, model_name]) - try: - # if the model was named on the command line, or - # no models were named (i.e., run all), import - # this model and add it to the list to test. - if not test_labels or model_name in set([label.split('.')[0] for label in test_labels]): - if verbosity >= 1: - print "Importing model %s" % model_name - mod = load_app(model_label) - if mod: - if model_label not in settings.INSTALLED_APPS: - settings.INSTALLED_APPS.append(model_label) - except Exception, e: - sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:])) - continue + # if the model was named on the command line, or + # no models were named (i.e., run all), import + # this model and add it to the list to test. + if not test_labels or model_name in test_labels_set: + if verbosity >= 1: + print "Importing model %s" % model_name + mod = load_app(model_label) + if mod: + if model_label not in settings.INSTALLED_APPS: + settings.INSTALLED_APPS.append(model_label) # Add tests for invalid models. extra_tests = [] From 5c6fe321fe00fa59006ea0c91300bd36a817411a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 21 Aug 2010 02:56:05 +0000 Subject: [PATCH 079/902] [1.2.X] Fixed #14148 -- Ensure the admin_script tests always use an absolute path; PyPy (and sometimes CPython) return relative paths for __file__. Thanks to Alex Gaynor for the report and patch. Backport of r13618 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13619 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/admin_scripts/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py index 7ec245456122..3dd8ad5d133f 100644 --- a/tests/regressiontests/admin_scripts/tests.py +++ b/tests/regressiontests/admin_scripts/tests.py @@ -133,7 +133,7 @@ def run_test(self, script, args, settings_file=None, apps=None): return out, err def run_django_admin(self, args, settings_file=None): - bin_dir = os.path.dirname(bin.__file__) + bin_dir = os.path.abspath(os.path.dirname(bin.__file__)) return self.run_test(os.path.join(bin_dir,'django-admin.py'), args, settings_file) def run_manage(self, args, settings_file=None): From 49c5c075686a4afd831f5d7d8dad6fe019c49091 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 21 Aug 2010 03:02:47 +0000 Subject: [PATCH 080/902] [1.2.X] Fixed #13140 -- Ensure that request headers are preserved through redirect chains in the test client. Thanks to David Novakovic for the report. Backport of r13620 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13621 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/client.py | 15 +++++++-------- .../regressiontests/test_client_regress/models.py | 14 ++++++++++++++ tests/regressiontests/test_client_regress/urls.py | 2 ++ .../regressiontests/test_client_regress/views.py | 5 +++++ 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/django/test/client.py b/django/test/client.py index 498af5c3444f..cceed8d9f60e 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -293,7 +293,7 @@ def get(self, path, data={}, follow=False, **extra): response = self.request(**r) if follow: - response = self._handle_redirects(response) + response = self._handle_redirects(response, **extra) return response def post(self, path, data={}, content_type=MULTIPART_CONTENT, @@ -325,7 +325,7 @@ def post(self, path, data={}, content_type=MULTIPART_CONTENT, response = self.request(**r) if follow: - response = self._handle_redirects(response) + response = self._handle_redirects(response, **extra) return response def head(self, path, data={}, follow=False, **extra): @@ -344,7 +344,7 @@ def head(self, path, data={}, follow=False, **extra): response = self.request(**r) if follow: - response = self._handle_redirects(response) + response = self._handle_redirects(response, **extra) return response def options(self, path, data={}, follow=False, **extra): @@ -362,7 +362,7 @@ def options(self, path, data={}, follow=False, **extra): response = self.request(**r) if follow: - response = self._handle_redirects(response) + response = self._handle_redirects(response, **extra) return response def put(self, path, data={}, content_type=MULTIPART_CONTENT, @@ -394,7 +394,7 @@ def put(self, path, data={}, content_type=MULTIPART_CONTENT, response = self.request(**r) if follow: - response = self._handle_redirects(response) + response = self._handle_redirects(response, **extra) return response def delete(self, path, data={}, follow=False, **extra): @@ -412,7 +412,7 @@ def delete(self, path, data={}, follow=False, **extra): response = self.request(**r) if follow: - response = self._handle_redirects(response) + response = self._handle_redirects(response, **extra) return response def login(self, **credentials): @@ -467,7 +467,7 @@ def logout(self): session.delete(session_key=session_cookie.value) self.cookies = SimpleCookie() - def _handle_redirects(self, response): + def _handle_redirects(self, response, **extra): "Follows any redirects by requesting responses from the server using GET." response.redirect_chain = [] @@ -478,7 +478,6 @@ def _handle_redirects(self, response): redirect_chain = response.redirect_chain redirect_chain.append((url, response.status_code)) - extra = {} if scheme: extra['wsgi.url_scheme'] = scheme diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index 692e0f63d3c4..5f23c8ee13a2 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -845,3 +845,17 @@ def test_guesses_content_type_on_file_encoding(self): encode_file('IGNORE', 'IGNORE', DummyFile("file.zip"))[2]) self.assertEqual('Content-Type: application/octet-stream', encode_file('IGNORE', 'IGNORE', DummyFile("file.unknown"))[2]) + +class RequestHeadersTest(TestCase): + def test_client_headers(self): + "A test client can receive custom headers" + response = self.client.get("/test_client_regress/check_headers/", HTTP_X_ARG_CHECK='Testing 123') + self.assertEquals(response.content, "HTTP_X_ARG_CHECK: Testing 123") + self.assertEquals(response.status_code, 200) + + def test_client_headers_redirect(self): + "Test client headers are preserved through redirects" + response = self.client.get("/test_client_regress/check_headers_redirect/", follow=True, HTTP_X_ARG_CHECK='Testing 123') + self.assertEquals(response.content, "HTTP_X_ARG_CHECK: Testing 123") + self.assertRedirects(response, '/test_client_regress/check_headers/', + status_code=301, target_status_code=200) diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py index 99eb7b70be4b..650d80b909ef 100644 --- a/tests/regressiontests/test_client_regress/urls.py +++ b/tests/regressiontests/test_client_regress/urls.py @@ -24,4 +24,6 @@ (r'^request_methods/$', views.request_methods_view), (r'^check_unicode/$', views.return_unicode), (r'^parse_unicode_json/$', views.return_json_file), + (r'^check_headers/$', views.check_headers), + (r'^check_headers_redirect/$', redirect_to, {'url': '/test_client_regress/check_headers/'}), ) diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py index 5ba7cc30a954..40aa61fca722 100644 --- a/tests/regressiontests/test_client_regress/views.py +++ b/tests/regressiontests/test_client_regress/views.py @@ -86,3 +86,8 @@ def return_json_file(request): mimetype='application/json; charset=' + charset) response['Content-Disposition'] = 'attachment; filename=testfile.json' return response + +def check_headers(request): + "A view that responds with value of the X-ARG-CHECK header" + return HttpResponse('HTTP_X_ARG_CHECK: %s' % request.META.get('HTTP_X_ARG_CHECK', 'Undefined')) + From 1a2464c06966e4fb71efab3629960c74cd41ec76 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 23 Aug 2010 07:06:10 +0000 Subject: [PATCH 081/902] [1.2.X] Fixed #14155 -- Refactored another doctest suite and call to flush. Thanks to Alex Gaynor for the patch. Backport of r13622 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13623 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../fixtures_model_package/models/__init__.py | 40 ----------- .../fixtures_model_package/tests.py | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 40 deletions(-) create mode 100644 tests/modeltests/fixtures_model_package/tests.py diff --git a/tests/modeltests/fixtures_model_package/models/__init__.py b/tests/modeltests/fixtures_model_package/models/__init__.py index 1581102b8859..c0450b27bf35 100644 --- a/tests/modeltests/fixtures_model_package/models/__init__.py +++ b/tests/modeltests/fixtures_model_package/models/__init__.py @@ -12,43 +12,3 @@ class Meta: app_label = 'fixtures_model_package' ordering = ('-pub_date', 'headline') -__test__ = {'API_TESTS': """ ->>> from django.core import management ->>> from django.db.models import get_app - -# Reset the database representation of this app. -# This will return the database to a clean initial state. ->>> management.call_command('flush', verbosity=0, interactive=False) - -# Syncdb introduces 1 initial data object from initial_data.json. ->>> Article.objects.all() -[] - -# Load fixture 1. Single JSON file, with two objects. ->>> management.call_command('loaddata', 'fixture1.json', verbosity=0) ->>> Article.objects.all() -[, , ] - -# Load fixture 2. JSON file imported by default. Overwrites some existing objects ->>> management.call_command('loaddata', 'fixture2.json', verbosity=0) ->>> Article.objects.all() -[, , , ] - -# Load a fixture that doesn't exist ->>> management.call_command('loaddata', 'unknown.json', verbosity=0) - -# object list is unaffected ->>> Article.objects.all() -[, , , ] -"""} - - -from django.test import TestCase - -class SampleTestCase(TestCase): - fixtures = ['fixture1.json', 'fixture2.json'] - - def testClassFixtures(self): - "Check that test case has installed 4 fixture objects" - self.assertEqual(Article.objects.count(), 4) - self.assertEquals(str(Article.objects.all()), "[, , , ]") diff --git a/tests/modeltests/fixtures_model_package/tests.py b/tests/modeltests/fixtures_model_package/tests.py new file mode 100644 index 000000000000..328f8b849854 --- /dev/null +++ b/tests/modeltests/fixtures_model_package/tests.py @@ -0,0 +1,71 @@ +from django.core import management +from django.test import TestCase + +from models import Article + + +class SampleTestCase(TestCase): + fixtures = ['fixture1.json', 'fixture2.json'] + + def testClassFixtures(self): + "Test cases can load fixture objects into models defined in packages" + self.assertEqual(Article.objects.count(), 4) + self.assertQuerysetEqual( + Article.objects.all(),[ + "Django conquers world!", + "Copyright is fine the way it is", + "Poker has no place on ESPN", + "Python program becomes self aware" + ], + lambda a: a.headline + ) + + +class FixtureTestCase(TestCase): + def test_initial_data(self): + "Fixtures can load initial data into models defined in packages" + #Syncdb introduces 1 initial data object from initial_data.json + self.assertQuerysetEqual( + Article.objects.all(), [ + "Python program becomes self aware" + ], + lambda a: a.headline + ) + + def test_loaddata(self): + "Fixtures can load data into models defined in packages" + # Load fixture 1. Single JSON file, with two objects + management.call_command("loaddata", "fixture1.json", verbosity=0) + self.assertQuerysetEqual( + Article.objects.all(), [ + "Time to reform copyright", + "Poker has no place on ESPN", + "Python program becomes self aware", + ], + lambda a: a.headline, + ) + + # Load fixture 2. JSON file imported by default. Overwrites some + # existing objects + management.call_command("loaddata", "fixture2.json", verbosity=0) + self.assertQuerysetEqual( + Article.objects.all(), [ + "Django conquers world!", + "Copyright is fine the way it is", + "Poker has no place on ESPN", + "Python program becomes self aware", + ], + lambda a: a.headline, + ) + + # Load a fixture that doesn't exist + management.call_command("loaddata", "unknown.json", verbosity=0) + self.assertQuerysetEqual( + Article.objects.all(), [ + "Django conquers world!", + "Copyright is fine the way it is", + "Poker has no place on ESPN", + "Python program becomes self aware", + ], + lambda a: a.headline, + ) From e5c4ba6151b3220a918bd47cc3f5a4e5f9ac89d1 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 23 Aug 2010 07:15:14 +0000 Subject: [PATCH 082/902] [1.2.X] Fixed #14147 -- Added documentation metadata for new assertQuerysetEqual test method. Thanks to djoume for the report. Backport of r13624 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13625 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 8efa29e69aaf..a7e9c051843c 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -1248,19 +1248,6 @@ cause of an failure in your test suite. ``target_status_code`` will be the url and status code for the final point of the redirect chain. -.. method:: TestCase.assertQuerysetEqual(response, qs, values, transform=repr) - - Asserts that a queryset ``qs`` returns a particular list of values ``values``. - - The comparison of the contents of ``qs`` and ``values`` is performed using - the function ``transform``; by default, this means that the ``repr()`` of - each value is compared. Any other callable can be used if ``repr()`` doesn't - provide a unique or helpful comparison. - - The comparison is also ordering dependent. If ``qs`` doesn't provide an - implicit ordering, you will need to apply a ``order_by()`` clause to your - queryset to ensure that the test will pass reliably. - .. _topics-testing-email: E-mail services From 0472978da5f164b6fa5ae76f721dc1d8abdefc75 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 23 Aug 2010 07:54:07 +0000 Subject: [PATCH 083/902] [1.2.X] Fixed #3051 -- Documented the requirements for standalone testing. Thanks to Daniel Roseman for the draft text. Backport of r13626 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13627 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index a7e9c051843c..6e2ee8a21de7 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -309,6 +309,24 @@ can press ``Ctrl-C`` a second time and the test run will halt immediately, but not gracefully. No details of the tests run before the interruption will be reported, and any test databases created by the run will not be destroyed. +Running tests outside the test runner +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to run tests outside of ``./manage.py test`` -- for example, +from a shell prompt -- you will need to set up the test +environment first. Django provides a convenience method to do this:: + + >>> from django.test.utils import setup_test_environment + >>> setup_test_environment() + +This convenience method sets up the test database, and puts other +Django features into modes that allow for repeatable testing. + +The call to :meth:`~django.test.utils.setup_test_environment` is made +automatically as part of the setup of `./manage.py test`. You only +need to manually invoke this method if you're not using running your +tests via Django's test runner. + The test database ----------------- From e47520b8bafe1dd6e65eb439d5d7165b26c99748 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 23 Aug 2010 08:08:46 +0000 Subject: [PATCH 084/902] [1.2.X] Fixed #14112 -- Various Markup fixes for the docs. Thanks to ramiro for the patch. Backport of r13628 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13629 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/faq/admin.txt | 21 ++++++++------ docs/ref/contrib/admin/index.txt | 6 ++-- docs/ref/contrib/contenttypes.txt | 4 +-- docs/ref/contrib/gis/testing.txt | 8 +++--- docs/ref/contrib/index.txt | 5 ++-- docs/ref/django-admin.txt | 2 +- docs/ref/models/fields.txt | 3 +- docs/ref/models/querysets.txt | 2 -- docs/ref/settings.txt | 46 ++++++++++++++++++++++++++++++- docs/ref/templates/api.txt | 2 +- docs/topics/testing.txt | 4 +-- 11 files changed, 74 insertions(+), 29 deletions(-) diff --git a/docs/faq/admin.txt b/docs/faq/admin.txt index 8ee6cc184bb8..cd0e4f92b677 100644 --- a/docs/faq/admin.txt +++ b/docs/faq/admin.txt @@ -35,19 +35,22 @@ Set the :setting:`CACHE_MIDDLEWARE_ANONYMOUS_ONLY` setting to ``True``. See the How do I automatically set a field's value to the user who last edited the object in the admin? ----------------------------------------------------------------------------------------------- -The :class:`ModelAdmin` class provides customization hooks that allow you to transform -an object as it saved, using details from the request. By extracting the current user -from the request, and customizing the :meth:`ModelAdmin.save_model` hook, you can update -an object to reflect the user that edited it. See :ref:`the documentation on ModelAdmin -methods ` for an example. +The :class:`~django.contrib.admin.ModelAdmin` class provides customization hooks +that allow you to transform an object as it saved, using details from the +request. By extracting the current user from the request, and customizing the +:meth:`~django.contrib.admin.ModelAdmin.save_model` hook, you can update an +object to reflect the user that edited it. See :ref:`the documentation on +ModelAdmin methods ` for an example. How do I limit admin access so that objects can only be edited by the users who created them? --------------------------------------------------------------------------------------------- -The :class:`ModelAdmin` class also provides customization hooks that allow you to control the -visibility and editability of objects in the admin. Using the same trick of extracting the -user from the request, the :meth:`ModelAdmin.queryset` and :meth:`ModelAdmin.has_change_permission` -can be used to control the visibility and editability of objects in the admin. +The :class:`~django.contrib.admin.ModelAdmin` class also provides customization +hooks that allow you to control the visibility and editability of objects in the +admin. Using the same trick of extracting the user from the request, the +:meth:`~django.contrib.admin.ModelAdmin.queryset` and +:meth:`~django.contrib.admin.ModelAdmin.has_change_permission` can be used to +control the visibility and editability of objects in the admin. My admin-site CSS and images showed up fine using the development server, but they're not displaying when using mod_python. --------------------------------------------------------------------------------------------------------------------------- diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 86163c8401a7..055057677c5f 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -5,8 +5,6 @@ The Django admin site .. module:: django.contrib.admin :synopsis: Django's admin site. -.. currentmodule:: django.contrib.admin - One of the most powerful parts of Django is the automatic admin interface. It reads metadata in your model to provide a powerful and production-ready interface that content producers can immediately use to start adding content to @@ -831,7 +829,7 @@ problems: Since this is usually not what you want, Django provides a convenience wrapper to check permissions and mark the view as non-cacheable. This wrapper is -:meth:`AdminSite.admin_view` (i.e. ``self.admin_site.admin_view`` inside a +:meth:`AdminSite.admin_view` (i.e. ``self.admin_site.admin_view`` inside a ``ModelAdmin`` instance); use it like so:: class MyModelAdmin(admin.ModelAdmin): @@ -1010,6 +1008,8 @@ information. ``InlineModelAdmin`` objects ============================ +.. class:: InlineModelAdmin + The admin interface has the ability to edit models on the same page as a parent model. These are called inlines. Suppose you have these two models:: diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index de56b0f0eecd..b6956512ad5e 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -112,7 +112,7 @@ Methods on ``ContentType`` instances Takes a set of valid :ref:`lookup arguments ` for the model the :class:`~django.contrib.contenttypes.models.ContentType` - represents, and does :ref:`a get() lookup ` on that model, + represents, and does :lookup:`a get() lookup ` on that model, returning the corresponding object. .. method:: models.ContentType.model_class() @@ -370,7 +370,7 @@ This enables the use of generic relations in forms and the admin. See the The :class:`~django.contrib.contenttypes.generic.GenericInlineModelAdmin` class inherits all properties from an - :class:`~django.contrib.admin.options.InlineModelAdmin` class. However, + :class:`~django.contrib.admin.InlineModelAdmin` class. However, it adds a couple of its own for working with the generic relation: .. attribute:: generic.GenericInlineModelAdmin.ct_field diff --git a/docs/ref/contrib/gis/testing.txt b/docs/ref/contrib/gis/testing.txt index 3401e4d76987..2e81510cd904 100644 --- a/docs/ref/contrib/gis/testing.txt +++ b/docs/ref/contrib/gis/testing.txt @@ -133,7 +133,7 @@ You will need to download the `initialization SQL`__ script for SpatiaLite:: If ``init_spatialite-2.3.sql`` is in the same path as your project's ``manage.py``, then all you have to do is:: - $ python manage.py test + $ python manage.py test Settings -------- @@ -166,9 +166,9 @@ must be used. To use this runner, configure :setting:`TEST_RUNNER` as follows:: .. note:: - In order to create a spatial database, the :setting:`DATABASE_USER` setting - (or :setting:`TEST_DATABASE_USER`, if optionally defined on Oracle) requires - elevated privileges. When using PostGIS or MySQL, the database user + In order to create a spatial database, the :setting:`USER` setting + (or :setting:`TEST_USER`, if optionally defined on Oracle) requires + elevated privileges. When using PostGIS or MySQL, the database user must have at least the ability to create databases. When testing on Oracle, the user should be a superuser. diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt index 89680150ff76..177da89eddbf 100644 --- a/docs/ref/contrib/index.txt +++ b/docs/ref/contrib/index.txt @@ -166,8 +166,9 @@ For more documentation, read the source code in ReStructured Text ----------------- -When using the `restructuredtext` markup filter you can define a :setting:`RESTRUCTUREDTEXT_FORMAT_SETTINGS` -in your django settings to override the default writer settings. See the `restructuredtext writer settings`_ for +When using the ``restructuredtext`` markup filter you can define a +:setting:`RESTRUCTUREDTEXT_FILTER_SETTINGS` in your django settings to override +the default writer settings. See the `restructuredtext writer settings`_ for details on what these settings are. .. _restructuredtext writer settings: http://docutils.sourceforge.net/docs/user/config.html#html4css1-writer diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 0c77c32c7a72..9442e5f28bd8 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -104,7 +104,7 @@ compilemessages Compiles .po files created with ``makemessages`` to .mo files for use with the builtin gettext support. See :doc:`/topics/i18n/index`. -Use the :djadminopt:`--locale`` option to specify the locale to process. +Use the :djadminopt:`--locale` option to specify the locale to process. If not provided, all locales are processed. Example usage:: diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index da0b24622a38..68208b3bfe93 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -291,8 +291,6 @@ A human-readable name for the field. If the verbose name isn't given, Django will automatically create it using the field's attribute name, converting underscores to spaces. See :ref:`Verbose field names `. -.. _model-field-types: - ``validators`` ------------------- @@ -303,6 +301,7 @@ underscores to spaces. See :ref:`Verbose field names `. A list of validators to run for this field.See the :doc:`validators documentation ` for more information. +.. _model-field-types: Field types =========== diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 02d105d3cb59..a42452d07d20 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -962,8 +962,6 @@ something *other than* a ``QuerySet``. These methods do not use a cache (see :ref:`caching-and-querysets`). Rather, they query the database each time they're called. -.. _get-kwargs: - ``get(**kwargs)`` ~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 834f6112a990..cb0e1afbf5a2 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -130,6 +130,22 @@ Default: ``'locmem://'`` The cache backend to use. See :doc:`/topics/cache`. +.. setting:: CACHE_MIDDLEWARE_ANONYMOUS_ONLY + +CACHE_MIDDLEWARE_ANONYMOUS_ONLY +------------------------------- + +Default: ``False`` + +If the value of this setting is ``True``, only anonymous requests (i.e., not +those made by a logged-in user) will be cached. Otherwise, the middleware +caches every page that doesn't have GET or POST parameters. + +If you set the value of this setting to ``True``, you should make sure you've +activated ``AuthenticationMiddleware``. + +See the :doc:`cache documentation ` for more information. + .. setting:: CACHE_MIDDLEWARE_KEY_PREFIX CACHE_MIDDLEWARE_KEY_PREFIX @@ -385,6 +401,17 @@ test database will use the name ``'test_' + DATABASE_NAME``. See :doc:`/topics/testing`. +.. setting:: TEST_USER + +TEST_USER +~~~~~~~~~ + +Default: ``None`` + +This is an Oracle-specific setting. + +The username to use when connecting to the Oracle database that will be used +when running tests. .. setting:: DATABASE_ROUTERS @@ -553,7 +580,7 @@ Default content type to use for all ``HttpResponse`` objects, if a MIME type isn't manually specified. Used with ``DEFAULT_CHARSET`` to construct the ``Content-Type`` header. -.. setting:: DEFAULT_FROM_EMAIL +.. setting:: DEFAULT_FILE_STORAGE DEFAULT_FILE_STORAGE -------------------- @@ -563,6 +590,8 @@ Default: ``'django.core.files.storage.FileSystemStorage'`` Default file storage class to be used for any file-related operations that don't specify a particular storage system. See :doc:`/topics/files`. +.. setting:: DEFAULT_FROM_EMAIL + DEFAULT_FROM_EMAIL ------------------ @@ -1166,6 +1195,21 @@ We don't list the default values here, because that would be profane. To see the default values, see the file `django/conf/global_settings.py`_. .. _django/conf/global_settings.py: http://code.djangoproject.com/browser/django/trunk/django/conf/global_settings.py + +.. setting:: RESTRUCTUREDTEXT_FILTER_SETTINGS + +RESTRUCTUREDTEXT_FILTER_SETTINGS +-------------------------------- + +Default: ``{}`` + +A dictionary containing settings for the ``restructuredtext`` markup filter from +the :doc:`django.contrib.markup application `. They override +the default writer settings. See the Docutils restructuredtext `writer settings +docs`_ for details. + +.. _writer settings docs: http://docutils.sourceforge.net/docs/user/config.html#html4css1-writer + .. setting:: ROOT_URLCONF ROOT_URLCONF diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 3c4e3b34e435..2ac4e653c491 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -700,7 +700,7 @@ Configuring the template system in standalone mode Normally, Django will load all the configuration information it needs from its own default configuration file, combined with the settings in the module given -in the :setting:`DJANGO_SETTINGS_MODULE` environment variable. But if you're +in the :envvar:`DJANGO_SETTINGS_MODULE` environment variable. But if you're using the template system independently of the rest of Django, the environment variable approach isn't very convenient, because you probably want to configure the template system in line with the rest of your application rather than diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 6e2ee8a21de7..b101d574bb34 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -310,7 +310,7 @@ but not gracefully. No details of the tests run before the interruption will be reported, and any test databases created by the run will not be destroyed. Running tests outside the test runner -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------- If you want to run tests outside of ``./manage.py test`` -- for example, from a shell prompt -- you will need to set up the test @@ -417,7 +417,7 @@ Other test conditions --------------------- Regardless of the value of the :setting:`DEBUG` setting in your configuration -file, all Django tests run with :setting:`DEBUG=False`. This is to ensure that +file, all Django tests run with :setting:`DEBUG`\=False. This is to ensure that the observed output of your code matches what will be seen in a production setting. From 2bbcb1a82132a469b5e5e8727b769e855a0e26b8 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 23 Aug 2010 08:12:17 +0000 Subject: [PATCH 085/902] [1.2.X] Fixed #13951 -- Corrected docstring in formtools wizard. Thanks to suzaku for the report, and lrekucki for the patch. Backport of r13630 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13631 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/formtools/wizard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django/contrib/formtools/wizard.py b/django/contrib/formtools/wizard.py index 02d8fd71d42c..32e27df57451 100644 --- a/django/contrib/formtools/wizard.py +++ b/django/contrib/formtools/wizard.py @@ -27,7 +27,7 @@ class FormWizard(object): def __init__(self, form_list, initial=None): """ Start a new wizard with a list of forms. - + form_list should be a list of Form classes (not instances). """ self.form_list = form_list[:] @@ -37,7 +37,7 @@ def __init__(self, form_list, initial=None): self.extra_context = {} # A zero-based counter keeping track of which step we're in. - self.step = 0 + self.step = 0 def __repr__(self): return "step: %d\nform_list: %s\ninitial_data: %s" % (self.step, self.form_list, self.initial) @@ -48,7 +48,7 @@ def get_form(self, step, data=None): def num_steps(self): "Helper method that returns the number of steps." - # You might think we should just set "self.form_list = len(form_list)" + # You might think we should just set "self.num_steps = len(form_list)" # in __init__(), but this calculation needs to be dynamic, because some # hook methods might alter self.form_list. return len(self.form_list) From 87cd047123307aa510b4ca3dd854d154f2b10ff8 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 23 Aug 2010 13:39:58 +0000 Subject: [PATCH 086/902] [1.2.X] Fixed #14084 -- Updated French translation. Thanks to stephaner. Backport of r13580 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13632 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/locale/fr/LC_MESSAGES/django.mo | Bin 78322 -> 78477 bytes django/conf/locale/fr/LC_MESSAGES/django.po | 204 ++++++++++---------- 2 files changed, 106 insertions(+), 98 deletions(-) diff --git a/django/conf/locale/fr/LC_MESSAGES/django.mo b/django/conf/locale/fr/LC_MESSAGES/django.mo index eae7521b800079af9a1dd24437702c65f9e2858c..e0c7259b82b8b1114a3078d6dec6630ce9a06e14 100644 GIT binary patch delta 24828 zcmYk^2YgTWzsK>D5Q#`)1hGQw9f>XW-h0oAy<^t)Gh^3?tyU{W6>UQ)rBvPr-SRk2 za0lWl*dGIMoyA)*l=u#2 z#J9+N9w)S$=! z+>09b5C$;6b3z6D88y*WOo=y84If||d}jHEJ>7-2M)m7taZl9515jJ+LEW-N=#O8b zc5EAF#>42*%C1vLfw#MNtci#pKw>>f=!3 zeT@D%2Gil>-t2#B3X4hT;aHElg59VAPM|uR#q@Xy)&3Fc3g4sJXXs-KK`o#tro_?~ zSHVEyx~N;$0=4kYeb|59qn;!*(Z{HJJkH{or~wmD9X>@}$>*pAZAA6ki)we+>Q7@J z@z1F7u48t5hwBb?DK6-%?P+1E`M2F$B+|Zpn4j>-hw=Q+@;8fl{Mh(+sGI zv!MFrMvYS(b!*FEer$@GXC&%vm@tt2-$G$EiKWhGIRFb(lb)F)oR5O>S7qjoR~vtT9EmsDHSg%2CT{%b`ONNB~=QCBn>w-C$_-i=)uzX4K~2LsC!*{q#L)y8pNNudYs!7GLa}U%6*z^ znr%=k?Ta;WJ!Zj&s0ji_^Er=|QCHj_V{jj8yf>H?(~sf938OGCj>2rX8jI=u-$NlY ziHE2L{(R-hqL?3>qpol?7Q=Pe27kdSSZtiT@PVjtCZQ%?fd%k848qQ946mXFe1VaeX1qILWmNrmjK(FX365iWyoy@z->7!kC%6x9Y1D!m zU>+QX9^K1j6m&07p|1QD>dLcEbg!%y<|A&4YCj(Ra2uw@U8wi}1lGr&u|MXV|Mh|LJ&ABUjJkrGs1E<2>ffVY&w$D9+mISt5{IGM^~a1j7`343 zsD8^a116$&csFV%f58FhH-)X$N8`{b?gSo8MjVe?=?u#!paxuqdL7rIAAV`^H>mnu z79TQCpvF0mx_~R_hc{8<+|z_wz*9@SLpAh^ckgKsYJdn-z91&YD2q#(6;Q8db@avN zs0Fn`P1G4RPA`iGp+1N_BPoPXn1Z?|3oKrbe#GCR2KpXT;9*q9lcFzk4Q4e`93}t?2ECqF(hkD;vnctZ|VL|c_QQvZ5Gu)@W0P6j& zjFqu9Y9Wg;09T+tuEW;2#p+Ycbax~OJ!+7ag3OE>Ag49RZ}}pq0ZU>aR>V|T$Kn>K zE&T|!)jhB(_CXffSguQ0ClCsQ4eKx)O+2^^1V>) zN1_%s9(4iJEk6&nkR_-cU19D(J?v*u7kYgb`>z3RS>i9$K+jS6ca~2v+sy}|CQgsK z$5~PB3Zt&5Bx;8$SzH%`iJMy-iyFT#s^1U~1zp(~48~7TEBh36<)5J%Zbx<8i<;mt zs@++OFJJ`m4OF{ipSV|=3KeHT?MN;R#{8%Y@szSc1q>lk9o3;3YT|Ylcf}y$zNlL^ z0=4kTs0A%T4Y&q1(FTh*qxx+_jk_P!{una8$2m`72#G7G0h-Qn8@54RVQ18ed!Pmy zYVibA`#I(!)C4QcMASkyqn@R2P!k_S^*iO2`~R~gE~Bpa57a;pEdR`WgSyAQbKL=g zPzwr0J!BE61;owa^#ldsM#^3GRexP`4%=wU9EX`kJVIjT5;4x)-fUXvG~-U!Q$26#HX3 zoPhcPV*!@OO{kyoZle~QY=Qgs1fV7gGxMS*DuLRe@~ClYS=`Y>AvK9!m9n~%mX22-a&egJfYgGHLmhXq!p|Pl+0cT@&+<+OF-#J1-E5CwT`2*Cw zO}^OO$_Ug$R0@@EjJg$lQ41M{+OY|!3yHV|3vM;1Jp#XQBS+?N_WCA%tV|M)i1`Zidsk= zvpMP(bXv*&XQt4XLN@P@^AQ4_sJEj)CU`+V4Ou za4+hLe?mQs7g0NT$3sC|^#pZAZ!ip#t#;$AsFmkNb%;c@D}x%avc)wlZh#uEDXM*Y z)CF|Ij5rMS9WV=3@0m+M11&|ZbR}v>l2A|gNz|2GLT&wZ)Yd&heKNj5)rYKc2h4zq zv!fQ87qy_Gs1Kep*a+(*JLhqhQK&@XI@ZP@EJ%D3 zHNi{NPW_9TC}f?xfE=i|p%|*ZD(WF@f_i&;uH*izVmt|ToMR1FV>05cm;#ef3)zKg zchK_ZQ49FZ`~x-Kebj}$MqN=SA!+i?tWAFyIUt8=zJk&!WlEPV3181ZAncl!0i`ud+ zsCGYMPJCu@#xLAkRTHDhcg0$`5NqOP9EQ0zxqtOqf_aIzV?26(vqYCK-S>VPwxr@S zY>Kb23D(=}zE;aoSH1)FL3IwZ<2}rUDYv)_DuVfl>tPujgj)DU48;Fo8@>O(P-sn} z7>~GCJ{9xeDb#!XH)?{^U%5LFi3Nx|Vk3;ll=w61nYo7k_=n~1V*v4UOo8uFx6Z2_#j~b||@)H4u^8gH1@kHZk+cvSy| z7_9gIQwm!77pQ@@pgvH(wfGonptBf^zoMSrJE(ykp|<`xYMe~pxEGWS>k{WfjXMxi z;|Oev)6o0=->?RcQ3Jd}?v3*nwN)*X+y%5l?NnDRjQuecE=5g{h+5EQ)PlERDDFYs z^Rt)(FQdkNo5cQW0H1AchhWqI8Bra=F$fD-Tnf_?S3zA#Gt`2*pmwI8+#faY$EbnF zpcXXK;-#ny-C*%H)Vv2h6x8qxYT~P?t-NDCw)`v9z`i@&Kdq$2(!|ZN0M0-yBnfrJ z-&=eNHSPuTchm*kN6qW`i-NZL9qOJ2>~!DXl9-LS8CJ(Q)Q%)!c})AAyO4%hk$4X3 zbKxXvL3dCKO1sNlKo-=k&4bDpcJm&mBn4e@CDc8wg=*LewZe{OU(^VoE?E@*}2H=_C{eb4@@u!DrIU^i+j52CL8gynxjE$9!_fcH=feu=*L z*5ZH7Z;50<7xD9Hnx}$a?4)rVy$DuwvRJa%4 z>~nuI+I7Hjs!)FxhhVyc{QU^yF#^wHd3=sqNc181UqoXtRPXNay4SZcE55_tnE9~#bv+5QF~75pg0}7;7Q|EN zhp$i*y~h-o;)uI5!Ki!~YU^{Lt}M#pau`iq5A)y{)WX)Fb}|Vy{sHv<@BbX7paD*q z7tHIJocvwXM2{^0FODS+IO;Au9<{)EsCG*%-iY~$zrt|5Xud#y;`GPZ|BMu}9doa| z1Zrg+&910{dz%As4e@YXikXkQKPe@luJACb{Y7ko*DwI1{^yQc&a8}TSMz`DzgE(K zgkHlIW;fJl^iYeZp>|>ks{IPoy-h@2@ov;TK7r}*4r+oos0H|*aBp2IRGa~IE3$hi zq@qw1)uEilRZtBZU=X&nxQp2r)qW^y$3~+*h$f&GxD?fYjn#jFfy7@~{cg-g>^Vpw zCxz=);d|0uX&`E0=`k21P%Dl^?NmjJ8)7hV2h@)A!SpyDwZMhur>GtM9MyjZvQUq6 zgo0Lf8r9)f^LNx0+_Crxdfy7mzeg>||CGDnjAjICoIB`+JW}gFxKpiS;!AUO*G3~jq0}zHSiCp{zp(dcoH?y1&eQ^ z#(9DzG3Y1mzwSxZpWNS88zKMW4C6oipzG80@F z7^Wj%3Nv9H)RxC$Fit={Gjlu?^wGG{DvqG8We&1F;6KMs4Ar z*c|;XyB{=dQTK8H7Q)Z5FrLB^_!_HVao zJxYDewIJ#Wt7AQk#|S)&W$-DM#zNQKzwN|gRpPyv1D)U9@BIj@LRcLudMHe$kQ)yu zfwwRx=DFdnv=(YhM`BUjhPvV#sP3Bx+$QJcH_R8I$2H)WG-5zfo84*7SYo z#sR2-Ls0!QS{#8|Z~@df#Vq!erJ#W-qdL|!8=5W6j;NjJjv9CzYM^PTotcN~zX&za zO4O}LwET8dzdaToLHc`~)0VhsUdLb>-a&n`y+D1*_&;(d%7_{$D{8{Ls2wYcx)tS6 z3#(yqL(~PfF*~99b@SG9|NB{DII5!uHNiB?&qpn28R|+mSbj6A{dUv@dr|!kp%!w= z>MvRT2F8(pfLcI@zt}0}ce<&7ea*qBfk&DXEk6@A@O;z&D^L?AVr$%rebN81yPzSM zns_9p#p$>Tm!o#9{1Yx#6W6Aod)o;0wD!a_=s|r@%r!Tehp{U8-%;NQxu3dkOF4`n zZj0S;6l#IjP#1I;weY9dA74M^{%h-c{q3%J0II=IbByIDqqcsA<>#OVT#UNH)fj|d zp>EY4)P?;IHQqVY=gP0x8N;8sF@%E z;yqOR_o#*WJ$EM#LFF@|<_SlAH0Q$fSiwU<6E{WeM0<;4E$)lDfPm8 z3aAUHZ#F}X*A}&qE~saq59%X54pZs(s#v;XSQ_qE$F4z-XGsENm+t|;E(#i)g>LM`MA)I+(` z@+VOfUqn6q*DSt|x}c}1e*c=mZ`gk|%=*SPKWcyysFlZ{7FZoMac#_m%}_hn7j;Dg zQJ;wWQ43G@)-@HXUnpv!*)b26z%Xp%p`Z=}%@L>_7;na-7BCCLaV}=SEvS2Z2)webf%M$Bft+bqj{0c6Kr{uE&{8L0h^M)$kkCEjVVLK@D^PweriB zzlqwZ`<8!V{)4)5pMTx<8BhxhN3}0zmh#qn_!_iCb=1UlQCrglb>;2No~R!_hoG)t zA?ovCCF+)JF!x&iJnD+Cn)fj?@hi-Q=^P&?mxn?r6|jvt8a2^MEQ$ND2tG&M<6J&I z-hacbg^|SlP+Ptn^^k2s?btrlh5U%RkTa+qxQu%EZlL$~|JM|>^}fD7-hb=KjCz_o znyau1@$c9gb0u>J9);SmDcBNcVKKac@t7{TkN5Y8rPz@85SCZo&&T;38~OQoyif5{ z5=B*!!pHl6%TWdA5GPvti_2~7xM?W?FXT5!79|1ZA9Igt*B>Zr^WkFJ8{fB zk6QS(0FOJsEfVVR7`^uhYZ0eQ>EnHv+M*`tkLoxC)qa#Y5%pHgzzAH5TG%esc!yB! zucG?jLM`Z_#|p1d4@CVd5%S2K%9&;qaNxR@pPh~_q?k$7>XKrj2Vx580VsP;2YG#wA124s4F{;8t|OO7f=hm zh8ph{>ef6)&G!$oaE}uh?Bo4}%Z=)gA2mTFYT_6ygdI>FHq;ds=&`2@9uJ zC+chX1!@7W&1C7_{z0e(g`?{8m_<=LQW|xEHBmd;9JQluJruM9v8Vz1p#~U&-q#S- zFy7)%Q0UT_}J5&Enp> zJh(;`sQ3JD)KeRp)yMmX!$R1DcrkXy_ZWd6Wpf>mx`k^{{dZ#?{0()1PPmWvk6byh zEMXlir1yUU1>J%#aWwA0Zdf9_`z^Kvb%i-{xC7KjeO(X5nz$IXV^{EF%ogE(lqaB` znboLUxfXS6x1es_4)p&1zsC}Xtif?qho3CIf_esSq27i^R{t6`Q1YDaGZBcYPm5|F zZuz{Zp9_kh#;t1BM~@OMDd?g4$n1eyd4JRdgHW&2aMVZdOw^9eMSW>4N44948fOa@ z!tJPWuA}C;i4E})YJnAU@&4;ItC7pc`v-v*sMqBHs=-;*0GDtuUO`=9licn{X$REn zIsmKVWYhwWqWYgiE$C;9FQXpbo2UzTo7>}lu=wY3JC;E$r~>Lhq$f z#UoH3wUbco7oo0n73zK8h#GGPYNw8%z5~vq+TZd}&=vn>zDG3-&g-j!2$)`A6N#V`fo%nFbTE59hTqc>T!-+gY&2rT|`~MJ=712 z&rz>mbV2uXq9f`dnt^@rB6h;+h1?IS&rtO@Q15?KVfSHdiCVyT)WW7?3cddcmRN?G zcrEIRx1#R#4vUYPXHZvu5%sX$HXooK#wVy}#=nSb2DCnuK zhrZYfbxYc!wr&7wqQR)AcQ~s39CJD9lW#p{!TqRb=rZa;?xG&TC#VbZDe8`w4n2B$ z!zgHFg-|{d;#kxb4z&7s)PyrpSG3OZJ5gJ_2ep7BmOqUe|2%3T*NXD~ z>os{yLKD45-RqD@ck5!zs;GhLpsuV5YM_p&{=HB;^|3k19EW=9CtAD+wX@4m7qT^y z_g{%0t--J6Rn&wxQ1|j4Y6qTJ-YMoTAOLlxp{NCfqZXJGb;}~n5>{W<>MNiYUe{xV z)~K!Qjv8n@>RFgpq~1FP+RUB<&G1I%6oEA z&{h=23Ro4jBjZpDNkHwyV$@cyLfxt))Bp!jS9-!ci+b9BLG{0ky3*^Y3wVV3h2Kus_RwG$tsu4Ekg;89&B#}0yc45jgS%4?NWM~>w# z&QjVQrtSxPjK6!h$t=IQr@V!;uXX&I{A=<-=kKId*~_4M%V<=n9zgicMS&13NZ{D}Gw$0x+QIjfV{z)U)-VNco|<9tZG zi}RG#@xc85G1-?Vr#+nqlHvzErxWKeyT)VWn$RgT=MwU-X?K`%8}j?{Ai00>8mEpW zoKvmrP4J)Av^hskkM01<`rKVb+|=f{@5?9mG6vRhnTqcyZ^q}u4d{@Ja!PE;=|iU( z`QnM(aSE+nhR1 zQJ0*$XIKE+a+c-P@ujJ9xc;gXD#x(iT9JANBJu(OMWZ45Y7jb3o%9!@_ffRdy$`$yhj24%Ma$xdD?Wq|8epw zjZ>KR{m5O@j{yD@#*w_r`77mKoF6mrSQ_19fFZ=+6YHpGs=S)Kj!fkGQ`W~qEz2b^ zRt)DgizBf-?atsO&SI8dryl_xQNhoo&O@AM6&hH_pXBu&(-7xUw~#g;ju!uE`#;O~ z!lIn|ks$Mb>eE{J6nVb(yvIp$p58QGVwE$@KIObC<~m(IiEO= zvmUt&ZfB=6@!z!FMEze3{01Kq7gvlwa1No(=bTl^o#CuT`5kR_jP(A$U~$-k!mKytS@r`Wh3xI(T0!Jo8g%^9P9 zy8iW4y!z0}{1+QDU=PkeXzWXyB{+(B0d+o<|0UO*SVvnI=Ns}m5;30oU?w@u`5WcI zH~~K#i-|o0`A<`l{HeexMLCTIpghzD{>cU`Y4Jj;chXMBO4{w>?CplmcJe2zt`qXt z1g8Nea9*ISV+j7tm;vs1?)@K5gH|@t3>ws-VGHZU)t(q}(Mk;O7&hfV49~jC1O1U8U(YCz8_m*~d$dBPXO|Bog+}Mo#?f&Q$Cq@!@zt z+=w_I=W*+F4?~D=F;EU`7)5S7!W5lDfdkHx>=~_b87B#z92h*vjpYu zIS8$N9^j}2W zlej+Rlh*zc?JCgz8x3ejSF=2AT70;2M58* z(3?ixsjNX+$7;^WULWFMd}vP=Ol46$^n#1 z({7}-RoDLH`cr>JfgKk-GzccC-x)99Myp6hULOM=jsR1B^w9AqXH9aassEibBkgMt*WzqW z`NL6;_J5JfhncLe>hfBHet6i*-`ZT>{Zg(o1J%O343HHc(cmGuV%BgF1HPp09CfKF zm!zD9Gn#T7?Y^;gt!+WY$p1?H9r7!zt|9Rh%8wc26UsI8FXsLvezs1jsaR-biq0a+ z>uFfq2AGhrr%jlrzO|T$b2TLANm`e~8Jx+P`VR49%Ht^4rri$CFRb5o^3%u{!6%#_ zQ%=Si!}`NT%)_a_dR0P|S)Zr%@Ri{BYa+NvzQC`jY;b=o{F2Qt5 zjHmr!TZ~f6sh`8C;~&nYoJTAkO1qVushGSAp7!P2vEqL^4`iao#1E`PRKkXxa?p>_omiZ4OfIPg%|Nx0?^gOval)`>YAE?LyPO zqrpI^0Wtiq48>+q4JtI!ytVHh*$5*s%NDxGux|UC4?dwo?o%R{YcP7^_ z;bey}zqe%PCfx0i)$@X;WlPl%a5RKTl!@o{?l@`$<3x* z1|M7Q1&(Ko@94kPakUy|#{8Bf1%#6!vF!Euan3HR9oGLsMI+(w+*myfeuR#B2`(a?-lyGJ z&cd8JZcu-h_M6?WAO<5bpF|m)>9+CyHQk5i delta 24753 zcmYk^1#}fx*T(S)Bmn{m1cC+$79a#jaCdhP?(R;|i#w$yK(V4NP_z`MxYGiCahD=3 z+M>nb`#*O!-|DP2{hhs!&Y78eleF(U{hsghJHDP9$$e)#920#VCp|XI<~V`=jx(*Q zvW~N_jpNMnah&~_lDKg@$BD!q*bL`kNqmW+7~S4+iefEHh!ZTHfhmZOVJf_a%;#}l zP$)zqSqI0-ftAf(n2UH0Ccu5>G4p>IO#TvTK@U+2Ox@8n55^EzKwa1Z%Wp?5>=fo= ze&-^EcqDu}ISv=@_?n4P6DP;`m=2R*Myt<_iHY;0CMaz-$7IB@7=)A259gYTP~(1& z37Owns{(F8O|%CS;Q>^`6W9dLSw2r^ccDd4{YqP01vPPP)E2i!?Z_ZZfYVSrHVf0@ z3iN1Y`zZM15%VM}e-20B6`YDqy6|k^UDUmNj_T*%)o}`7AZkJ7&=;$t>g%A!Ym5o7 z1t!N1UD^Mn6b6&ffD=(yumCl{T2zOP7=k-c?N6bu@CK^=EA%d;o4bHOOhi7F#bFpg zoC|f!3ZmMV?Z*D=9#tWsi5jEsaVv|vqpqkws>5*9m5fC#XfmqbB2>E-R=)uQh_|4| z+lLwPBxb|MsD8no?rtG1YGs*G9rB>Ix;ScqRV|LO`i`i3*$=glai|5(Gnb%tYNfdi zwIfF{08gXZd#+K?72QFt=z+!0%-5(Z`-obIe-C${lx8N>K)Fy8Mxol5MctZesD(B| zjo$;ckO9cN9%m$l)Fj5CIxKW6oOP&yH=#Q0L_IWz%!{ZAAEKU}cc=-1dOA*SOofTD zJPyFxsD&LwwL5`+djHQ+&;(bk;x4M=V{Ze;d4rnh6GmfDFL%evVmNVS)WAJa3m$}N za0F_iIj9|2WbsetcJ$TzzmI}GKn|lOx{cb2=NO3bd%Fgs7M2NBpBuF!C9S>=Y9TFA z3+aSfKu^>?ABeiuqcAB>K#vBTLqQFfp*pU{Al!(WXdh~ePos9~9%`U}P_OAL)Wq*l z{o?m=$4QR5wP`U2Mx*9wih3K`_F@0mQy4{J9_H)ouJkx+f^%3GuV69E-Oq74Vn7;~yitGlUn^=$LRa1ubwzzp`GJ-nff{%m zCdK(yzuM}5MlEQEx!3ZCQ9E)1)$b~5;zyVSUwbHIrr^sXngPR6164$=ygF(jP0Th} zh`0+@#zj~d?_pI;KhSaNV@Irq+p#1D4B~qNt6&M7fZ7qy0SZwRu45Pm4R)MTm=AUD zdSY1|kGg^*SO70zE)0luoCqw4`LQ(?#+g_XkDIB6a8bnV%~hC2@Bb4D$*J%k>Y5g{ z!bq%yT`(Q4M-6-&^Pn@#z0$l`oNzQUptB1@@jO<;XP6yJ40qqMwwRB2B&OB-zn+2` z95esM9K<1f_36q=qB?ZOCipFu#iyu+M~`&BPAj4&ZiP8A`vm>L&jTHJBl=-ai~FPM zhgm${oQ@i2Hfo$j=!ZX`##w_}z$T9+_MjRbLEY1nr~&@4{6q95erEB%=10_P={v#w zAvOfHpwy^|GNZ=HWpRGgttpQBVDeO^pnKBD5?#=bcra?95$KN-Q60ZRt$aRe;2%)! z)}#7uH}{|xa0u1@gvA%ln@B&8^A`nw8azXN;=Qrhf1*1;AZntNs0lKnCd_8_d9A*% zSq3XoUlp}u(^2EjM9s6rT!{(1?>_|%xD_?QKGcejcpLEgS^Z_KLjDd`#oUwJ_5)B8 z3`O0FNytZoGZ!^Zs>yaMP!D-7)GaQJfjoaseF}Qt+n7VmS(uCbdepbuB}|16Q17=h z#c|3&8q~s@U_xw#39vK9U~j8GirSHrR(}RPYIwyO+_nbyt-%x2fUhtBKcSw1L{r^3 zC2C8;1vUO`?63EK5d{s9e1_XFE$Rw0qgI>)HBcdo z%cI)YHDgc{v@$!P7Sap#%=AZ1JPy@whB?P8_y0QzTG98Yf!0}rE#@xsAZj7Mq8_e` zsD(bW`q!xbpD;f9f9)dP)AYMcU?K<|H93R+=R)Iu6q+`{aP+KGOsfk&aP zWE`sfRLqOBumB#!Jop;bKKnOryIiP#`B4vLG4$vsk_r^G@)oFtbU;nm8-sBK>fX;l z4X^;!ZkfgFP*=1W)o;JWzoYtHGw-ANzeFwk{Wsiy-7DXj?urtj7L)>&kHktCjfHS9 zdT)`r1$8C6Q42VVx;5ue3;EmXKcM>g$GNv62-Q9$j{T2EAv1{-m<^L-QPf9m4J?7} zP(Rx(LM?132H{@RM5oMasEHn;cIY{3oR1cV%yK_JGGQ|E`8*UfKv_%FMh)B=b!ELQ zAB(}nlQAhSKrLhqhTu=A2@j*T_J63YzlQR5%mx~ zw!Hth?ybmzT1X^n$BLq^q?F~WqOPzZ>Xx>!xEpH6`e8owU>@8c_5NR{5Q&NBy1(HR zMeRUu)IhOV87E*Ap2Xhx2_vxAJok6Id03YC62@TK`R;c^UkoRni8XKs@&mNvw}8de zr7)SoM7)BW}Eat?JXuA`ofJLW^w6+c5gtZz{}68}55T@WfxiQ2I+)I*#d z^;#GCj{R3+1PM(r12w^Hb0KO6mZK)xh`L2PP!pcQGI;7xc4-LTU;-EpZ05@++tg_bmT6YQTRjes6L7CGLO;QSFnXE+7i~4fQP4M-AM>;?}5vJEA7)jauMf)GZv3 zTIf>LLf50(?MLm%an!h1EPo3V>ixg(E%5N5o`H9$6(?Hm-ijuuD{YS&um@^_0Txe0 zUFi&qm!lTG1~uW&7H>ygz+THANAKVNf47QDO3>goZoo&Vffju44zvvQ3APILQ0=k! zl*M;Z3w(*X;t!~2Dewn(r_z|=sD%_n@8ADQQ^-J~I%)x3P!sn>Jrl8*3&)`*SdZGN zt*D6(qZV`l^*aA)^>0uQVS*Lz+mjL1uL!DNyg`rw2|To@sH~weD;0DMO(V6-}`oZosZ7ip7wUs*GG@f3m<9Kt7IX*0(PusP7jvQ(-Ub733^u{(7=wReWxfBUdDOF! z7>|07SED94f_d>C=ERVn+}E-cCL*4OdS>Qe0-SI8Wtfn7E&AgY^u=AMXJ9|7|55b5 z|EDQvfGgJEwl%nK@pIHbZ!G^A)jrWiHy@082GXD=jzrZL!XPY#>R%HBu>oq~ZPEMl ze@6=XKc1V;{?I1wzXmu-LLJUw61-{gV+oDDT_9@M}EPzx$=ab47v zwzjxCYT}`&b`w$a&h}8yRxUPITEzy`z&o%U9>c;Iu+4oN%Apq04Ryu+Egp{=_$zZR z>H?ObCSHNs>CLEH>e)*{@9!hj2S%dp{IG!Gs2%BsCGZ$(A-+4@e?L$e^|>$(bK+vu zf{vjUa2j=MuUh`Lfg=mg}Q)#ySV?_lA$DY`+JSVa0kUIij7IgVhPr^7sAr=Q zs$F~30(xUg9D@0925O$YsD=FQp`fiei(2_r)ByKU1HVK)3m;Jni@(Q>6QTwPLM~+L`Sbf(KCx zzl7>{7d7q^^98E?Thn*1cftJk9~87z=}|ioj(QgIVp|^`D%^vM_qjhAwL0K9WvL&C z{c$VS!ia#D8Np3_I#BtP*M`V^HIFGJBx<_d|~g!z?ileTk=|CYovaP1aC1H;Al{Cta3AKvi^q8XwKAXM?g|s2KXITLf=h`* zaUSl*W~I?WtmVPMA=abEQRV{$?9uk0C7XBZ;k1RJ7Z=XXL-*G3R>xUOpM!69~6gB zD?WqTkv}YcX7$cVcSnLy?|(Mb0*jetQLkGSRR89vg?2|RtUuD8|Nh4fRRh zR&cLT1J*$e&;-Ms2ghFMW~%viP>>IYMgVJ32&h$c22wP6QV~? zc`$`AOohp?2TEJoR1Zu*wsBx~M7WxQvi(a0# z_y4ms^gH8r2*h+WNQs&#%B+a$*aS6jd(=*JN9|x=)I`H9o{Ac0HWtL6P`BhBhU4=y z?EhH`Y54~~;5tuG6RbGr4!jYY6K}(+nBqL|Jhnhhe9}CN$%!vx8hnh}a=#1i+ms#k z%oIR}mjYi#)NtT~(`PrC~{6f@L|7`UKt^POE!q1=3b+N!<6A6_B`>*uco>Y`h@W5& zY;xKCXg-WJh*Mp0ZHM{w{;zin&L5bM1|e77e}7*QwS|+h0se>~_z??Y@@wwbZWYW; z+z$)jA}ot%uo|Yg&c`*jM2)i>Yh&;YjnDmWLqYdulsO-DWt*`&zQ!;tf7AWluss$g zUVts|43@)ux7-EyMSb6YgJp3umcmz<6$|{~#xa=LLt-`sw!+zl#qlBL!R)u)D{X)p zU=nu6BUlsj-*Ff2K@B_)bKq{&PTs?GnDVasr=2LwNjwI1%h#YMg2EXJb@3x+!rJ%T zEAE9+#51rm?z4P?Klz*|&WWMe4%6dEEP@L#49{BqON=B=_LqB$N@028zJIa*MJa41 zkqe)o?q%xx?zdD0)RlL{D!3f|@Db`ge})Os=K+5Q#sD0GV^B}`2UPn+58ZZ2F%@wd zOoaI!vj4i`VkFXFQ}o4t)^ITT6MuyPI2(K764b;&kK8R!jj4&tpazaX?Mz!tfFm#( z$Kzz&hKaG9=ds(N7RDzLgBrNG*#UJ0JPptq=I#pWzoYt{xBBZA-$(U(ii!08 zzqN|^f4iU6iBStkiy9y!szW$xf_$ij6+$hfjMZ1Sd;{!Fz9p*vNz_iAH?Ns@(EIoQ zhbqwEg;jh&4IKX;cYq+&gsCtF!>~KXq84-)lj1`R#&@_FlRR~IY%OX5+flc6FX~~v zgq~y+o>I^Uh0inB^kxw(N4`GlyI>aTZCQ56oc&kBCsy$awe|02(Cj- zd;ql*CoDdrj!IlZ4Rjkd@I%YLu=uUj$9v_zEdi(jLs3_f-HbL%p%z{Z)vg6<{7$Ix zds)3_Fa>Sp6x5Z?#SmPKYPcEIaW`t9!>9%Sj(SMXV-CE9>Ywmm_ccz6>X#AKKHTz= zsCFgXdXH1yEjZOt6V^AITfPJ8p7%gq(Gb)Er=b?K6gANrbCczNLEVDGsQx$2$EbPU zVyxc(c(2`ohM@);k6Q6`48l0nl`gY*9qI~pnFmn={)*~<7WE8VMSY~-LfxXfsD4k) z*OK|2PZTtO-y3&;ATt%JLq=46cGT;d7xfyJK~2;e)vg1oU3b*P1FSw4bwMLAD~?6& z$V&A7_kWuy=n8kCo`s_ppEqxzcH$9g;CHAAKcRQwZ&?g+5Z1-o7>$cj?N6iHokuO? z25P77zGeS)MUO4<5w#G%ckV)xq3(4?RK5Ue;?k%stz>aM)D<;BT|h^(KdRkmb2_U3 zJk)}gykq~h!XHU!f_0b%x1+Z9H0p{jpgsw6ymwdL)$EJvHw3lNahMI~VQSoiy3z~g z4b%?&X+HK)&;p)g2K*OOV#o*g9!Fpe;_8?c=b%=;6ZL^~5cSD;5w(+lp(cETTA1_E z{n0EjD$a@eU@L%X@2NyVThtgeP&?GU?uY6y8r5MM>h)WMYPS)!L;Eom9zorL>!<}j zM2-6bwWFU=?b3X5Z$Um+k5ia}1}cSGd3kG49ko^UEFWXGMqPPli-(~WI2P4@wzfUcaUD#GktoMJvx4`!_W~SmY>I&YYK45%5ySF6B%!bMrM_qA6vmT};ZiQJe z)?8rjG5NrA#WSca_wn)ZK4ihD9ScWYNp93u7e?(s zdDO#K6}6*nP+Q*xE1(DUFdsyZ3V!i?oU&L2V{j5`;9ICIdxVYfIp)Kv@qN7iGC39_ zh(BX3jPUjG{^>OqmlJQsOxVQF$NLLSEY>Gpfiv)}pO43BMPahPkMlj=$7(n|f!pCC zYODPcx>uGIb!$?ht}LU);iv`YGmE1ZUJ2E|CaQg7^xh(@OdOlg{O)xILCe@)ba8knt6&q`O+4i3WHxWYrB2!%7Kr#K+M z9Uwhwf~=_5ClBVr$~YDKm>*DEJSnlefK{ls<0sTJatQUFAGi7|sBv$bp2rmQH2#a) zfiy|nhbbc}jzC>m6l%bt7MDWr7NZ8NiMln7Q4_XCExZrL$H}Pn(@_uWEM#7fvxI^k zq64Tad4ifKFwkw7+{}pj0E$2jR0_RYj@s&)s0GBJ7SIOuHuc5kI2%jjGc1NtK|W3$ z=6AYLC`@7}YJg{`D}RTY$VuwP2~bxWXmM)Pm1Mv$%!Rsz)lsis3)FZ6P|wZ?)VR}7 z3!kZa=6B{((6`od)UDWvZSgGX$_gcO2P$h;L+wO;)Ic3k?{`m&$DtNJ4K-mLY9aH? zWtLxq-oO8Eq@aOzT7v_)lK3~&K>dT=frg^4WEAQHX@s zz4wVz_;|lvv!li>k%ISM9cz%#Q`!QxfHr1VRDD0xg2q~YiW!I6k%g!$T!Y&39jJxw zMeV>*RR1%m@h_wIHS|zW!^f6*iE8)>HIQG3Yhp}K9E_ou*W&7!ow&2b(^0Av`ZngUJ zSdTby8Xxa}WYQA15^qB-v{zd9)=j~s%A;QMrs=#7t;ZQcp$Cbtu@ip8HrOH5eXS0g zf1>VTg7oeHnK2u2In;tWVNM*6MR7ew;9sa)kUWEr{wIS@2JC?IaDv|dPZV^8<1@Mg zY(#y3U%^WF5w&F%GWj@za183R{4MI4@egzFWdQ2lhM;a;2Gp(0YH@_sN1^V0F~xfS zD^SogP!shwG_(e7Py=;C4b;c#2cz1Lwft1n51%tp1FtkUn!lj>A2v^*7Je4J|Nj3X z1-(w!Q6Ig}P+Rsd>Pmewy9)_I4HSaf+ECO$l~EH{NBtbo5VgQ%sMl;Y_Qaj2w?UJTW~ z2I`6%n(a~T`r#DxcqnMVfb2fre_0%Yde}zdceoq1L(Rk80b8RM+6A@6{ZSJPL*2qD zsPBSBsD*4n?ch;VyK7ee2sN)KUJiGlBxVSzB0Z`@IL2ThtcugI2wp(73(Dy(Fb(R8 z)1&g)&3sm047H$As0*lrCG`E@jDlXjIT(otP!G{R*bPhN^6~zRxC+Yc1&!~wL<#sPP1e59gPj87lW+Bv-mqI->HO+dcU(Fh$2JT_@ zN4=&)QCmL>b*0~0{zue9y#eFlFQ{9x2jlDgKTAOqT|_;-*H9f^nogwq$(IDvkq<{b zMCDM=KyB1R*ckQjc0!Fe81?WDM=fjyY6n(W{YLbt<5mi~;v=XlJZ}vip(cEax}wB+ z+<`Nowm1uF0g;w3h?<}nY9SR---?Y;^R!3Z>i(!5T%3pZUxgJUG|)QKm2E){bO1HL zNz_|#)x2rmK|S?cPdP6s@4_-RAVp^w?dEarpxl-* z_~F<4?W)^bide@p>isxVhFjt^jr5@CC>OW1TBs*8HF^ggaqgs6j}Kpe&Y#wU(5b+* zSuE~>t*HNUOe5aGS%Jg~rqWRsJJIGC=M&h(d|iDpS#}?*ReSs z#N)}B%fLFWP_d2j8hk}ugAVa1C&Y%FpQ)QdK7!8GIX@EX(G0}4Se-V9F(q*o&XM%} za`d$FO4{hq{2OTVv7$HRv82H_oP6Ipe^9ZId=j;?V+}5*egKxHzA9%k%6cc-$Bn2F z>iL(9K72OYR1}?`7%dTTXUZcPtr>0Zaq2ipU3}_ZU^vEb7UtBk+Eh7%{=xAh?aI)1 zFXgeE&xjkTf}^r-#MuP2Vs2ppw2-;k?7{WMI3{4nNp;MB1Mf4A~&oJ@THwqtiW zbp8CmN^p*3DbA(DeNe|h^8HxgD9)M0y*aCrOW}5Q+7Lgd?T^$yVc@s;m^cdm#yy<< zX|tTO47pRB^vsfg$CP*^{ZSn?{^P)lb*If{Hg^TA81*76W$B#59gan>iRuJWFjT ze61%?`XG!v#T3Ao5=rabuExTFF4il8_o-qb@aotjOp)==idK;G-zlOO{PIr z8aA*F2bHHj6=xqhT_S&ia#8XrIOlSH&smwen&j6~-;8oOx>dC?mQml@ozOWzJ^|&Q z$?=iSw*~a~27Kx>fQ~ertH?E?;RFV`L3s>05A~hM{Xn^v^~q0J$9&G)ao6jn_OzzQ z*EUi?n|6k`4Wp62LOBQdp|-qSIVv)BYlt|0#R_<`Iz+Wkp>DCh4~b|;q= z>yp3spZ0CY*JeKbRvg`iiftsm91n?W5ohN-W}WV1An{!W%47}mlG{Q#F)p|M)o`%& zQL{{xJI0-^pTe_@n){splI_KrpYku9`)QGrQI}#E{`Z(ez!T>br>?BpQ!a>mh<~DO zTfEEpf_w-jqE1H?mlj2N1#Mmu&yUYd&P#FxjdTpNi7I1na!1{?^S}mZPA-_UBJoqs zmDWB!W4xuDoO2?5Zg4)ZIt?+OJ|nR;x$K-PiLY{g`QUa6cmv)*Dt#GDM`y|-C=cOG zW((jCfle&>?$j@$oXpyuqyKE;j>Od{pRo3qX;+-~KWjic+MC5`Q{Q6~E0>ziS7@Ax zigv_9tZp#nWR&x8{!a}#N>Q%Md7Sn+h_li+Bjur#7h@$g;)ro^-s1vWlv4bK(izO@ zGc@jxhN(U6>5`Q2tTh)I8NM93;rNtuBe}**laBK$c^%ELiQ3Ws8)9F|>p68)qg^w~ zc`2W@_E%|F)9Uk6{)4)%dJ3i!bfHmuD$7&Wv4nFhxzFU%VO<7nOnEBj2TmP6xY^n( zZb_Rs+UnTK$zOV%dYolx6UCX9vmtHv(Jz6wmHxr2UY_Jy8+05t;2dKE<+J=b6?_CF*9;=QrZ|)~+D!23uRDdQjh;`s-FN&g=6y2}%7fcOHMVN+0sAh`$^~ zY(Z&QtPkZ>RD@HG)=AslrIBT7fQW0lmiq0&`KhUtU4d98p-XyiBG(+ncMeSGAj^#W_Tm&a^#-si|@!yn( zQ?5k2Eu5=3b!;L(fqVr1!#RMm4`)%%FUMzQyvTWuK3zz5rCnmT-s7yIB8e?Y9Zu7z z91V(+E6v%B@*>VJM+~{I2_{=&B<&B`Vw9Rs{a2hi-g3_4JYsP_+AZWv#N=)8WV}o~ zJOAn2i-~FxKe7&afmHa%bOHb|^xkkiKDSyW~o$;P?z9RP?cXR$2pI--7{b%BvRDR^l z%*7m}(|?bA#N$Yu<7`5GD;&rmUyd5qegV1Bj1iyn|6djw{T{cjc~;NA)}tpQ)Kgb{ zX}L_4Pms!GaY^D*oO2l|5BXwh&vAtM4CHkr#LPH=HrdE`v{mfGXmT~EziGMQ`rG0K zg2L9hEw-RhOFBk!7N*=eu5XJJU1KTrwN_*3v7NXG)4aB!np)j+;*Fdo>HX!{K-*db z0ko-N`5+le2GBMErw%{Pl9bci zm@$?gMB66xOGA9ea+SzUrCbRAw%kh`#TeV@zs_=R>35X)3}*^`88@{B=qFGeM`;*I zt|9{^qdeW}FHkp-@-EIe%KzeI`Ygq!_?lcx&T;f>N8F!$797qP7jcg*APxESoIeu> z#pB~_yHyk*Stl;KRSHi+GG&PTIbYEA%aNO*M-$Y?Vbq18j!@cvPduFRKAb|Fp1MNx zeN3(ZMTip19>3gJ|@jrX5<=n%so{^WIh zAYN^QDE^aj82wsPo)Z_?I@FV$)@R6mQnejb$mz&IOC4)%G?mxauv7eJjcz{lX3!>WhG`N4j_@y+CL!I#a8kc;bu6;QKn{{eX#_G{a#e~-2UdiN_HRvu`=DJeQ!2KeA_y!PtdkeS$zVN{vRi6GnxPZ diff --git a/django/conf/locale/fr/LC_MESSAGES/django.po b/django/conf/locale/fr/LC_MESSAGES/django.po index 412c2cb53559..e9ec95ea9f9d 100644 --- a/django/conf/locale/fr/LC_MESSAGES/django.po +++ b/django/conf/locale/fr/LC_MESSAGES/django.po @@ -10,9 +10,9 @@ msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-05-14 11:01+0200\n" -"PO-Revision-Date: 2010-04-17 00:18+0200\n" -"Last-Translator: David Larlet \n" +"POT-Creation-Date: 2010-08-09 12:11+0200\n" +"PO-Revision-Date: 2010-08-09 14:38+0200\n" +"Last-Translator: Stéphane Raimbault \n" "Language-Team: French \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -72,7 +72,7 @@ msgid "Spanish" msgstr "Espagnol" #: conf/global_settings.py:57 -msgid "Argentinean Spanish" +msgid "Argentinian Spanish" msgstr "Espagnol argentin" #: conf/global_settings.py:58 @@ -168,98 +168,102 @@ msgid "Macedonian" msgstr "Macédonien" #: conf/global_settings.py:81 +msgid "Malayalam" +msgstr "Malayâlam" + +#: conf/global_settings.py:82 msgid "Mongolian" msgstr "Mongole" -#: conf/global_settings.py:82 +#: conf/global_settings.py:83 msgid "Dutch" msgstr "Hollandais" -#: conf/global_settings.py:83 +#: conf/global_settings.py:84 msgid "Norwegian" msgstr "Norvégien" -#: conf/global_settings.py:84 +#: conf/global_settings.py:85 msgid "Norwegian Bokmal" msgstr "Norvégien Bokmal" -#: conf/global_settings.py:85 +#: conf/global_settings.py:86 msgid "Norwegian Nynorsk" msgstr "Norvégien Nynorsk" -#: conf/global_settings.py:86 +#: conf/global_settings.py:87 msgid "Polish" msgstr "Polonais" -#: conf/global_settings.py:87 +#: conf/global_settings.py:88 msgid "Portuguese" msgstr "Portugais" -#: conf/global_settings.py:88 +#: conf/global_settings.py:89 msgid "Brazilian Portuguese" msgstr "Portugais brésilien" -#: conf/global_settings.py:89 +#: conf/global_settings.py:90 msgid "Romanian" msgstr "Roumain" -#: conf/global_settings.py:90 +#: conf/global_settings.py:91 msgid "Russian" msgstr "Russe" -#: conf/global_settings.py:91 +#: conf/global_settings.py:92 msgid "Slovak" msgstr "Slovaque" -#: conf/global_settings.py:92 +#: conf/global_settings.py:93 msgid "Slovenian" msgstr "Slovène" -#: conf/global_settings.py:93 +#: conf/global_settings.py:94 msgid "Albanian" msgstr "Albanais" -#: conf/global_settings.py:94 +#: conf/global_settings.py:95 msgid "Serbian" msgstr "Serbe" -#: conf/global_settings.py:95 +#: conf/global_settings.py:96 msgid "Serbian Latin" msgstr "Serbe latin" -#: conf/global_settings.py:96 +#: conf/global_settings.py:97 msgid "Swedish" msgstr "Suédois" -#: conf/global_settings.py:97 +#: conf/global_settings.py:98 msgid "Tamil" msgstr "Tamoul" -#: conf/global_settings.py:98 +#: conf/global_settings.py:99 msgid "Telugu" msgstr "Télougou" -#: conf/global_settings.py:99 +#: conf/global_settings.py:100 msgid "Thai" msgstr "Thaï" -#: conf/global_settings.py:100 +#: conf/global_settings.py:101 msgid "Turkish" msgstr "Turc" -#: conf/global_settings.py:101 +#: conf/global_settings.py:102 msgid "Ukrainian" msgstr "Ukrainien" -#: conf/global_settings.py:102 +#: conf/global_settings.py:103 msgid "Vietnamese" msgstr "Vietnamien" -#: conf/global_settings.py:103 +#: conf/global_settings.py:104 msgid "Simplified Chinese" msgstr "Chinois simplifié" -#: conf/global_settings.py:104 +#: conf/global_settings.py:105 msgid "Traditional Chinese" msgstr "Chinois traditionnel" @@ -311,15 +315,15 @@ msgstr "Ce mois-ci" msgid "This year" msgstr "Cette année" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "Yes" msgstr "Oui" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:478 msgid "No" msgstr "Non" -#: contrib/admin/filterspecs.py:154 forms/widgets.py:469 +#: contrib/admin/filterspecs.py:154 forms/widgets.py:478 msgid "Unknown" msgstr "Inconnu" @@ -697,7 +701,7 @@ msgid "Filter" msgstr "Filtre" #: contrib/admin/templates/admin/delete_confirmation.html:10 -#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:302 +#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:300 msgid "Delete" msgstr "Supprimer" @@ -796,7 +800,7 @@ msgstr "" #: contrib/admin/templates/admin/login.html:19 msgid "Username:" -msgstr "Nom d'utilisateur :" +msgstr "Nom d'utilisateur :" #: contrib/admin/templates/admin/login.html:22 msgid "Password:" @@ -859,7 +863,7 @@ msgstr "Enregistrer et ajouter un nouveau" msgid "Save and continue editing" msgstr "Enregistrer et continuer les modifications" -#: contrib/admin/templates/admin/auth/user/add_form.html:5 +#: contrib/admin/templates/admin/auth/user/add_form.html:6 msgid "" "First, enter a username and password. Then, you'll be able to edit more user " "options." @@ -867,6 +871,10 @@ msgstr "" "Saisissez tout d'abord un nom d'utilisateur et un mot de passe. Vous pourrez " "ensuite modifier plus d'options." +#: contrib/admin/templates/admin/auth/user/add_form.html:8 +msgid "Enter a username and password." +msgstr "Saisissez un nom d'utilisateur et un mot de passe." + #: contrib/admin/templates/admin/auth/user/change_password.html:28 #, python-format msgid "Enter a new password for the user %(username)s." @@ -1441,8 +1449,8 @@ msgstr "message" msgid "Logged out" msgstr "Déconnecté" -#: contrib/auth/management/commands/createsuperuser.py:23 -#: core/validators.py:120 forms/fields.py:428 +#: contrib/auth/management/commands/createsuperuser.py:24 +#: core/validators.py:120 forms/fields.py:427 msgid "Enter a valid e-mail address." msgstr "Entrez une adresse de courriel valide." @@ -1511,7 +1519,7 @@ msgid "Email address" msgstr "Adresse électronique" #: contrib/comments/forms.py:95 contrib/flatpages/admin.py:8 -#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1101 +#: contrib/flatpages/models.py:7 db/models/fields/__init__.py:1112 msgid "URL" msgstr "URL" @@ -1563,7 +1571,7 @@ msgstr "commentaire" msgid "date/time submitted" msgstr "date et heure soumises" -#: contrib/comments/models.py:60 db/models/fields/__init__.py:896 +#: contrib/comments/models.py:60 db/models/fields/__init__.py:907 msgid "IP address" msgstr "adresse IP" @@ -4513,26 +4521,26 @@ msgstr "sites" msgid "Enter a valid value." msgstr "Saisissez une valeur valide." -#: core/validators.py:87 forms/fields.py:529 +#: core/validators.py:87 forms/fields.py:528 msgid "Enter a valid URL." msgstr "Saisissez une URL valide." -#: core/validators.py:89 forms/fields.py:530 +#: core/validators.py:89 forms/fields.py:529 msgid "This URL appears to be a broken link." msgstr "Cette URL semble être cassée." -#: core/validators.py:123 forms/fields.py:873 +#: core/validators.py:123 forms/fields.py:877 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Ce champ ne doit contenir que des lettres, des nombres, des tirets bas _ et " "des traits d'union." -#: core/validators.py:126 forms/fields.py:866 +#: core/validators.py:126 forms/fields.py:870 msgid "Enter a valid IPv4 address." msgstr "Saisissez une adresse IPv4 valide." -#: core/validators.py:129 db/models/fields/__init__.py:572 +#: core/validators.py:129 db/models/fields/__init__.py:575 msgid "Enter only digits separated by commas." msgstr "Saisissez uniquement des chiffres séparés par des virgules." @@ -4543,13 +4551,13 @@ msgstr "" "Assurez-vous que cette valeur est %(limit_value)s (actuellement %(show_value)" "s)." -#: core/validators.py:153 forms/fields.py:205 forms/fields.py:257 +#: core/validators.py:153 forms/fields.py:204 forms/fields.py:256 #, python-format msgid "Ensure this value is less than or equal to %(limit_value)s." msgstr "" "Assurez-vous que cette valeur est inférieure ou égale à %(limit_value)s." -#: core/validators.py:158 forms/fields.py:206 forms/fields.py:258 +#: core/validators.py:158 forms/fields.py:205 forms/fields.py:257 #, python-format msgid "Ensure this value is greater than or equal to %(limit_value)s." msgstr "" @@ -4602,134 +4610,134 @@ msgstr "Ce champ ne peut pas être vide." msgid "Field of type: %(field_type)s" msgstr "Champ de type : %(field_type)s" -#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:852 -#: db/models/fields/__init__.py:961 db/models/fields/__init__.py:972 -#: db/models/fields/__init__.py:999 +#: db/models/fields/__init__.py:451 db/models/fields/__init__.py:863 +#: db/models/fields/__init__.py:972 db/models/fields/__init__.py:983 +#: db/models/fields/__init__.py:1010 msgid "Integer" msgstr "Entier" -#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:850 +#: db/models/fields/__init__.py:455 db/models/fields/__init__.py:861 msgid "This value must be an integer." msgstr "Cette valeur doit être un entier." -#: db/models/fields/__init__.py:490 +#: db/models/fields/__init__.py:493 msgid "This value must be either True or False." msgstr "Cette valeur doit être soit vraie (True) soit fausse (False)." -#: db/models/fields/__init__.py:492 +#: db/models/fields/__init__.py:495 msgid "Boolean (Either True or False)" msgstr "Booléen (soit vrai ou faux)" -#: db/models/fields/__init__.py:539 db/models/fields/__init__.py:982 +#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:993 #, python-format msgid "String (up to %(max_length)s)" msgstr "Chaîne de caractère (jusqu'à %(max_length)s)" -#: db/models/fields/__init__.py:567 +#: db/models/fields/__init__.py:570 msgid "Comma-separated integers" msgstr "Des entiers séparés par une virgule" -#: db/models/fields/__init__.py:581 +#: db/models/fields/__init__.py:584 msgid "Date (without time)" msgstr "Date (sans l'heure)" -#: db/models/fields/__init__.py:585 +#: db/models/fields/__init__.py:588 msgid "Enter a valid date in YYYY-MM-DD format." msgstr "Saisissez une date valide au format AAAA-MM-JJ." -#: db/models/fields/__init__.py:586 +#: db/models/fields/__init__.py:589 #, python-format msgid "Invalid date: %s" msgstr "Date non valide : %s" -#: db/models/fields/__init__.py:667 +#: db/models/fields/__init__.py:670 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format." msgstr "" "Saisissez une date et une heure valides au format AAAA-MM-JJ HH:MM[:ss[." "uuuuuu]]." -#: db/models/fields/__init__.py:669 +#: db/models/fields/__init__.py:672 msgid "Date (with time)" msgstr "Date (avec l'heure)" -#: db/models/fields/__init__.py:735 +#: db/models/fields/__init__.py:738 msgid "This value must be a decimal number." msgstr "Cette valeur doit être un nombre décimal." -#: db/models/fields/__init__.py:737 +#: db/models/fields/__init__.py:740 msgid "Decimal number" msgstr "Nombre décimal" -#: db/models/fields/__init__.py:792 +#: db/models/fields/__init__.py:795 msgid "E-mail address" msgstr "Adresse électronique" -#: db/models/fields/__init__.py:799 db/models/fields/files.py:220 +#: db/models/fields/__init__.py:810 db/models/fields/files.py:220 #: db/models/fields/files.py:331 msgid "File path" msgstr "Chemin vers le fichier" -#: db/models/fields/__init__.py:822 +#: db/models/fields/__init__.py:833 msgid "This value must be a float." msgstr "Cette valeur doit être un nombre réel." -#: db/models/fields/__init__.py:824 +#: db/models/fields/__init__.py:835 msgid "Floating point number" msgstr "Nombre à virgule flottante" -#: db/models/fields/__init__.py:883 +#: db/models/fields/__init__.py:894 msgid "Big (8 byte) integer" msgstr "Grand entier (8 octets)" -#: db/models/fields/__init__.py:912 +#: db/models/fields/__init__.py:923 msgid "This value must be either None, True or False." msgstr "Cette valeur doit être nulle (None), vraie (True) ou fausse (False)." -#: db/models/fields/__init__.py:914 +#: db/models/fields/__init__.py:925 msgid "Boolean (Either True, False or None)" msgstr "Booléen (soit vrai, faux ou nul)" -#: db/models/fields/__init__.py:1005 +#: db/models/fields/__init__.py:1016 msgid "Text" msgstr "Texte" -#: db/models/fields/__init__.py:1021 +#: db/models/fields/__init__.py:1032 msgid "Time" msgstr "Heure" -#: db/models/fields/__init__.py:1025 +#: db/models/fields/__init__.py:1036 msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgstr "Saisissez une heure valide au format HH:MM[:ss[.uuuuuu]]." -#: db/models/fields/__init__.py:1109 +#: db/models/fields/__init__.py:1128 msgid "XML text" msgstr "Texte XML" -#: db/models/fields/related.py:799 +#: db/models/fields/related.py:801 #, python-format msgid "Model %(model)s with pk %(pk)r does not exist." msgstr "Le modèle %(model)s avec la clef primaire %(pk)r n'existe pas." -#: db/models/fields/related.py:801 +#: db/models/fields/related.py:803 msgid "Foreign Key (type determined by related field)" msgstr "Clé étrangère (type défini par le champ lié)" -#: db/models/fields/related.py:918 +#: db/models/fields/related.py:921 msgid "One-to-one relationship" msgstr "Relation un à un" -#: db/models/fields/related.py:980 +#: db/models/fields/related.py:983 msgid "Many-to-many relationship" msgstr "Relation plusieurs à plusieurs" -#: db/models/fields/related.py:1000 +#: db/models/fields/related.py:1003 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" "Maintenez appuyé « Ctrl », ou « Commande (touche pomme) » sur un Mac, pour en " "sélectionner plusieurs." -#: db/models/fields/related.py:1061 +#: db/models/fields/related.py:1064 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -4743,55 +4751,55 @@ msgstr[1] "" msgid "This field is required." msgstr "Ce champ est obligatoire." -#: forms/fields.py:204 +#: forms/fields.py:203 msgid "Enter a whole number." msgstr "Saisissez un nombre entier." -#: forms/fields.py:235 forms/fields.py:256 +#: forms/fields.py:234 forms/fields.py:255 msgid "Enter a number." msgstr "Saisissez un nombre." -#: forms/fields.py:259 +#: forms/fields.py:258 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Assurez-vous qu'il n'y a pas plus de %s chiffres au total." -#: forms/fields.py:260 +#: forms/fields.py:259 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Assurez-vous qu'il n'y a pas plus de %s chiffres après la virgule." -#: forms/fields.py:261 +#: forms/fields.py:260 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Assurez-vous qu'il n'y a pas plus de %s chiffres avant la virgule." -#: forms/fields.py:323 forms/fields.py:838 +#: forms/fields.py:322 forms/fields.py:837 msgid "Enter a valid date." msgstr "Saisissez une date valide." -#: forms/fields.py:351 forms/fields.py:839 +#: forms/fields.py:350 forms/fields.py:838 msgid "Enter a valid time." msgstr "Saisissez une heure valide." -#: forms/fields.py:377 +#: forms/fields.py:376 msgid "Enter a valid date/time." msgstr "Saisissez une date et une heure valides." -#: forms/fields.py:435 +#: forms/fields.py:434 msgid "No file was submitted. Check the encoding type on the form." msgstr "" "Aucun fichier n'a été soumis. Vérifiez le type d'encodage du formulaire." -#: forms/fields.py:436 +#: forms/fields.py:435 msgid "No file was submitted." msgstr "Aucun fichier n'a été soumis." -#: forms/fields.py:437 +#: forms/fields.py:436 msgid "The submitted file is empty." msgstr "Le fichier soumis est vide." -#: forms/fields.py:438 +#: forms/fields.py:437 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -4799,7 +4807,7 @@ msgstr "" "Assurez-vous que ce nom de fichier ne contient pas plus de %(max)d " "caractères (actuellement %(length)d caractères)." -#: forms/fields.py:473 +#: forms/fields.py:472 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -4807,17 +4815,17 @@ msgstr "" "Téléversez une image valide. Le fichier que vous avez transféré n'est pas " "une image ou bien est corrompu." -#: forms/fields.py:596 forms/fields.py:671 +#: forms/fields.py:595 forms/fields.py:670 #, python-format msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Sélectionnez un choix valide. %(value)s n'en fait pas partie." -#: forms/fields.py:672 forms/fields.py:734 forms/models.py:1002 +#: forms/fields.py:671 forms/fields.py:733 forms/models.py:1002 msgid "Enter a list of values." msgstr "Saisissez une liste de valeurs." # Si « : » est requis, créer un ticket -#: forms/formsets.py:298 forms/formsets.py:300 +#: forms/formsets.py:296 forms/formsets.py:298 msgid "Order" msgstr "Ordre" @@ -4868,28 +4876,28 @@ msgstr "Sélectionnez un choix valide ; %s n'en fait pas partie." msgid "\"%s\" is not a valid value for a primary key." msgstr "« %s » n'est pas une valeur correcte pour une clé primaire." -#: template/defaultfilters.py:776 +#: template/defaultfilters.py:780 msgid "yes,no,maybe" msgstr "oui, non, peut-être" -#: template/defaultfilters.py:807 +#: template/defaultfilters.py:811 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d octet" msgstr[1] "%(size)d octets" -#: template/defaultfilters.py:809 +#: template/defaultfilters.py:813 #, python-format msgid "%.1f KB" msgstr "%.1f Ko" -#: template/defaultfilters.py:811 +#: template/defaultfilters.py:815 #, python-format msgid "%.1f MB" msgstr "%.1f Mo" -#: template/defaultfilters.py:812 +#: template/defaultfilters.py:816 #, python-format msgid "%.1f GB" msgstr "%.1f Go" @@ -5100,7 +5108,7 @@ msgstr "nov." msgid "Dec." msgstr "déc." -#: utils/text.py:130 +#: utils/text.py:136 msgid "or" msgstr "ou" From 06a35609a17c99512685e8cd65afb26490a72a2a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 23 Aug 2010 13:51:49 +0000 Subject: [PATCH 087/902] [1.2.X] Fixed #14154 -- Corrected grammar error in settings docs. Thanks to d00gs for the report. Backport of r13633 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13634 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index cb0e1afbf5a2..b5556deac8a7 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -222,7 +222,7 @@ DATABASES Default: ``{}`` (Empty dictionary) A dictionary containing the settings for all databases to be used with -Django. It is a nested dictionary who's contents maps database aliases +Django. It is a nested dictionary whose contents maps database aliases to a dictionary containing the options for an individual database. The :setting:`DATABASES` setting must configure a ``default`` database; From 0ed363dd1a4c0a352a60f9c8f7835005c20227c0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 24 Aug 2010 02:27:22 +0000 Subject: [PATCH 088/902] [1.2.X] Fixed #14159 -- Corrected more potential uses of relative paths in tests. Thanks to Alex Gaynor for the patch. Backport of r13636 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13637 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/forms/fields.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index 990a9f74cf0e..e4f2c261c91d 100644 --- a/tests/regressiontests/forms/fields.py +++ b/tests/regressiontests/forms/fields.py @@ -766,13 +766,13 @@ def test_combofield_64(self): # FilePathField ############################################################### def test_filepathfield_65(self): - path = forms.__file__ + path = os.path.abspath(forms.__file__) path = os.path.dirname(path) + '/' - assert fix_os_paths(path).endswith('/django/forms/') + self.assertTrue(fix_os_paths(path).endswith('/django/forms/')) def test_filepathfield_66(self): path = forms.__file__ - path = os.path.dirname(path) + '/' + path = os.path.dirname(os.path.abspath(path)) + '/' f = FilePathField(path=path) f.choices = [p for p in f.choices if p[0].endswith('.py')] f.choices.sort() @@ -787,13 +787,13 @@ def test_filepathfield_66(self): ] for exp, got in zip(expected, fix_os_paths(f.choices)): self.assertEqual(exp[1], got[1]) - assert got[0].endswith(exp[0]) + self.assertTrue(got[0].endswith(exp[0])) self.assertRaisesErrorWithMessage(ValidationError, "[u'Select a valid choice. fields.py is not one of the available choices.']", f.clean, 'fields.py') assert fix_os_paths(f.clean(path + 'fields.py')).endswith('/django/forms/fields.py') def test_filepathfield_67(self): path = forms.__file__ - path = os.path.dirname(path) + '/' + path = os.path.dirname(os.path.abspath(path)) + '/' f = FilePathField(path=path, match='^.*?\.py$') f.choices.sort() expected = [ @@ -807,10 +807,10 @@ def test_filepathfield_67(self): ] for exp, got in zip(expected, fix_os_paths(f.choices)): self.assertEqual(exp[1], got[1]) - assert got[0].endswith(exp[0]) + self.assertTrue(got[0].endswith(exp[0])) def test_filepathfield_68(self): - path = forms.__file__ + path = os.path.abspath(forms.__file__) path = os.path.dirname(path) + '/' f = FilePathField(path=path, recursive=True, match='^.*?\.py$') f.choices.sort() @@ -827,7 +827,7 @@ def test_filepathfield_68(self): ] for exp, got in zip(expected, fix_os_paths(f.choices)): self.assertEqual(exp[1], got[1]) - assert got[0].endswith(exp[0]) + self.assertTrue(got[0].endswith(exp[0])) # SplitDateTimeField ########################################################## From 188c6b3e9a99e7a5b53ebb57de0b8467c1131eee Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Thu, 26 Aug 2010 11:10:45 +0000 Subject: [PATCH 089/902] [1.2.X] Fixed #14172: Corrected misspelling of explicitly. Thanks 3point2. r13638 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13639 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/admin/actions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index f3bdf1a82f50..86a5355b28d0 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -292,7 +292,7 @@ Disabling a site-wide action site-wide. If, however, you need to re-enable a globally-disabled action for one - particular model, simply list it explicitally in your ``ModelAdmin.actions`` + particular model, simply list it explicitly in your ``ModelAdmin.actions`` list:: # Globally disable delete selected From 35411fd7da0bfce6605d810c9844889f90cfb01b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 27 Aug 2010 13:58:36 +0000 Subject: [PATCH 090/902] [1.2.X] Fixed #14116 -- Added a flag to enable CSRF checks in the test client. Thanks to jon@licq.org for the suggestion. Backport of r13640 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13642 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/client.py | 10 +++++++--- docs/ref/contrib/csrf.txt | 7 +++++++ docs/topics/testing.txt | 13 +++++++++++++ tests/modeltests/test_client/models.py | 24 ++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/django/test/client.py b/django/test/client.py index cceed8d9f60e..08e3ff6b71cd 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -55,6 +55,10 @@ class ClientHandler(BaseHandler): Uses the WSGI interface to compose requests, but returns the raw HttpResponse object """ + def __init__(self, enforce_csrf_checks=True, *args, **kwargs): + self.enforce_csrf_checks = enforce_csrf_checks + super(ClientHandler, self).__init__(*args, **kwargs) + def __call__(self, environ): from django.conf import settings from django.core import signals @@ -71,7 +75,7 @@ def __call__(self, environ): # CsrfViewMiddleware. This makes life easier, and is probably # required for backwards compatibility with external tests against # admin views. - request._dont_enforce_csrf_checks = True + request._dont_enforce_csrf_checks = not self.enforce_csrf_checks response = self.get_response(request) # Apply response middleware. @@ -169,8 +173,8 @@ class Client(object): contexts and templates produced by a view, rather than the HTML rendered to the end-user. """ - def __init__(self, **defaults): - self.handler = ClientHandler() + def __init__(self, enforce_csrf_checks=False, **defaults): + self.handler = ClientHandler(enforce_csrf_checks) self.defaults = defaults self.cookies = SimpleCookie() self.exc_info = None diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index 979c221581f0..c32dd73986f0 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -398,6 +398,13 @@ set a flag on requests which relaxes the middleware and the ``csrf_protect`` decorator so that they no longer rejects requests. In every other respect (e.g. sending cookies etc.), they behave the same. +If, for some reason, you *want* the test client to perform CSRF +checks, you can create an instance of the test client that enforces +CSRF checks:: + + >>> from django.test import Client + >>> csrf_client = Client(enforce_csrf_checks=True) + Limitations =========== diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index b101d574bb34..c5f8f0040d3b 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -572,6 +572,19 @@ Note a few important things about how the test client works: This black magic (essentially a patching of Django's template system in memory) only happens during test running. + * By default, the test client will disable any CSRF checks + performed by your site. + + If, for some reason, you *want* the test client to perform CSRF + checks, you can create an instance of the test client that + enforces CSRF checks. To do this, pass in the + ``enforce_csrf_checks`` argument when you construct your + client:: + + >>> from django.test import Client + >>> csrf_client = Client(enforce_csrf_checks=True) + + .. _urllib: http://docs.python.org/library/urllib.html .. _urllib2: http://docs.python.org/library/urllib2.html diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index c51323d843cf..30520082da8f 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -21,6 +21,7 @@ """ from django.test import Client, TestCase +from django.conf import settings from django.core import mail class ClientTest(TestCase): @@ -433,3 +434,26 @@ def test_mass_mail_sending(self): self.assertEqual(mail.outbox[1].from_email, 'from@example.com') self.assertEqual(mail.outbox[1].to[0], 'second@example.com') self.assertEqual(mail.outbox[1].to[1], 'third@example.com') + +class CSRFEnabledClientTests(TestCase): + def setUp(self): + # Enable the CSRF middleware for this test + self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES + csrf_middleware_class = 'django.middleware.csrf.CsrfViewMiddleware' + if csrf_middleware_class not in settings.MIDDLEWARE_CLASSES: + settings.MIDDLEWARE_CLASSES += (csrf_middleware_class,) + + def tearDown(self): + settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES + + def test_csrf_enabled_client(self): + "A client can be instantiated with CSRF checks enabled" + csrf_client = Client(enforce_csrf_checks=True) + + # The normal client allows the post + response = self.client.post('/test_client/post_view/', {}) + self.assertEqual(response.status_code, 200) + + # The CSRF-enabled client rejects it + response = csrf_client.post('/test_client/post_view/', {}) + self.assertEqual(response.status_code, 403) From 2370a1038071bacd713721f5ad7a0811225bb040 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 27 Aug 2010 13:59:46 +0000 Subject: [PATCH 091/902] [1.2.X] Fixed #14156 -- Modified the way CSRF protection is applied to flatpages so that the flatpage middleware doesn't cause all POSTs resulting in 404s to turn into 403s. Thanks to patrys for the report. Backport of r13641 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13643 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../flatpages/fixtures/sample_flatpages.json | 32 +++++++++ django/contrib/flatpages/tests/__init__.py | 3 + django/contrib/flatpages/tests/csrf.py | 70 +++++++++++++++++++ django/contrib/flatpages/tests/middleware.py | 56 +++++++++++++++ .../flatpages/tests/templates/404.html | 1 + .../tests/templates/flatpages/default.html | 2 + .../tests/templates/registration/login.html | 0 django/contrib/flatpages/tests/urls.py | 8 +++ django/contrib/flatpages/tests/views.py | 50 +++++++++++++ django/contrib/flatpages/views.py | 14 +++- 10 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 django/contrib/flatpages/fixtures/sample_flatpages.json create mode 100644 django/contrib/flatpages/tests/__init__.py create mode 100644 django/contrib/flatpages/tests/csrf.py create mode 100644 django/contrib/flatpages/tests/middleware.py create mode 100644 django/contrib/flatpages/tests/templates/404.html create mode 100644 django/contrib/flatpages/tests/templates/flatpages/default.html create mode 100644 django/contrib/flatpages/tests/templates/registration/login.html create mode 100644 django/contrib/flatpages/tests/urls.py create mode 100644 django/contrib/flatpages/tests/views.py diff --git a/django/contrib/flatpages/fixtures/sample_flatpages.json b/django/contrib/flatpages/fixtures/sample_flatpages.json new file mode 100644 index 000000000000..79808c2d2b62 --- /dev/null +++ b/django/contrib/flatpages/fixtures/sample_flatpages.json @@ -0,0 +1,32 @@ +[ + { + "pk": 1, + "model": "flatpages.flatpage", + "fields": { + "registration_required": false, + "title": "A Flatpage", + "url": "/flatpage/", + "template_name": "", + "sites": [ + 1 + ], + "content": "Isn't it flat!", + "enable_comments": false + } + }, + { + "pk": 2, + "model": "flatpages.flatpage", + "fields": { + "registration_required": true, + "title": "Sekrit Flatpage", + "url": "/sekrit/", + "template_name": "", + "sites": [ + 1 + ], + "content": "Isn't it sekrit!", + "enable_comments": false + } + } +] \ No newline at end of file diff --git a/django/contrib/flatpages/tests/__init__.py b/django/contrib/flatpages/tests/__init__.py new file mode 100644 index 000000000000..2672dbfae80c --- /dev/null +++ b/django/contrib/flatpages/tests/__init__.py @@ -0,0 +1,3 @@ +from django.contrib.flatpages.tests.csrf import * +from django.contrib.flatpages.tests.middleware import * +from django.contrib.flatpages.tests.views import * diff --git a/django/contrib/flatpages/tests/csrf.py b/django/contrib/flatpages/tests/csrf.py new file mode 100644 index 000000000000..dca92bec5ee2 --- /dev/null +++ b/django/contrib/flatpages/tests/csrf.py @@ -0,0 +1,70 @@ +import os +from django.conf import settings +from django.test import TestCase, Client + +class FlatpageCSRFTests(TestCase): + fixtures = ['sample_flatpages'] + urls = 'django.contrib.flatpages.tests.urls' + + def setUp(self): + self.client = Client(enforce_csrf_checks=True) + self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES + flatpage_middleware_class = 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware' + csrf_middleware_class = 'django.middleware.csrf.CsrfViewMiddleware' + if csrf_middleware_class not in settings.MIDDLEWARE_CLASSES: + settings.MIDDLEWARE_CLASSES += (csrf_middleware_class,) + if flatpage_middleware_class not in settings.MIDDLEWARE_CLASSES: + settings.MIDDLEWARE_CLASSES += (flatpage_middleware_class,) + self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS + settings.TEMPLATE_DIRS = ( + os.path.join( + os.path.dirname(__file__), + 'templates' + ), + ) + + def tearDown(self): + settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES + settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS + + def test_view_flatpage(self): + "A flatpage can be served through a view, even when the middleware is in use" + response = self.client.get('/flatpage_root/flatpage/') + self.assertEquals(response.status_code, 200) + self.assertContains(response, "

        Isn't it flat!

        ") + + def test_view_non_existent_flatpage(self): + "A non-existent flatpage raises 404 when served through a view, even when the middleware is in use" + response = self.client.get('/flatpage_root/no_such_flatpage/') + self.assertEquals(response.status_code, 404) + + def test_view_authenticated_flatpage(self): + "A flatpage served through a view can require authentication" + response = self.client.get('/flatpage_root/sekrit/') + self.assertRedirects(response, '/accounts/login/?next=/flatpage_root/sekrit/') + + def test_fallback_flatpage(self): + "A flatpage can be served by the fallback middlware" + response = self.client.get('/flatpage/') + self.assertEquals(response.status_code, 200) + self.assertContains(response, "

        Isn't it flat!

        ") + + def test_fallback_non_existent_flatpage(self): + "A non-existent flatpage raises a 404 when served by the fallback middlware" + response = self.client.get('/no_such_flatpage/') + self.assertEquals(response.status_code, 404) + + def test_post_view_flatpage(self): + "POSTing to a flatpage served through a view will raise a CSRF error if no token is provided (Refs #14156)" + response = self.client.post('/flatpage_root/flatpage/') + self.assertEquals(response.status_code, 403) + + def test_post_fallback_flatpage(self): + "POSTing to a flatpage served by the middleware will raise a CSRF error if no token is provided (Refs #14156)" + response = self.client.post('/flatpage/') + self.assertEquals(response.status_code, 403) + + def test_post_unknown_page(self): + "POSTing to an unknown page isn't caught as a 403 CSRF error" + response = self.client.post('/no_such_page/') + self.assertEquals(response.status_code, 404) diff --git a/django/contrib/flatpages/tests/middleware.py b/django/contrib/flatpages/tests/middleware.py new file mode 100644 index 000000000000..04396d190e3c --- /dev/null +++ b/django/contrib/flatpages/tests/middleware.py @@ -0,0 +1,56 @@ +import os +from django.conf import settings +from django.test import TestCase + +class FlatpageMiddlewareTests(TestCase): + fixtures = ['sample_flatpages'] + urls = 'django.contrib.flatpages.tests.urls' + + def setUp(self): + self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES + flatpage_middleware_class = 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware' + if flatpage_middleware_class not in settings.MIDDLEWARE_CLASSES: + settings.MIDDLEWARE_CLASSES += (flatpage_middleware_class,) + self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS + settings.TEMPLATE_DIRS = ( + os.path.join( + os.path.dirname(__file__), + 'templates' + ), + ) + + def tearDown(self): + settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES + settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS + + def test_view_flatpage(self): + "A flatpage can be served through a view, even when the middleware is in use" + response = self.client.get('/flatpage_root/flatpage/') + self.assertEquals(response.status_code, 200) + self.assertContains(response, "

        Isn't it flat!

        ") + + def test_view_non_existent_flatpage(self): + "A non-existent flatpage raises 404 when served through a view, even when the middleware is in use" + response = self.client.get('/flatpage_root/no_such_flatpage/') + self.assertEquals(response.status_code, 404) + + def test_view_authenticated_flatpage(self): + "A flatpage served through a view can require authentication" + response = self.client.get('/flatpage_root/sekrit/') + self.assertRedirects(response, '/accounts/login/?next=/flatpage_root/sekrit/') + + def test_fallback_flatpage(self): + "A flatpage can be served by the fallback middlware" + response = self.client.get('/flatpage/') + self.assertEquals(response.status_code, 200) + self.assertContains(response, "

        Isn't it flat!

        ") + + def test_fallback_non_existent_flatpage(self): + "A non-existent flatpage raises a 404 when served by the fallback middlware" + response = self.client.get('/no_such_flatpage/') + self.assertEquals(response.status_code, 404) + + def test_fallback_authenticated_flatpage(self): + "A flatpage served by the middleware can require authentication" + response = self.client.get('/sekrit/') + self.assertRedirects(response, '/accounts/login/?next=/sekrit/') diff --git a/django/contrib/flatpages/tests/templates/404.html b/django/contrib/flatpages/tests/templates/404.html new file mode 100644 index 000000000000..5fd5f3cf3b6a --- /dev/null +++ b/django/contrib/flatpages/tests/templates/404.html @@ -0,0 +1 @@ +

        Oh Noes!

        \ No newline at end of file diff --git a/django/contrib/flatpages/tests/templates/flatpages/default.html b/django/contrib/flatpages/tests/templates/flatpages/default.html new file mode 100644 index 000000000000..c6323fd91de8 --- /dev/null +++ b/django/contrib/flatpages/tests/templates/flatpages/default.html @@ -0,0 +1,2 @@ +

        {{ flatpage.title }}

        +

        {{ flatpage.content }}

        \ No newline at end of file diff --git a/django/contrib/flatpages/tests/templates/registration/login.html b/django/contrib/flatpages/tests/templates/registration/login.html new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/django/contrib/flatpages/tests/urls.py b/django/contrib/flatpages/tests/urls.py new file mode 100644 index 000000000000..3cffd09d0f99 --- /dev/null +++ b/django/contrib/flatpages/tests/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import * + +# special urls for flatpage test cases +urlpatterns = patterns('', + (r'^flatpage_root', include('django.contrib.flatpages.urls')), + (r'^accounts/', include('django.contrib.auth.urls')), +) + diff --git a/django/contrib/flatpages/tests/views.py b/django/contrib/flatpages/tests/views.py new file mode 100644 index 000000000000..bb94d1e37500 --- /dev/null +++ b/django/contrib/flatpages/tests/views.py @@ -0,0 +1,50 @@ +import os +from django.conf import settings +from django.test import TestCase + +class FlatpageViewTests(TestCase): + fixtures = ['sample_flatpages'] + urls = 'django.contrib.flatpages.tests.urls' + + def setUp(self): + self.old_MIDDLEWARE_CLASSES = settings.MIDDLEWARE_CLASSES + flatpage_middleware_class = 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware' + if flatpage_middleware_class in settings.MIDDLEWARE_CLASSES: + settings.MIDDLEWARE_CLASSES = tuple(m for m in settings.MIDDLEWARE_CLASSES if m != flatpage_middleware_class) + self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS + settings.TEMPLATE_DIRS = ( + os.path.join( + os.path.dirname(__file__), + 'templates' + ), + ) + settings.DEBUG = True + def tearDown(self): + settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES + settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS + + def test_view_flatpage(self): + "A flatpage can be served through a view" + response = self.client.get('/flatpage_root/flatpage/') + self.assertEquals(response.status_code, 200) + self.assertContains(response, "

        Isn't it flat!

        ") + + def test_view_non_existent_flatpage(self): + "A non-existent flatpage raises 404 when served through a view" + response = self.client.get('/flatpage_root/no_such_flatpage/') + self.assertEquals(response.status_code, 404) + + def test_view_authenticated_flatpage(self): + "A flatpage served through a view can require authentication" + response = self.client.get('/flatpage_root/sekrit/') + self.assertRedirects(response, '/accounts/login/?next=/flatpage_root/sekrit/') + + def test_fallback_flatpage(self): + "A fallback flatpage won't be served if the middleware is disabled" + response = self.client.get('/flatpage/') + self.assertEquals(response.status_code, 404) + + def test_fallback_non_existent_flatpage(self): + "A non-existent flatpage won't be served if the fallback middlware is disabled" + response = self.client.get('/no_such_flatpage/') + self.assertEquals(response.status_code, 404) diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py index 336600328d04..88ef4da65e22 100644 --- a/django/contrib/flatpages/views.py +++ b/django/contrib/flatpages/views.py @@ -13,10 +13,13 @@ # when a 404 is raised, which often means CsrfViewMiddleware.process_view # has not been called even if CsrfViewMiddleware is installed. So we need # to use @csrf_protect, in case the template needs {% csrf_token %}. -@csrf_protect +# However, we can't just wrap this view; if no matching flatpage exists, +# or a redirect is required for authentication, the 404 needs to be returned +# without any CSRF checks. Therefore, we only +# CSRF protect the internal implementation. def flatpage(request, url): """ - Flat page view. + Public interface to the flat page view. Models: `flatpages.flatpages` Templates: Uses the template defined by the ``template_name`` field, @@ -30,6 +33,13 @@ def flatpage(request, url): if not url.startswith('/'): url = "/" + url f = get_object_or_404(FlatPage, url__exact=url, sites__id__exact=settings.SITE_ID) + return render_flatpage(request, f) + +@csrf_protect +def render_flatpage(request, f): + """ + Internal interface to the flat page view. + """ # If registration is required for accessing this page, and the user isn't # logged in, redirect to the login page. if f.registration_required and not request.user.is_authenticated(): From 38e9ca1e09bcbfdb5bf3c5bb906b7ba13efed3c6 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 27 Aug 2010 15:18:10 +0000 Subject: [PATCH 092/902] [1.2.X] Fixed #14164 -- Ensure that sitemap priorities aren't rendered with localized numerical formats. Thanks to dokterbob for the report, and vung for the draft patch. Backport of r13644 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13646 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sitemaps/__init__.py | 2 +- django/contrib/sitemaps/tests/__init__.py | 1 + django/contrib/sitemaps/tests/basic.py | 39 +++++++++++++++++++++++ django/contrib/sitemaps/tests/urls.py | 20 ++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 django/contrib/sitemaps/tests/__init__.py create mode 100644 django/contrib/sitemaps/tests/basic.py create mode 100644 django/contrib/sitemaps/tests/urls.py diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py index f877317f167a..45b85c81d0b5 100644 --- a/django/contrib/sitemaps/__init__.py +++ b/django/contrib/sitemaps/__init__.py @@ -69,7 +69,7 @@ def get_urls(self, page=1): 'location': loc, 'lastmod': self.__get('lastmod', item, None), 'changefreq': self.__get('changefreq', item, None), - 'priority': self.__get('priority', item, None) + 'priority': str(self.__get('priority', item, '')) } urls.append(url_info) return urls diff --git a/django/contrib/sitemaps/tests/__init__.py b/django/contrib/sitemaps/tests/__init__.py new file mode 100644 index 000000000000..c5b483cde2c8 --- /dev/null +++ b/django/contrib/sitemaps/tests/__init__.py @@ -0,0 +1 @@ +from django.contrib.sitemaps.tests.basic import * diff --git a/django/contrib/sitemaps/tests/basic.py b/django/contrib/sitemaps/tests/basic.py new file mode 100644 index 000000000000..695b84a0aecf --- /dev/null +++ b/django/contrib/sitemaps/tests/basic.py @@ -0,0 +1,39 @@ +from datetime import date +from django.conf import settings +from django.test import TestCase +from django.utils.formats import localize +from django.utils.translation import activate + + +class SitemapTests(TestCase): + urls = 'django.contrib.sitemaps.tests.urls' + + def setUp(self): + self.old_USE_L10N = settings.USE_L10N + + def tearDown(self): + settings.USE_L10N = self.old_USE_L10N + + def test_simple_sitemap(self): + "A simple sitemap can be rendered" + # Retrieve the sitemap. + response = self.client.get('/sitemaps/sitemap.xml') + # Check for all the important bits: + self.assertEquals(response.content, """ + +http://example.com/ticket14164%snever0.5 + +""" % date.today().strftime('%Y-%m-%d')) + + def test_localized_priority(self): + "The priority value should not be localized (Refs #14164)" + # Localization should be active + settings.USE_L10N = True + activate('fr') + self.assertEqual(u'0,3', localize(0.3)) + + # Retrieve the sitemap. Check that priorities + # haven't been rendered in localized format + response = self.client.get('/sitemaps/sitemap.xml') + self.assertContains(response, '0.5') + self.assertContains(response, '%s' % date.today().strftime('%Y-%m-%d')) diff --git a/django/contrib/sitemaps/tests/urls.py b/django/contrib/sitemaps/tests/urls.py new file mode 100644 index 000000000000..b29b5a6a4945 --- /dev/null +++ b/django/contrib/sitemaps/tests/urls.py @@ -0,0 +1,20 @@ +from datetime import datetime +from django.conf.urls.defaults import * +from django.contrib.sitemaps import Sitemap + +class SimpleSitemap(Sitemap): + changefreq = "never" + priority = 0.5 + location = '/ticket14164' + lastmod = datetime.now() + + def items(self): + return [object()] + +sitemaps = { + 'simple': SimpleSitemap, +} + +urlpatterns = patterns('django.contrib.sitemaps.views', + (r'^sitemaps/sitemap\.xml$', 'sitemap', {'sitemaps': sitemaps}), +) From c868c368eb7d84dfb75f59ba5cf99a601ac077f4 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 28 Aug 2010 02:44:24 +0000 Subject: [PATCH 093/902] [1.2.X] Refactored markup documentation to give it it's own home. Backport of r13647 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13649 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/index.txt | 37 +++-------------------------- docs/ref/contrib/markup.txt | 42 +++++++++++++++++++++++++++++++++ docs/ref/templates/builtins.txt | 2 +- 3 files changed, 46 insertions(+), 35 deletions(-) create mode 100644 docs/ref/contrib/markup.txt diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt index 177da89eddbf..90edf72c948d 100644 --- a/docs/ref/contrib/index.txt +++ b/docs/ref/contrib/index.txt @@ -33,6 +33,7 @@ those packages have. gis/index humanize localflavor + markup messages redirects sitemaps @@ -134,44 +135,12 @@ contains a ``USZipCodeField`` that you can use to validate U.S. zip codes. See the :doc:`localflavor documentation `. -.. _ref-contrib-markup: - markup ====== -A collection of template filters that implement common markup languages: - - * ``textile`` -- implements `Textile`_ -- requires `PyTextile`_ - * ``markdown`` -- implements `Markdown`_ -- requires `Python-markdown`_ - * ``restructuredtext`` -- implements `ReST (ReStructured Text)`_ - -- requires `doc-utils`_ - -In each case, the filter expects formatted markup as a string and returns a -string representing the marked-up text. For example, the ``textile`` filter -converts text that is marked-up in Textile format to HTML. - -To activate these filters, add ``'django.contrib.markup'`` to your -:setting:`INSTALLED_APPS` setting. Once you've done that, use -``{% load markup %}`` in a template, and you'll have access to these filters. -For more documentation, read the source code in -:file:`django/contrib/markup/templatetags/markup.py`. - -.. _Textile: http://en.wikipedia.org/wiki/Textile_%28markup_language%29 -.. _Markdown: http://en.wikipedia.org/wiki/Markdown -.. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText -.. _PyTextile: http://loopcore.com/python-textile/ -.. _Python-markdown: http://www.freewisdom.org/projects/python-markdown -.. _doc-utils: http://docutils.sf.net/ - -ReStructured Text ------------------ - -When using the ``restructuredtext`` markup filter you can define a -:setting:`RESTRUCTUREDTEXT_FILTER_SETTINGS` in your django settings to override -the default writer settings. See the `restructuredtext writer settings`_ for -details on what these settings are. +A collection of template filters that implement common markup languages -.. _restructuredtext writer settings: http://docutils.sourceforge.net/docs/user/config.html#html4css1-writer +See the :doc:`markup documentation `. messages ======== diff --git a/docs/ref/contrib/markup.txt b/docs/ref/contrib/markup.txt new file mode 100644 index 000000000000..f2c43fe25f60 --- /dev/null +++ b/docs/ref/contrib/markup.txt @@ -0,0 +1,42 @@ +===================== +django.contrib.markup +===================== + +.. module:: django.contrib.markup + :synopsis: A collection of template filters that implement common markup languages. + +Django provides template filters that implement the following markup +languages: + + * ``textile`` -- implements `Textile`_ -- requires `PyTextile`_ + * ``markdown`` -- implements `Markdown`_ -- requires `Python-markdown`_ + * ``restructuredtext`` -- implements `ReST (ReStructured Text)`_ + -- requires `doc-utils`_ + +In each case, the filter expects formatted markup as a string and +returns a string representing the marked-up text. For example, the +``textile`` filter converts text that is marked-up in Textile format +to HTML. + +To activate these filters, add ``'django.contrib.markup'`` to your +:setting:`INSTALLED_APPS` setting. Once you've done that, use +``{% load markup %}`` in a template, and you'll have access to these filters. +For more documentation, read the source code in +:file:`django/contrib/markup/templatetags/markup.py`. + +.. _Textile: http://en.wikipedia.org/wiki/Textile_%28markup_language%29 +.. _Markdown: http://en.wikipedia.org/wiki/Markdown +.. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText +.. _PyTextile: http://loopcore.com/python-textile/ +.. _Python-markdown: http://www.freewisdom.org/projects/python-markdown +.. _doc-utils: http://docutils.sf.net/ + +ReStructured Text +----------------- + +When using the ``restructuredtext`` markup filter you can define a +:setting:`RESTRUCTUREDTEXT_FILTER_SETTINGS` in your django settings to +override the default writer settings. See the `restructuredtext writer +settings`_ for details on what these settings are. + +.. _restructuredtext writer settings: http://docutils.sourceforge.net/docs/user/config.html#html4css1-writer diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index ac7fa576a15d..b5ce71e321e2 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2071,7 +2071,7 @@ A collection of template filters that implement these common markup languages: * Markdown * ReST (ReStructured Text) -See :ref:`ref-contrib-markup`. +See the :doc:`markup documentation `. django.contrib.webdesign ~~~~~~~~~~~~~~~~~~~~~~~~ From 37d5ec34b6032f1c64989b7b4b9581541273276a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 28 Aug 2010 06:13:41 +0000 Subject: [PATCH 094/902] [1.2.X] Removed a stray DEBUG=True test setting introduced in r13641. Backport of r13650 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13651 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/flatpages/tests/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/flatpages/tests/views.py b/django/contrib/flatpages/tests/views.py index bb94d1e37500..a9004209b7fe 100644 --- a/django/contrib/flatpages/tests/views.py +++ b/django/contrib/flatpages/tests/views.py @@ -18,7 +18,7 @@ def setUp(self): 'templates' ), ) - settings.DEBUG = True + def tearDown(self): settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS From be7f912ce75838aaa94c29967a334e443fa88aa0 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sat, 28 Aug 2010 11:30:28 +0000 Subject: [PATCH 095/902] [1.2.X] Fixed #14185 - improved example SQL for 'select' example. Thanks to Trindaz for the suggestion. Also fixed some references to 'lede' which is no longer part of the example Blog model. Backport of [13652] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13653 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/querysets.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index a42452d07d20..c1c586aada97 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -703,9 +703,10 @@ of the arguments is required, but you should use at least one of them. greater than Jan. 1, 2006. Django inserts the given SQL snippet directly into the ``SELECT`` - statement, so the resulting SQL of the above example would be:: + statement, so the resulting SQL of the above example would be something + like:: - SELECT blog_entry.*, (pub_date > '2006-01-01') + SELECT blog_entry.*, (pub_date > '2006-01-01') AS is_recent FROM blog_entry; @@ -859,7 +860,7 @@ deferred field will be retrieved from the database if you access that field You can make multiple calls to ``defer()``. Each call adds new fields to the deferred set:: - # Defers both the body and lede fields. + # Defers both the body and headline fields. Entry.objects.defer("body").filter(rating=5).defer("headline") The order in which fields are added to the deferred set does not matter. Calling ``defer()`` with a field name that has already been deferred is harmless (the field will still be deferred). @@ -919,7 +920,7 @@ immediately; the remainder are deferred. Thus, successive calls to ``only()`` result in only the final fields being considered:: # This will defer all fields except the headline. - Entry.objects.only("body", "lede").only("headline") + Entry.objects.only("body", "rating").only("headline") Since ``defer()`` acts incrementally (adding fields to the deferred list), you can combine calls to ``only()`` and ``defer()`` and things will behave From 174940cab2e689bc91388f4c6f3051331f41270d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 28 Aug 2010 12:31:12 +0000 Subject: [PATCH 096/902] [1.2.X] Fixed #14166 -- Modified the list of state choices in the Indian localflavor to be a list of tuples, not just a list of values. Thanks to gogna for the report and patch. Backport of r13656 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13657 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/localflavor/in_/in_states.py | 70 ++++++++++----------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/django/contrib/localflavor/in_/in_states.py b/django/contrib/localflavor/in_/in_states.py index 498efe706988..bb4a7482cac1 100644 --- a/django/contrib/localflavor/in_/in_states.py +++ b/django/contrib/localflavor/in_/in_states.py @@ -7,43 +7,43 @@ """ STATE_CHOICES = ( - 'KA', 'Karnataka', - 'AP', 'Andhra Pradesh', - 'KL', 'Kerala', - 'TN', 'Tamil Nadu', - 'MH', 'Maharashtra', - 'UP', 'Uttar Pradesh', - 'GA', 'Goa', - 'GJ', 'Gujarat', - 'RJ', 'Rajasthan', - 'HP', 'Himachal Pradesh', - 'JK', 'Jammu and Kashmir', - 'AR', 'Arunachal Pradesh', - 'AS', 'Assam', - 'BR', 'Bihar', - 'CG', 'Chattisgarh', - 'HR', 'Haryana', - 'JH', 'Jharkhand', - 'MP', 'Madhya Pradesh', - 'MN', 'Manipur', - 'ML', 'Meghalaya', - 'MZ', 'Mizoram', - 'NL', 'Nagaland', - 'OR', 'Orissa', - 'PB', 'Punjab', - 'SK', 'Sikkim', - 'TR', 'Tripura', - 'UA', 'Uttarakhand', - 'WB', 'West Bengal', + ('KA', 'Karnataka'), + ('AP', 'Andhra Pradesh'), + ('KL', 'Kerala'), + ('TN', 'Tamil Nadu'), + ('MH', 'Maharashtra'), + ('UP', 'Uttar Pradesh'), + ('GA', 'Goa'), + ('GJ', 'Gujarat'), + ('RJ', 'Rajasthan'), + ('HP', 'Himachal Pradesh'), + ('JK', 'Jammu and Kashmir'), + ('AR', 'Arunachal Pradesh'), + ('AS', 'Assam'), + ('BR', 'Bihar'), + ('CG', 'Chattisgarh'), + ('HR', 'Haryana'), + ('JH', 'Jharkhand'), + ('MP', 'Madhya Pradesh'), + ('MN', 'Manipur'), + ('ML', 'Meghalaya'), + ('MZ', 'Mizoram'), + ('NL', 'Nagaland'), + ('OR', 'Orissa'), + ('PB', 'Punjab'), + ('SK', 'Sikkim'), + ('TR', 'Tripura'), + ('UA', 'Uttarakhand'), + ('WB', 'West Bengal'), # Union Territories - 'AN', 'Andaman and Nicobar', - 'CH', 'Chandigarh', - 'DN', 'Dadra and Nagar Haveli', - 'DD', 'Daman and Diu', - 'DL', 'Delhi', - 'LD', 'Lakshadweep', - 'PY', 'Pondicherry', + ('AN', 'Andaman and Nicobar'), + ('CH', 'Chandigarh'), + ('DN', 'Dadra and Nagar Haveli'), + ('DD', 'Daman and Diu'), + ('DL', 'Delhi'), + ('LD', 'Lakshadweep'), + ('PY', 'Pondicherry'), ) STATES_NORMALIZED = { From ba876ea2cc10699f1d9ea8842f3982d8d97a2778 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 28 Aug 2010 13:19:28 +0000 Subject: [PATCH 097/902] [1.2.X] Fixed #13681 -- Added a commented out admindocs entry to the default INSTALLED_APPS for a new project. Thanks to elkan for the report, and andrews for the patch. Backport of r13658 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13661 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/project_template/settings.py | 2 ++ django/conf/project_template/urls.py | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py index ee4c9b5e9654..c49df24ce579 100644 --- a/django/conf/project_template/settings.py +++ b/django/conf/project_template/settings.py @@ -91,4 +91,6 @@ 'django.contrib.messages', # Uncomment the next line to enable the admin: # 'django.contrib.admin', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', ) diff --git a/django/conf/project_template/urls.py b/django/conf/project_template/urls.py index dfb49d3bdcac..3d0ff636a50a 100644 --- a/django/conf/project_template/urls.py +++ b/django/conf/project_template/urls.py @@ -8,8 +8,7 @@ # Example: # (r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')), - # Uncomment the admin/doc line below and add 'django.contrib.admindocs' - # to INSTALLED_APPS to enable admin documentation: + # Uncomment the admin/doc line below to enable admin documentation: # (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: From af0e3217ae4d2e34de7bb90cd84f4ceff48a327e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 28 Aug 2010 13:19:49 +0000 Subject: [PATCH 098/902] [1.2.X] Fixed #13723 -- Improved the legibility of hyperlinks included in admin validation error messages. Thanks to Sebastian Noack for the suggestion and patch. Backport of r13659 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13662 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/media/css/base.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/django/contrib/admin/media/css/base.css b/django/contrib/admin/media/css/base.css index 645f1725b5f5..9c1f71f810fc 100644 --- a/django/contrib/admin/media/css/base.css +++ b/django/contrib/admin/media/css/base.css @@ -478,6 +478,11 @@ ul.errorlist { background: red url(../img/admin/icon_alert.gif) 5px .3em no-repeat; } +.errorlist li a { + color: white; + text-decoration: underline; +} + td ul.errorlist { margin: 0 !important; padding: 0 !important; From 6fbd860fa890dff3226ddeac9e62c52af6e792e1 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 28 Aug 2010 13:20:11 +0000 Subject: [PATCH 099/902] [1.2.X] Fixed #12343 -- Added support for connection-by-socket to MySQL using the dbshell management command. Thanks to elyon001@gmail.com for the report, and sfllaw for the patch. Backport of r13660 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13663 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/mysql/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/django/db/backends/mysql/client.py b/django/db/backends/mysql/client.py index ff5b64d1e081..24dc642abe2c 100644 --- a/django/db/backends/mysql/client.py +++ b/django/db/backends/mysql/client.py @@ -24,7 +24,10 @@ def runshell(self): if passwd: args += ["--password=%s" % passwd] if host: - args += ["--host=%s" % host] + if '/' in host: + args += ["--socket=%s" % host] + else: + args += ["--host=%s" % host] if port: args += ["--port=%s" % port] if db: From dde1f13315e92266a1e411926ba4d372723f103a Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 30 Aug 2010 10:27:08 +0000 Subject: [PATCH 100/902] [1.2.X] Fixed #13759 - Multi-db docs have an example that isn't syntax highlighting Thanks to Tim Hatch for report and patch. Backport of [13664] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13665 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/multi-db.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index c6070504b97f..1a939b0e3a1e 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -238,7 +238,7 @@ master/slave relationship between the databases ``master``, ``slave1`` and return False return None - class MasterSlaveRouter(object): + class MasterSlaveRouter(object): """A router that sets up a simple master/slave configuration""" def db_for_read(self, model, **hints): From 57e6ee8507ba4311888bc35ebbea0b6f50516e5a Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 30 Aug 2010 10:31:56 +0000 Subject: [PATCH 101/902] Fixed #14189 - permalink docs import mistake Thanks to 7times9 for report and patch. Backport of [13666] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13667 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/instances.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index d0076593017a..8bb6cf9cc54c 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -515,7 +515,7 @@ the ``url`` function):: ...and then using that name to perform the reverse URL resolution instead of the view name:: - from django.db.models import permalink + from django.db import models @models.permalink def get_absolute_url(self): From 2c82a2aaf7e7d22fab5bfa181d153689c3d80c01 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 30 Aug 2010 12:33:45 +0000 Subject: [PATCH 102/902] [1.2.X] Added a missing (empty) models.py file required for the tests from r13644 to actually run. Backport of r13670 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13671 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sitemaps/models.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 django/contrib/sitemaps/models.py diff --git a/django/contrib/sitemaps/models.py b/django/contrib/sitemaps/models.py new file mode 100644 index 000000000000..7ff128fa692e --- /dev/null +++ b/django/contrib/sitemaps/models.py @@ -0,0 +1 @@ +# This file intentionally left blank \ No newline at end of file From 62355d8253c299936b7df268f2792b5260c661b9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 30 Aug 2010 13:25:22 +0000 Subject: [PATCH 103/902] [1.2.X] Fixed #13798 -- Added connection argument to the connection_created signal. Thanks to liangent for the report, and Alex Gaynor for the patch. Backport of r13672 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13674 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../gis/db/backends/spatialite/base.py | 2 +- django/db/backends/mysql/base.py | 2 +- django/db/backends/oracle/base.py | 2 +- django/db/backends/postgresql/base.py | 5 +- .../db/backends/postgresql_psycopg2/base.py | 2 +- django/db/backends/signals.py | 2 +- django/db/backends/sqlite3/base.py | 2 +- tests/regressiontests/backends/tests.py | 65 ++++++++----------- 8 files changed, 36 insertions(+), 46 deletions(-) diff --git a/django/contrib/gis/db/backends/spatialite/base.py b/django/contrib/gis/db/backends/spatialite/base.py index d419dab5e122..729fc152e7f2 100644 --- a/django/contrib/gis/db/backends/spatialite/base.py +++ b/django/contrib/gis/db/backends/spatialite/base.py @@ -51,7 +51,7 @@ def _cursor(self): self.connection.create_function("django_extract", 2, _sqlite_extract) self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc) self.connection.create_function("regexp", 2, _sqlite_regexp) - connection_created.send(sender=self.__class__) + connection_created.send(sender=self.__class__, connection=self) ## From here on, customized for GeoDjango ## diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index e94e24bff940..a39c41f8d8a7 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -297,7 +297,7 @@ def _cursor(self): self.connection = Database.connect(**kwargs) self.connection.encoders[SafeUnicode] = self.connection.encoders[unicode] self.connection.encoders[SafeString] = self.connection.encoders[str] - connection_created.send(sender=self.__class__) + connection_created.send(sender=self.__class__, connection=self) cursor = CursorWrapper(self.connection.cursor()) return cursor diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 369e65baf744..0cf26c406da1 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -384,7 +384,7 @@ def _cursor(self): # Django docs specify cx_Oracle version 4.3.1 or higher, but # stmtcachesize is available only in 4.3.2 and up. pass - connection_created.send(sender=self.__class__) + connection_created.send(sender=self.__class__, connection=self) if not cursor: cursor = FormatStylePlaceholderCursor(self.connection) return cursor diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index a1c858bd8ff4..f84ad1da6105 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -135,8 +135,9 @@ def _cursor(self): if settings_dict['PORT']: conn_string += " port=%s" % settings_dict['PORT'] self.connection = Database.connect(conn_string, **settings_dict['OPTIONS']) - self.connection.set_isolation_level(1) # make transactions transparent to all cursors - connection_created.send(sender=self.__class__) + # make transactions transparent to all cursors + self.connection.set_isolation_level(1) + connection_created.send(sender=self.__class__, connection=self) cursor = self.connection.cursor() if new_connection: if set_tz: diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 29b7e7ff1a52..ce4e48330eb5 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -136,7 +136,7 @@ def _cursor(self): self.connection = Database.connect(**conn_params) self.connection.set_client_encoding('UTF8') self.connection.set_isolation_level(self.isolation_level) - connection_created.send(sender=self.__class__) + connection_created.send(sender=self.__class__, connection=self) cursor = self.connection.cursor() cursor.tzinfo_factory = None if new_connection: diff --git a/django/db/backends/signals.py b/django/db/backends/signals.py index a8079d0d76fd..c16a63f9f67f 100644 --- a/django/db/backends/signals.py +++ b/django/db/backends/signals.py @@ -1,3 +1,3 @@ from django.dispatch import Signal -connection_created = Signal() +connection_created = Signal(providing_args=["connection"]) diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index bc97f5cfd80c..1ab255762769 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -176,7 +176,7 @@ def _cursor(self): self.connection.create_function("django_extract", 2, _sqlite_extract) self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc) self.connection.create_function("regexp", 2, _sqlite_regexp) - connection_created.send(sender=self.__class__) + connection_created.send(sender=self.__class__, connection=self) return self.connection.cursor(factory=SQLiteCursorWrapper) def close(self): diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 6a26a608eb3f..4984cdf4f773 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -5,6 +5,7 @@ import unittest from django.db import backend, connection, DEFAULT_DB_ALIAS from django.db.backends.signals import connection_created +from django.db.backends.postgresql import version as pg_version from django.conf import settings from django.test import TestCase @@ -88,46 +89,34 @@ def test_bad_parameter_count(self): self.assertRaises(Exception, cursor.executemany, query, [(1,2,3),]) self.assertRaises(Exception, cursor.executemany, query, [(1,),]) +class PostgresVersionTest(TestCase): + def assert_parses(self, version_string, version): + self.assertEqual(pg_version._parse_version(version_string), version) -def connection_created_test(sender, **kwargs): - print 'connection_created signal' - -__test__ = {'API_TESTS': """ -# Check Postgres version parsing ->>> from django.db.backends.postgresql import version as pg_version - ->>> pg_version._parse_version("PostgreSQL 8.3.1 on i386-apple-darwin9.2.2, compiled by GCC i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5478)") -(8, 3, 1) - ->>> pg_version._parse_version("PostgreSQL 8.3.6") -(8, 3, 6) - ->>> pg_version._parse_version("PostgreSQL 8.3") -(8, 3, None) - ->>> pg_version._parse_version("EnterpriseDB 8.3") -(8, 3, None) - ->>> pg_version._parse_version("PostgreSQL 8.3 beta4") -(8, 3, None) - ->>> pg_version._parse_version("PostgreSQL 8.4beta1") -(8, 4, None) - -"""} + def test_parsing(self): + self.assert_parses("PostgreSQL 8.3 beta4", (8, 3, None)) + self.assert_parses("PostgreSQL 8.3", (8, 3, None)) + self.assert_parses("EnterpriseDB 8.3", (8, 3, None)) + self.assert_parses("PostgreSQL 8.3.6", (8, 3, 6)) + self.assert_parses("PostgreSQL 8.4beta1", (8, 4, None)) + self.assert_parses("PostgreSQL 8.3.1 on i386-apple-darwin9.2.2, compiled by GCC i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5478)", (8, 3, 1)) # Unfortunately with sqlite3 the in-memory test database cannot be # closed, and so it cannot be re-opened during testing, and so we # sadly disable this test for now. -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.sqlite3': - __test__['API_TESTS'] += """ ->>> connection_created.connect(connection_created_test) ->>> connection.close() # Ensure the connection is closed ->>> cursor = connection.cursor() -connection_created signal ->>> connection_created.disconnect(connection_created_test) ->>> cursor = connection.cursor() -""" - -if __name__ == '__main__': - unittest.main() +if settings.DATABASES[DEFAULT_DB_ALIAS]["ENGINE"] != "django.db.backends.sqlite3": + class ConnectionCreatedSignalTest(TestCase): + def test_signal(self): + data = {} + def receiver(sender, connection, **kwargs): + data["connection"] = connection + + connection_created.connect(receiver) + connection.close() + cursor = connection.cursor() + self.assertTrue(data["connection"] is connection) + + connection_created.disconnect(receiver) + data.clear() + cursor = connection.cursor() + self.assertTrue(data == {}) From 5295d678b940c7f1bc2d1122d4c9aeb2c0bcd968 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 30 Aug 2010 13:25:47 +0000 Subject: [PATCH 104/902] [1.2.X] Fixed #14054 -- Added documentation for the connection_created event. Thanks to Rob Hudson for the report and patch. Backport of r13673 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13675 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/signals.txt | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index adce5d217714..9badf0eea5a2 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -436,3 +436,39 @@ Arguments sent with this signal: context The :class:`~django.template.Context` with which the template was rendered. + +Database Wrappers +================= + +.. module:: django.db.backends + :synopsis: Core signals sent by the database wrapper. + +Signals sent by the database wrapper when a database connection is +initiated. + +connection_created +------------------ + +.. data:: django.db.backends.signals.connection_created + :module: + +.. versionadded:: 1.1 + +.. versionchanged:: 1.2 + The connection argument was added + +Sent when the database wrapper makes the initial connection to the +database. This is particularly useful if you'd like to send any post +connection commands to the SQL backend. + +Arguments sent with this signal: + + sender + The database wrapper class -- i.e. + :class: `django.db.backends.postgresql_psycopg2.DatabaseWrapper` or + :class: `django.db.backends.mysql.DatabaseWrapper`, etc. + + connection + The database connection that was opened. This can be used in a + multiple-database configuration to differentiate connection signals + from different databases. From 4d8af2faa3bde14d2f3a5a30b0772b554c8af245 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 30 Aug 2010 15:11:21 +0000 Subject: [PATCH 105/902] [1.2.X] Fixed #14198 -- Corrected rendering of generic sitemaps when no priority is specified. Thanks to palkeo for the report. Backport of r13676 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13677 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sitemaps/__init__.py | 3 ++- django/contrib/sitemaps/tests/basic.py | 20 +++++++++++++++++--- django/contrib/sitemaps/tests/urls.py | 16 ++++++++++++---- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py index 45b85c81d0b5..d31a9d7d2e09 100644 --- a/django/contrib/sitemaps/__init__.py +++ b/django/contrib/sitemaps/__init__.py @@ -65,11 +65,12 @@ def get_urls(self, page=1): urls = [] for item in self.paginator.page(page).object_list: loc = "http://%s%s" % (current_site.domain, self.__get('location', item)) + priority = self.__get('priority', item, None) url_info = { 'location': loc, 'lastmod': self.__get('lastmod', item, None), 'changefreq': self.__get('changefreq', item, None), - 'priority': str(self.__get('priority', item, '')) + 'priority': str(priority is not None and priority or '') } urls.append(url_info) return urls diff --git a/django/contrib/sitemaps/tests/basic.py b/django/contrib/sitemaps/tests/basic.py index 695b84a0aecf..5dd2aa20101d 100644 --- a/django/contrib/sitemaps/tests/basic.py +++ b/django/contrib/sitemaps/tests/basic.py @@ -1,5 +1,6 @@ from datetime import date from django.conf import settings +from django.contrib.auth.models import User from django.test import TestCase from django.utils.formats import localize from django.utils.translation import activate @@ -10,6 +11,8 @@ class SitemapTests(TestCase): def setUp(self): self.old_USE_L10N = settings.USE_L10N + # Create a user that will double as sitemap content + User.objects.create_user('testuser', 'test@example.com', 's3krit') def tearDown(self): settings.USE_L10N = self.old_USE_L10N @@ -17,11 +20,11 @@ def tearDown(self): def test_simple_sitemap(self): "A simple sitemap can be rendered" # Retrieve the sitemap. - response = self.client.get('/sitemaps/sitemap.xml') + response = self.client.get('/simple/sitemap.xml') # Check for all the important bits: self.assertEquals(response.content, """ -http://example.com/ticket14164%snever0.5 +http://example.com/location/%snever0.5 """ % date.today().strftime('%Y-%m-%d')) @@ -34,6 +37,17 @@ def test_localized_priority(self): # Retrieve the sitemap. Check that priorities # haven't been rendered in localized format - response = self.client.get('/sitemaps/sitemap.xml') + response = self.client.get('/simple/sitemap.xml') self.assertContains(response, '0.5') self.assertContains(response, '%s' % date.today().strftime('%Y-%m-%d')) + + def test_generic_sitemap(self): + "A minimal generic sitemap can be rendered" + # Retrieve the sitemap. + response = self.client.get('/generic/sitemap.xml') + # Check for all the important bits: + self.assertEquals(response.content, """ + +http://example.com/users/testuser/ + +""") diff --git a/django/contrib/sitemaps/tests/urls.py b/django/contrib/sitemaps/tests/urls.py index b29b5a6a4945..0fb2a7214e73 100644 --- a/django/contrib/sitemaps/tests/urls.py +++ b/django/contrib/sitemaps/tests/urls.py @@ -1,20 +1,28 @@ from datetime import datetime from django.conf.urls.defaults import * -from django.contrib.sitemaps import Sitemap +from django.contrib.sitemaps import Sitemap, GenericSitemap +from django.contrib.auth.models import User class SimpleSitemap(Sitemap): changefreq = "never" priority = 0.5 - location = '/ticket14164' + location = '/location/' lastmod = datetime.now() def items(self): return [object()] -sitemaps = { +simple_sitemaps = { 'simple': SimpleSitemap, } +generic_sitemaps = { + 'generic': GenericSitemap({ + 'queryset': User.objects.all() + }), +} + urlpatterns = patterns('django.contrib.sitemaps.views', - (r'^sitemaps/sitemap\.xml$', 'sitemap', {'sitemaps': sitemaps}), + (r'^simple/sitemap\.xml$', 'sitemap', {'sitemaps': simple_sitemaps}), + (r'^generic/sitemap\.xml$', 'sitemap', {'sitemaps': generic_sitemaps}), ) From 7a1b69418937fa589602ea104a24213d80585b47 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 31 Aug 2010 00:45:35 +0000 Subject: [PATCH 106/902] [1.2.X] Fixed #14199 -- Added a missing table creation statement in the db cache backend cull implementation, and added tests for cache culling. Thanks to Tim for the report. Backport of r13678 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13679 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/cache/backends/db.py | 1 + tests/regressiontests/cache/tests.py | 29 +++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index 1300dd4b665f..833fa749aac9 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -124,6 +124,7 @@ def _cull(self, db, cursor, now): if self._cull_frequency == 0: self.clear() else: + table = connections[db].ops.quote_name(self._table) cursor.execute("DELETE FROM %s WHERE expires < %%s" % table, [connections[db].ops.value_to_db_datetime(now)]) cursor.execute("SELECT COUNT(*) FROM %s" % table) diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 109374c46cab..0f9f5c9fffd5 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -352,21 +352,41 @@ def test_long_timeout(self): self.assertEqual(self.cache.get('key3'), 'sausage') self.assertEqual(self.cache.get('key4'), 'lobster bisque') + def perform_cull_test(self, initial_count, final_count): + """This is implemented as a utility method, because only some of the backends + implement culling. The culling algorithm also varies slightly, so the final + number of entries will vary between backends""" + # Create initial cache key entries. This will overflow the cache, causing a cull + for i in range(1, initial_count): + self.cache.set('cull%d' % i, 'value', 1000) + count = 0 + # Count how many keys are left in the cache. + for i in range(1, initial_count): + if self.cache.has_key('cull%d' % i): + count = count + 1 + self.assertEqual(count, final_count) + class DBCacheTests(unittest.TestCase, BaseCacheTests): def setUp(self): # Spaces are used in the table name to ensure quoting/escaping is working self._table_name = 'test cache table' management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False) - self.cache = get_cache('db://%s' % self._table_name) + self.cache = get_cache('db://%s?max_entries=30' % self._table_name) def tearDown(self): from django.db import connection cursor = connection.cursor() cursor.execute('DROP TABLE %s' % connection.ops.quote_name(self._table_name)) + def test_cull(self): + self.perform_cull_test(50, 29) + class LocMemCacheTests(unittest.TestCase, BaseCacheTests): def setUp(self): - self.cache = get_cache('locmem://') + self.cache = get_cache('locmem://?max_entries=30') + + def test_cull(self): + self.perform_cull_test(50, 29) # memcached backend isn't guaranteed to be available. # To check the memcached backend, the test settings file will @@ -383,7 +403,7 @@ class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): """ def setUp(self): self.dirname = tempfile.mkdtemp() - self.cache = get_cache('file://%s' % self.dirname) + self.cache = get_cache('file://%s?max_entries=30' % self.dirname) def test_hashing(self): """Test that keys are hashed into subdirectories correctly""" @@ -406,6 +426,9 @@ def test_subdirectory_removal(self): self.assert_(not os.path.exists(os.path.dirname(keypath))) self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath)))) + def test_cull(self): + self.perform_cull_test(50, 28) + class CacheUtils(unittest.TestCase): """TestCase for django.utils.cache functions.""" From 7a601b3fc121d613dc56cd1db82a128f0fd95fa1 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 3 Sep 2010 18:29:33 +0000 Subject: [PATCH 107/902] [1.2.X] Fixed #14145 - undeterministic behavior when project or app template contains dotdir Thanks to marbu for report and patch. Backport of [13668] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13681 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/core/management/base.py b/django/core/management/base.py index 6761cb69bc76..3e20e2db29f2 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -394,9 +394,9 @@ def copy_helper(style, app_or_project, name, directory, other_name=''): relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name) if relative_dir: os.mkdir(os.path.join(top_dir, relative_dir)) - for i, subdir in enumerate(subdirs): + for subdir in subdirs[:]: if subdir.startswith('.'): - del subdirs[i] + subdirs.remove(subdir) for f in files: if not f.endswith('.py'): # Ignore .pyc, .pyo, .py.class etc, as they cause various From 94047d7a3a53d4825d8e9ba1b51022005116ac34 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 3 Sep 2010 18:34:30 +0000 Subject: [PATCH 108/902] [1.2.X] Added explanatory note on CSRF failure page for the case of a missing Referer header. This is intended to help power users who have disabled Referer headers, or installed add-ons which have done so, and to help web site administrators with debugging, since this problem will be browser specific and not a programming error. Backport of [13680] from trunk. Technically this is a (tiny) new feature, but it has been backported because it might give significant help with debugging rare problems with Django 1.2's new CSRF protection. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13682 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/middleware/csrf.py | 24 ++++++++++++++++++------ django/views/csrf.py | 17 +++++++++++++++-- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index 10fab290c9a9..acb941315dc8 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -27,19 +27,29 @@ randrange = random.randrange _MAX_CSRF_KEY = 18446744073709551616L # 2 << 63 +REASON_NO_REFERER = "Referer checking failed - no Referer." +REASON_BAD_REFERER = "Referer checking failed - %s does not match %s." +REASON_NO_COOKIE = "No CSRF or session cookie." +REASON_NO_CSRF_COOKIE = "CSRF cookie not set." +REASON_BAD_TOKEN = "CSRF token missing or incorrect." + + def _get_failure_view(): """ Returns the view to be used for CSRF rejections """ return get_callable(settings.CSRF_FAILURE_VIEW) + def _get_new_csrf_key(): return md5_constructor("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() + def _make_legacy_session_token(session_id): return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() + def get_token(request): """ Returns the the CSRF token required for a POST form. @@ -52,6 +62,7 @@ def get_token(request): request.META["CSRF_COOKIE_USED"] = True return request.META.get("CSRF_COOKIE", None) + class CsrfViewMiddleware(object): """ Middleware that requires a present and correct csrfmiddlewaretoken @@ -129,13 +140,13 @@ def accept(): # Strict referer checking for HTTPS referer = request.META.get('HTTP_REFERER') if referer is None: - return reject("Referer checking failed - no Referer.") + return reject(REASON_NO_REFERER) # The following check ensures that the referer is HTTPS, # the domains match and the ports match. This might be too strict. good_referer = 'https://%s/' % request.get_host() if not referer.startswith(good_referer): - return reject("Referer checking failed - %s does not match %s." % + return reject(REASON_BAD_REFERER % (referer, good_referer)) # If the user didn't already have a CSRF cookie, then fall back to @@ -150,7 +161,7 @@ def accept(): # No CSRF cookie and no session cookie. For POST requests, # we insist on a CSRF cookie, and in this way we can avoid # all CSRF attacks, including login CSRF. - return reject("No CSRF or session cookie.") + return reject(REASON_NO_COOKIE) else: csrf_token = request.META["CSRF_COOKIE"] @@ -159,9 +170,9 @@ def accept(): if request_csrf_token != csrf_token: if cookie_is_new: # probably a problem setting the CSRF cookie - return reject("CSRF cookie not set.") + return reject(REASON_NO_CSRF_COOKIE) else: - return reject("CSRF token missing or incorrect.") + return reject(REASON_BAD_TOKEN) return accept() @@ -187,6 +198,7 @@ def process_response(self, request, response): response.csrf_processing_done = True return response + class CsrfResponseMiddleware(object): """ DEPRECATED @@ -237,6 +249,7 @@ def add_csrf_field(match): del response['ETag'] return response + class CsrfMiddleware(object): """ Django middleware that adds protection against Cross Site @@ -264,4 +277,3 @@ def process_response(self, request, resp): def process_view(self, request, callback, callback_args, callback_kwargs): return self.view_middleware.process_view(request, callback, callback_args, callback_kwargs) - diff --git a/django/views/csrf.py b/django/views/csrf.py index fa996fff24c7..c627812dcbaa 100644 --- a/django/views/csrf.py +++ b/django/views/csrf.py @@ -23,7 +23,7 @@ h1 span { font-size:60%; color:#666; font-weight:normal; } #info { background:#f6f6f6; } #info ul { margin: 0.5em 4em; } - #info p { padding-top:10px; } + #info p, #summary p { padding-top:10px; } #summary { background: #ffc; } #explanation { background:#eee; border-bottom: 0px none; } @@ -32,6 +32,16 @@

        Forbidden (403)

        CSRF verification failed. Request aborted.

        +{% if no_referer %} +

        You are seeing this message because this HTTPS site requires a 'Referer + header' to be sent by your web browser, but none was sent. This header is + required for security reasons, to ensure that your browser is not being + hijacked by third parties.

        + +

        If you have configured your browser to disable 'Referer' headers, please + re-enable them, at least for this site, or for HTTPS connections, or for + 'same-origin' requests.

        +{% endif %}
        {% if DEBUG %}
        @@ -83,7 +93,10 @@ def csrf_failure(request, reason=""): """ Default view used when request fails CSRF protection """ + from django.middleware.csrf import REASON_NO_REFERER t = Template(CSRF_FAILRE_TEMPLATE) c = Context({'DEBUG': settings.DEBUG, - 'reason': reason}) + 'reason': reason, + 'no_referer': reason == REASON_NO_REFERER + }) return HttpResponseForbidden(t.render(c), mimetype='text/html') From 4b4168b1bf74ec2abe6c2fb10a80f426d860e952 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 3 Sep 2010 18:58:53 +0000 Subject: [PATCH 109/902] Fixed #14090 - Many sql queries needed to display change user form Thanks to Suor for report and patch. Backport for [13683] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13684 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/forms.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 086acf334907..992a0ff44d4a 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -52,6 +52,10 @@ class UserChangeForm(forms.ModelForm): class Meta: model = User + def __init__(self, *args, **kwargs): + super(UserChangeForm, self).__init__(*args, **kwargs) + self.fields['user_permissions'].queryset = self.fields['user_permissions'].queryset.select_related('content_type') + class AuthenticationForm(forms.Form): """ Base class for authenticating users. Extend this to get a form that accepts From 17ae1d3262e777c0d3c62e30090050363de5a679 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 3 Sep 2010 19:04:29 +0000 Subject: [PATCH 110/902] [1.2.X] Fixed #13754 - Add a note about a test client session property gotcha Thanks SmileyChris for report and patch. Backport of [13685] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13686 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index c5f8f0040d3b..074f94846140 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -935,6 +935,15 @@ can access these properties as part of a test condition. A dictionary-like object containing session information. See the :doc:`session documentation` for full details. + To modify the session and then save it, it must be stored in a variable + first (because a new ``SessionStore`` is created every time this property + is accessed):: + + def test_something(self): + session = self.client.session + session['somekey'] = 'test' + session.save() + .. _Cookie module documentation: http://docs.python.org/library/cookie.html Example From 01e8d75c203231468d00ec726511b577eaa39b09 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 7 Sep 2010 20:26:23 +0000 Subject: [PATCH 111/902] [1.2.X] Fixed #14205 - Tiny grammar fix in form validation documentation Thanks to zendak for report and patch. Backport of [13687] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13689 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/forms/validation.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt index 6cc3280e0606..1c047f246f7a 100644 --- a/docs/ref/forms/validation.txt +++ b/docs/ref/forms/validation.txt @@ -178,7 +178,7 @@ Using validators .. versionadded:: 1.2 Django's form (and model) fields support use of simple utility functions and -classes known as validators. These can passed to a field's constructor, via +classes known as validators. These can be passed to a field's constructor, via the field's ``validators`` argument, or defined on the Field class itself with the ``default_validators`` attribute. From a537235d7fff5f6c66e8d0276b8a0181e1adf9d9 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 7 Sep 2010 20:33:58 +0000 Subject: [PATCH 112/902] [1.2.X] Fixed #13853 - line with csrf_token too wide Thanks to alper for report, alper/richardb for patch Backport of [13688] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13691 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index d8712d52c887..4a6e30fc0017 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -794,7 +794,8 @@ the following line to your URLconf::

        Your username and password didn't match. Please try again.

        {% endif %} - {% csrf_token %} + + {% csrf_token %} From 79d92067119569e95f2a0aa6827586d83ab88eda Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 7 Sep 2010 20:45:48 +0000 Subject: [PATCH 113/902] [1.2.X] Fixed #13414 - QuerySet API ref wrong sql equivalent in __year lookup example Thanks to idle for report and patch Backport of [13690] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13693 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/defaulttags.py | 11 +++++++++-- tests/regressiontests/templates/tests.py | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 318ae5ffd2ee..d629a690c5d4 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -157,15 +157,22 @@ def render(self, context): loop_dict['first'] = (i == 0) loop_dict['last'] = (i == len_values - 1) + pop_context = False if unpack: # If there are multiple loop variables, unpack the item into # them. - context.update(dict(zip(self.loopvars, item))) + try: + unpacked_vars = dict(zip(self.loopvars, item)) + except TypeError: + pass + else: + pop_context = True + context.update(unpacked_vars) else: context[self.loopvars[0]] = item for node in self.nodelist_loop: nodelist.append(node.render(context)) - if unpack: + if pop_context: # The loop variables were pushed on to the context so pop them # off again. This is necessary because the tag lets the length # of loopvars differ to the length of each set of items and we diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 2f2df65e9698..bbbcae30ebb0 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -701,6 +701,7 @@ def get_template_tests(self): 'for-tag-unpack11': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, ("one:1,/two:2,/", "one:1,INVALID/two:2,INVALID/")), 'for-tag-unpack12': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2))}, ("one:1,carrot/two:2,/", "one:1,carrot/two:2,INVALID/")), 'for-tag-unpack13': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'cheese'))}, ("one:1,carrot/two:2,cheese/", "one:1,carrot/two:2,cheese/")), + 'for-tag-unpack14': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (1, 2)}, (":/:/", "INVALID:INVALID/INVALID:INVALID/")), 'for-tag-empty01': ("{% for val in values %}{{ val }}{% empty %}empty text{% endfor %}", {"values": [1, 2, 3]}, "123"), 'for-tag-empty02': ("{% for val in values %}{{ val }}{% empty %}values array empty{% endfor %}", {"values": []}, "values array empty"), 'for-tag-empty03': ("{% for val in values %}{{ val }}{% empty %}values array not found{% endfor %}", {}, "values array not found"), From 1b910d7fbcc664ae22bef010af05746027b82138 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 7 Sep 2010 20:54:13 +0000 Subject: [PATCH 114/902] [1.2.X] Fixed #13350 - Documentation on autoescape should mention endautoescape Thanks to mountainpaul@gmail.com for report and to dwillis for patch. Backport of [13694] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13695 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/templates/builtins.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index b5ce71e321e2..4f33bd212cc6 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -23,7 +23,7 @@ autoescape Control the current auto-escaping behavior. This tag takes either ``on`` or ``off`` as an argument and that determines whether auto-escaping is in effect -inside the block. +inside the block. The block is closed with an ``endautoescape`` ending tag. When auto-escaping is in effect, all variable content has HTML escaping applied to it before placing the result into the output (but after any filters have @@ -34,6 +34,12 @@ The only exceptions are variables that are already marked as "safe" from escaping, either by the code that populated the variable, or because it has had the ``safe`` or ``escape`` filters applied. +Sample usage:: + + {% autoescape on %} + {{ body }} + {% endautoescape %} + .. templatetag:: block block From a04398eb1b958830698fe42bd9f5244662e7d8b3 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 7 Sep 2010 20:59:28 +0000 Subject: [PATCH 115/902] [1.2.X] Fixed #13081 - Admin actions lose get-parameters in changelist view Thanks to joh for report and to SmileyChris for patch. Backport of [13696] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13697 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/options.py | 2 +- tests/regressiontests/admin_views/tests.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 1f8ff6dbd189..3a824c6e0a90 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -754,7 +754,7 @@ def response_action(self, request, queryset): if isinstance(response, HttpResponse): return response else: - return HttpResponseRedirect(".") + return HttpResponseRedirect(request.get_full_path()) else: msg = _("No action selected.") self.message_user(request, msg) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index f5a54f3f6cd3..f76765236ef2 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -1477,6 +1477,21 @@ def test_custom_function_action_with_redirect(self): response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data) self.failUnlessEqual(response.status_code, 302) + def test_default_redirect(self): + """ + Test that actions which don't return an HttpResponse are redirected to + the same page, retaining the querystring (which may contain changelist + information). + """ + action_data = { + ACTION_CHECKBOX_NAME: [1], + 'action' : 'external_mail', + 'index': 0, + } + url = '/test_admin/admin/admin_views/externalsubscriber/?ot=asc&o=1' + response = self.client.post(url, action_data) + self.assertRedirects(response, url) + def test_model_without_action(self): "Tests a ModelAdmin without any action" response = self.client.get('/test_admin/admin/admin_views/oldsubscriber/') From 7f84657b6b2243cc787bdb9f296710c8d13ad0bd Mon Sep 17 00:00:00 2001 From: James Bennett Date: Thu, 9 Sep 2010 00:36:08 +0000 Subject: [PATCH 116/902] [1.2.X] Patch CSRF-protection system to deal with reported security issue. Announcement and details to follow. Backport of [13698] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13699 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/middleware/csrf.py | 6 ++++-- django/template/defaulttags.py | 3 ++- tests/regressiontests/csrf_tests/tests.py | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index acb941315dc8..59eef72315f8 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -13,6 +13,7 @@ from django.core.urlresolvers import get_callable from django.utils.cache import patch_vary_headers from django.utils.hashcompat import md5_constructor +from django.utils.html import escape from django.utils.safestring import mark_safe _POST_FORM_RE = \ @@ -52,7 +53,8 @@ def _make_legacy_session_token(session_id): def get_token(request): """ - Returns the the CSRF token required for a POST form. + Returns the the CSRF token required for a POST form. No assumptions should + be made about what characters might be in the CSRF token. A side effect of calling this function is to make the the csrf_protect decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' @@ -233,7 +235,7 @@ def add_csrf_field(match): """Returns the matched tag plus the added element""" return mark_safe(match.group() + "
        " + \ "
        ") # Modify any POST forms diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index d629a690c5d4..0914b1c3b19b 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -9,6 +9,7 @@ from django.template import get_library, Library, InvalidTemplateLibrary from django.template.smartif import IfParser, Literal from django.conf import settings +from django.utils.html import escape from django.utils.encoding import smart_str, smart_unicode from django.utils.safestring import mark_safe @@ -42,7 +43,7 @@ def render(self, context): if csrf_token == 'NOTPROVIDED': return mark_safe(u"") else: - return mark_safe(u"
        " % (csrf_token)) + return mark_safe(u"
        " % escape(csrf_token)) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py index f383978266c1..fadecf565efd 100644 --- a/tests/regressiontests/csrf_tests/tests.py +++ b/tests/regressiontests/csrf_tests/tests.py @@ -6,6 +6,7 @@ from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt from django.core.context_processors import csrf from django.contrib.sessions.middleware import SessionMiddleware +from django.utils.html import escape from django.utils.importlib import import_module from django.conf import settings from django.template import RequestContext, Template @@ -56,7 +57,9 @@ def is_secure(self): return getattr(self, '_is_secure', False) class CsrfMiddlewareTest(TestCase): - _csrf_id = "1" + # The csrf token is potentially from an untrusted source, so could have + # characters that need escaping + _csrf_id = "<1>" # This is a valid session token for this ID and secret key. This was generated using # the old code that we're to be backwards-compatible with. Don't use the CSRF code @@ -101,7 +104,7 @@ def _get_POST_session_request_no_token(self): return req def _check_token_present(self, response, csrf_id=None): - self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id)) + self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % escape(csrf_id or self._csrf_id)) # Check the post processing and outgoing cookie def test_process_response_no_csrf_cookie(self): From 329fab430b51bd49035ccffd2d736a7fe67a90ba Mon Sep 17 00:00:00 2001 From: James Bennett Date: Thu, 9 Sep 2010 00:37:33 +0000 Subject: [PATCH 117/902] [1.2.X] Increment to Django 1.2.2. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13700 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django/__init__.py b/django/__init__.py index cdaf1ec40eec..48815232f82e 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 2, 1, 'final', 0) +VERSION = (1, 2, 2, 'final', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) diff --git a/setup.py b/setup.py index f13d729b5649..938849aa6d61 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'http://media.djangoproject.com/releases/1.2/Django-1.2.1.tar.gz', + download_url = 'http://media.djangoproject.com/releases/1.2/Django-1.2.2.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From 5357000bc3fc0e22c6708fc2bd11e3e4e5abecef Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 9 Sep 2010 23:38:01 +0000 Subject: [PATCH 118/902] [1.2.X] Converted tests for contrib.auth.forms to unit tests. Backport of [13701], needed in order to backport [13702] git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13703 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/tests/__init__.py | 3 +- django/contrib/auth/tests/forms.py | 467 +++++++++++++------------- 2 files changed, 237 insertions(+), 233 deletions(-) diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py index 965ea2d6be0c..a1d02b6014a4 100644 --- a/django/contrib/auth/tests/__init__.py +++ b/django/contrib/auth/tests/__init__.py @@ -1,7 +1,7 @@ from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest from django.contrib.auth.tests.basic import BASIC_TESTS from django.contrib.auth.tests.decorators import LoginRequiredTestCase -from django.contrib.auth.tests.forms import FORM_TESTS +from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest from django.contrib.auth.tests.remote_user \ import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest from django.contrib.auth.tests.models import ProfileTestCase @@ -13,6 +13,5 @@ __test__ = { 'BASIC_TESTS': BASIC_TESTS, - 'FORM_TESTS': FORM_TESTS, 'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS, } diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index b691c560be5a..355b9eaf6e63 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -1,231 +1,236 @@ - -FORM_TESTS = """ ->>> from django.contrib.auth.models import User ->>> from django.contrib.auth.forms import UserCreationForm, AuthenticationForm ->>> from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm - -# The user already exists. - ->>> user = User.objects.create_user("jsmith", "jsmith@example.com", "test123") ->>> data = { -... 'username': 'jsmith', -... 'password1': 'test123', -... 'password2': 'test123', -... } ->>> form = UserCreationForm(data) ->>> form.is_valid() -False ->>> form["username"].errors -[u'A user with that username already exists.'] - -# The username contains invalid data. - ->>> data = { -... 'username': 'jsmith!', -... 'password1': 'test123', -... 'password2': 'test123', -... } ->>> form = UserCreationForm(data) ->>> form.is_valid() -False ->>> form["username"].errors -[u'This value may contain only letters, numbers and @/./+/-/_ characters.'] - -# The verification password is incorrect. - ->>> data = { -... 'username': 'jsmith2', -... 'password1': 'test123', -... 'password2': 'test', -... } ->>> form = UserCreationForm(data) ->>> form.is_valid() -False ->>> form["password2"].errors -[u"The two password fields didn't match."] - -# One (or both) passwords weren't given - ->>> data = {'username': 'jsmith2'} ->>> form = UserCreationForm(data) ->>> form.is_valid() -False ->>> form['password1'].errors -[u'This field is required.'] ->>> form['password2'].errors -[u'This field is required.'] - ->>> data['password2'] = 'test123' ->>> form = UserCreationForm(data) ->>> form.is_valid() -False ->>> form['password1'].errors -[u'This field is required.'] - -# The success case. - ->>> data = { -... 'username': 'jsmith2@example.com', -... 'password1': 'test123', -... 'password2': 'test123', -... } ->>> form = UserCreationForm(data) ->>> form.is_valid() -True ->>> form.save() - - -# The user submits an invalid username. - ->>> data = { -... 'username': 'jsmith_does_not_exist', -... 'password': 'test123', -... } - ->>> form = AuthenticationForm(None, data) ->>> form.is_valid() -False ->>> form.non_field_errors() -[u'Please enter a correct username and password. Note that both fields are case-sensitive.'] - -# The user is inactive. - ->>> data = { -... 'username': 'jsmith', -... 'password': 'test123', -... } ->>> user.is_active = False ->>> user.save() ->>> form = AuthenticationForm(None, data) ->>> form.is_valid() -False ->>> form.non_field_errors() -[u'This account is inactive.'] - ->>> user.is_active = True ->>> user.save() - -# The success case - ->>> form = AuthenticationForm(None, data) ->>> form.is_valid() -True ->>> form.non_field_errors() -[] - -### SetPasswordForm: - -# The two new passwords do not match. - ->>> data = { -... 'new_password1': 'abc123', -... 'new_password2': 'abc', -... } ->>> form = SetPasswordForm(user, data) ->>> form.is_valid() -False ->>> form["new_password2"].errors -[u"The two password fields didn't match."] - -# The success case. - ->>> data = { -... 'new_password1': 'abc123', -... 'new_password2': 'abc123', -... } ->>> form = SetPasswordForm(user, data) ->>> form.is_valid() -True - -### PasswordChangeForm: - -The old password is incorrect. - ->>> data = { -... 'old_password': 'test', -... 'new_password1': 'abc123', -... 'new_password2': 'abc123', -... } ->>> form = PasswordChangeForm(user, data) ->>> form.is_valid() -False ->>> form["old_password"].errors -[u'Your old password was entered incorrectly. Please enter it again.'] - -# The two new passwords do not match. - ->>> data = { -... 'old_password': 'test123', -... 'new_password1': 'abc123', -... 'new_password2': 'abc', -... } ->>> form = PasswordChangeForm(user, data) ->>> form.is_valid() -False ->>> form["new_password2"].errors -[u"The two password fields didn't match."] - -# The success case. - ->>> data = { -... 'old_password': 'test123', -... 'new_password1': 'abc123', -... 'new_password2': 'abc123', -... } ->>> form = PasswordChangeForm(user, data) ->>> form.is_valid() -True - -# Regression test - check the order of fields: - ->>> PasswordChangeForm(user, {}).fields.keys() -['old_password', 'new_password1', 'new_password2'] - -### UserChangeForm - ->>> from django.contrib.auth.forms import UserChangeForm ->>> data = {'username': 'not valid'} ->>> form = UserChangeForm(data, instance=user) ->>> form.is_valid() -False ->>> form['username'].errors -[u'This value may contain only letters, numbers and @/./+/-/_ characters.'] - - -### PasswordResetForm - ->>> from django.contrib.auth.forms import PasswordResetForm ->>> data = {'email':'not valid'} ->>> form = PasswordResetForm(data) ->>> form.is_valid() -False ->>> form['email'].errors -[u'Enter a valid e-mail address.'] - -# Test nonexistant email address ->>> data = {'email':'foo@bar.com'} ->>> form = PasswordResetForm(data) ->>> form.is_valid() -False ->>> form.errors -{'email': [u"That e-mail address doesn't have an associated user account. Are you sure you've registered?"]} - -# Test cleaned_data bug fix ->>> user = User.objects.create_user("jsmith3", "jsmith3@example.com", "test123") ->>> data = {'email':'jsmith3@example.com'} ->>> form = PasswordResetForm(data) ->>> form.is_valid() -True ->>> form.cleaned_data['email'] -u'jsmith3@example.com' - -# bug #5605, preserve the case of the user name (before the @ in the email address) -# when creating a user. ->>> user = User.objects.create_user('forms_test2', 'tesT@EXAMple.com', 'test') ->>> user.email -'tesT@example.com' ->>> user = User.objects.create_user('forms_test3', 'tesT', 'test') ->>> user.email -'tesT' - -""" +from django.contrib.auth.models import User +from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm +from django.test import TestCase + + +class UserCreationFormTest(TestCase): + + fixtures = ['authtestdata.json'] + + def test_user_already_exists(self): + data = { + 'username': 'testclient', + 'password1': 'test123', + 'password2': 'test123', + } + form = UserCreationForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual(form["username"].errors, + [u'A user with that username already exists.']) + + def test_invalid_data(self): + data = { + 'username': 'jsmith!', + 'password1': 'test123', + 'password2': 'test123', + } + form = UserCreationForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual(form["username"].errors, + [u'This value may contain only letters, numbers and @/./+/-/_ characters.']) + + + def test_password_verification(self): + # The verification password is incorrect. + data = { + 'username': 'jsmith', + 'password1': 'test123', + 'password2': 'test', + } + form = UserCreationForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual(form["password2"].errors, + [u"The two password fields didn't match."]) + + + def test_both_passwords(self): + # One (or both) passwords weren't given + data = {'username': 'jsmith'} + form = UserCreationForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual(form['password1'].errors, + [u'This field is required.']) + self.assertEqual(form['password2'].errors, + [u'This field is required.']) + + + data['password2'] = 'test123' + form = UserCreationForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual(form['password1'].errors, + [u'This field is required.']) + + def test_success(self): + # The success case. + + data = { + 'username': 'jsmith@example.com', + 'password1': 'test123', + 'password2': 'test123', + } + form = UserCreationForm(data) + self.assertTrue(form.is_valid()) + u = form.save() + self.assertEqual(repr(u), '') + + +class AuthenticationFormTest(TestCase): + + fixtures = ['authtestdata.json'] + + def test_invalid_username(self): + # The user submits an invalid username. + + data = { + 'username': 'jsmith_does_not_exist', + 'password': 'test123', + } + form = AuthenticationForm(None, data) + self.assertFalse(form.is_valid()) + self.assertEqual(form.non_field_errors(), + [u'Please enter a correct username and password. Note that both fields are case-sensitive.']) + + def test_inactive_user(self): + # The user is inactive. + data = { + 'username': 'inactive', + 'password': 'password', + } + form = AuthenticationForm(None, data) + self.assertFalse(form.is_valid()) + self.assertEqual(form.non_field_errors(), + [u'This account is inactive.']) + + + def test_success(self): + # The success case + data = { + 'username': 'testclient', + 'password': 'password', + } + form = AuthenticationForm(None, data) + self.assertTrue(form.is_valid()) + self.assertEqual(form.non_field_errors(), []) + + +class SetPasswordFormTest(TestCase): + + fixtures = ['authtestdata.json'] + + def test_password_verification(self): + # The two new passwords do not match. + user = User.objects.get(username='testclient') + data = { + 'new_password1': 'abc123', + 'new_password2': 'abc', + } + form = SetPasswordForm(user, data) + self.assertFalse(form.is_valid()) + self.assertEqual(form["new_password2"].errors, + [u"The two password fields didn't match."]) + + def test_success(self): + user = User.objects.get(username='testclient') + data = { + 'new_password1': 'abc123', + 'new_password2': 'abc123', + } + form = SetPasswordForm(user, data) + self.assertTrue(form.is_valid()) + + +class PasswordChangeFormTest(TestCase): + + fixtures = ['authtestdata.json'] + + def test_incorrect_password(self): + user = User.objects.get(username='testclient') + data = { + 'old_password': 'test', + 'new_password1': 'abc123', + 'new_password2': 'abc123', + } + form = PasswordChangeForm(user, data) + self.assertFalse(form.is_valid()) + self.assertEqual(form["old_password"].errors, + [u'Your old password was entered incorrectly. Please enter it again.']) + + + def test_password_verification(self): + # The two new passwords do not match. + user = User.objects.get(username='testclient') + data = { + 'old_password': 'password', + 'new_password1': 'abc123', + 'new_password2': 'abc', + } + form = PasswordChangeForm(user, data) + self.assertFalse(form.is_valid()) + self.assertEqual(form["new_password2"].errors, + [u"The two password fields didn't match."]) + + + def test_success(self): + # The success case. + user = User.objects.get(username='testclient') + data = { + 'old_password': 'password', + 'new_password1': 'abc123', + 'new_password2': 'abc123', + } + form = PasswordChangeForm(user, data) + self.assertTrue(form.is_valid()) + + def test_field_order(self): + # Regression test - check the order of fields: + user = User.objects.get(username='testclient') + self.assertEqual(PasswordChangeForm(user, {}).fields.keys(), + ['old_password', 'new_password1', 'new_password2']) + +class UserChangeFormTest(TestCase): + + fixtures = ['authtestdata.json'] + + def test_username_validity(self): + user = User.objects.get(username='testclient') + data = {'username': 'not valid'} + form = UserChangeForm(data, instance=user) + self.assertFalse(form.is_valid()) + self.assertEqual(form['username'].errors, + [u'This value may contain only letters, numbers and @/./+/-/_ characters.']) + +class PasswordResetFormTest(TestCase): + + fixtures = ['authtestdata.json'] + + def test_invalid_email(self): + data = {'email':'not valid'} + form = PasswordResetForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual(form['email'].errors, + [u'Enter a valid e-mail address.']) + + def test_nonexistant_email(self): + # Test nonexistant email address + data = {'email':'foo@bar.com'} + form = PasswordResetForm(data) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors, + {'email': [u"That e-mail address doesn't have an associated user account. Are you sure you've registered?"]}) + + def test_cleaned_data(self): + # Regression test + user = User.objects.create_user("jsmith3", "jsmith3@example.com", "test123") + data = {'email':'jsmith3@example.com'} + form = PasswordResetForm(data) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data['email'], u'jsmith3@example.com') + + + def test_bug_5605(self): + # bug #5605, preserve the case of the user name (before the @ in the + # email address) when creating a user. + user = User.objects.create_user('forms_test2', 'tesT@EXAMple.com', 'test') + self.assertEqual(user.email, 'tesT@example.com') + user = User.objects.create_user('forms_test3', 'tesT', 'test') + self.assertEqual(user.email, 'tesT') From 6ea90256d767bb79649df83ec2f5b0e265d6871b Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 9 Sep 2010 23:43:01 +0000 Subject: [PATCH 119/902] [1.2.X] Fixed #14242 - UserChangeForm subclasses without 'user_permissions' field causes KeyError This was a regression introduced by [13684] Thanks to adammckerlie@gmail.com for report. Backport of [13702] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13704 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/forms.py | 4 +++- django/contrib/auth/tests/forms.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 992a0ff44d4a..d20c472495c9 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -54,7 +54,9 @@ class Meta: def __init__(self, *args, **kwargs): super(UserChangeForm, self).__init__(*args, **kwargs) - self.fields['user_permissions'].queryset = self.fields['user_permissions'].queryset.select_related('content_type') + f = self.fields.get('user_permissions', None) + if f is not None: + f.queryset = f.queryset.select_related('content_type') class AuthenticationForm(forms.Form): """ diff --git a/django/contrib/auth/tests/forms.py b/django/contrib/auth/tests/forms.py index 355b9eaf6e63..5aa49e09c3d8 100644 --- a/django/contrib/auth/tests/forms.py +++ b/django/contrib/auth/tests/forms.py @@ -199,6 +199,22 @@ def test_username_validity(self): self.assertEqual(form['username'].errors, [u'This value may contain only letters, numbers and @/./+/-/_ characters.']) + def test_bug_14242(self): + # A regression test, introduce by adding an optimization for the + # UserChangeForm. + + class MyUserForm(UserChangeForm): + def __init__(self, *args, **kwargs): + super(MyUserForm, self).__init__(*args, **kwargs) + self.fields['groups'].help_text = 'These groups give users different permissions' + + class Meta(UserChangeForm.Meta): + fields = ('groups',) + + # Just check we can create it + form = MyUserForm({}) + + class PasswordResetFormTest(TestCase): fixtures = ['authtestdata.json'] From 6e1c46dab96395b437f77ee0c39deffa47042408 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 10 Sep 2010 13:09:19 +0000 Subject: [PATCH 120/902] [1.2.X] Fixed #14250 - FileBasedCacheTests.test_cull test failure This patch makes the cull behaviour (which files deleted and how many deleted) deterministic. Backport of [13705] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13706 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/cache/backends/filebased.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index fe833336d0d9..6057f11ef7b0 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -116,7 +116,7 @@ def _cull(self): return try: - filelist = os.listdir(self._dir) + filelist = sorted(os.listdir(self._dir)) except (IOError, OSError): return From ec4d6bb21d2b7bffae21e7c55b5444fead9df41d Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 10 Sep 2010 13:17:38 +0000 Subject: [PATCH 121/902] [1.2.X] Fixed #14247 - 'forms' test failure in 1.2.X Thanks to ramiro/pmclanaham for the report and analysis. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13707 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/forms/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py index 028ff9bad2a9..548f75d4ee6f 100644 --- a/tests/regressiontests/forms/models.py +++ b/tests/regressiontests/forms/models.py @@ -119,12 +119,12 @@ def test_callable_initial_value(self): - Hold down "Control", or "Command" on a Mac, to select more than one.

        + Hold down "Control", or "Command" on a Mac, to select more than one.

        Hold down "Control", or "Command" on a Mac, to select more than one.

        """) + Hold down "Control", or "Command" on a Mac, to select more than one.

        """) def test_initial_instance_value(self): "Initial instances for model fields may also be instances (refs #7287)" @@ -151,13 +151,13 @@ def test_initial_instance_value(self): - Hold down "Control", or "Command" on a Mac, to select more than one.

        + Hold down "Control", or "Command" on a Mac, to select more than one.

        - Hold down "Control", or "Command" on a Mac, to select more than one.

        """) + Hold down "Control", or "Command" on a Mac, to select more than one.

        """) __test__ = {'API_TESTS': """ From 1b0084e2a046f6cca03ef68f72e34e9b0544ae60 Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Fri, 10 Sep 2010 17:08:14 +0000 Subject: [PATCH 122/902] [1.2.X] Fixed some of the problems with aggregation_regress tests on higher levles of Postgres. Refs #14246. r13709 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13710 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../aggregation_regress/tests.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py index b32d4b5842c7..9a413ec60abf 100644 --- a/tests/regressiontests/aggregation_regress/tests.py +++ b/tests/regressiontests/aggregation_regress/tests.py @@ -5,7 +5,7 @@ from django.conf import settings from django.test import TestCase, Approximate from django.db import DEFAULT_DB_ALIAS -from django.db.models import Count, Max, Avg, Sum, F +from django.db.models import Count, Max, Avg, Sum, StdDev, Variance, F from regressiontests.aggregation_regress.models import * @@ -623,60 +623,60 @@ def test_more_more_more(self): def test_stddev(self): self.assertEqual( Book.objects.aggregate(StdDev('pages')), - {'pages__stddev': 311.46} + {'pages__stddev': Approximate(311.46, 1)} ) self.assertEqual( Book.objects.aggregate(StdDev('rating')), - {'rating__stddev': 0.60} + {'rating__stddev': Approximate(0.60, 1)} ) self.assertEqual( Book.objects.aggregate(StdDev('price')), - {'price__stddev': 24.16} + {'price__stddev': Approximate(24.16, 2)} ) self.assertEqual( Book.objects.aggregate(StdDev('pages', sample=True)), - {'pages__stddev': 341.19} + {'pages__stddev': Approximate(341.19, 2)} ) self.assertEqual( Book.objects.aggregate(StdDev('rating', sample=True)), - {'rating__stddev': 0.66} + {'rating__stddev': Approximate(0.66, 2)} ) self.assertEqual( Book.objects.aggregate(StdDev('price', sample=True)), - {'price__stddev': 26.46} + {'price__stddev': Approximate(26.46, 1)} ) self.assertEqual( Book.objects.aggregate(Variance('pages')), - {'pages__variance': 97010.80} + {'pages__variance': Approximate(97010.80, 1)} ) self.assertEqual( Book.objects.aggregate(Variance('rating')), - {'rating__variance': 0.36} + {'rating__variance': Approximate(0.36, 1)} ) self.assertEqual( Book.objects.aggregate(Variance('price')), - {'price__variance': 583.77} + {'price__variance': Approximate(583.77, 1)} ) self.assertEqual( Book.objects.aggregate(Variance('pages', sample=True)), - {'pages__variance': 116412.96} + {'pages__variance': Approximate(116412.96, 1)} ) self.assertEqual( Book.objects.aggregate(Variance('rating', sample=True)), - {'rating__variance': 0.44} + {'rating__variance': Approximate(0.44, 2)} ) self.assertEqual( Book.objects.aggregate(Variance('price', sample=True)), - {'price__variance': 700.53} + {'price__variance': Approximate(700.53, 2)} ) From 13f1192911c7a4a5e761ed1d7a44946336255867 Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Fri, 10 Sep 2010 18:11:25 +0000 Subject: [PATCH 123/902] [1.2.X] Fixed #14246: Modified aggregation_regress tests so that they will pass on a variety (sqlite, Postgres, MySQL/MyISAM) of DBs. r13712 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13714 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/aggregation_regress/tests.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py index 9a413ec60abf..51f439c5a19f 100644 --- a/tests/regressiontests/aggregation_regress/tests.py +++ b/tests/regressiontests/aggregation_regress/tests.py @@ -135,7 +135,6 @@ def test_annotation(self): contact_id=3, id=2, isbn=u'067232959', - manufacture_cost=11.545, mean_auth_age=45.0, name='Sams Teach Yourself Django in 24 Hours', pages=528, @@ -144,6 +143,8 @@ def test_annotation(self): publisher_id=2, rating=3.0 ) + # Different DB backends return different types for the extra select computation + self.assertTrue(obj.manufacture_cost == 11.545 or obj.manufacture_cost == Decimal('11.545')) # Order of the annotate/extra in the query doesn't matter obj = Book.objects.extra(select={'manufacture_cost' : 'price * .5'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=2) @@ -151,7 +152,6 @@ def test_annotation(self): contact_id=3, id=2, isbn=u'067232959', - manufacture_cost=11.545, mean_auth_age=45.0, name=u'Sams Teach Yourself Django in 24 Hours', pages=528, @@ -160,14 +160,18 @@ def test_annotation(self): publisher_id=2, rating=3.0 ) + # Different DB backends return different types for the extra select computation + self.assertTrue(obj.manufacture_cost == 11.545 or obj.manufacture_cost == Decimal('11.545')) # Values queries can be combined with annotate and extra obj = Book.objects.annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).values().get(pk=2) + manufacture_cost = obj['manufacture_cost'] + self.assertTrue(manufacture_cost == 11.545 or manufacture_cost == Decimal('11.545')) + del obj['manufacture_cost'] self.assertEqual(obj, { "contact_id": 3, "id": 2, "isbn": u"067232959", - "manufacture_cost": 11.545, "mean_auth_age": 45.0, "name": u"Sams Teach Yourself Django in 24 Hours", "pages": 528, @@ -180,11 +184,13 @@ def test_annotation(self): # The order of the (empty) values, annotate and extra clauses doesn't # matter obj = Book.objects.values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'manufacture_cost' : 'price * .5'}).get(pk=2) + manufacture_cost = obj['manufacture_cost'] + self.assertTrue(manufacture_cost == 11.545 or manufacture_cost == Decimal('11.545')) + del obj['manufacture_cost'] self.assertEqual(obj, { 'contact_id': 3, 'id': 2, 'isbn': u'067232959', - 'manufacture_cost': 11.545, 'mean_auth_age': 45.0, 'name': u'Sams Teach Yourself Django in 24 Hours', 'pages': 528, From ec8d04cffe720477908c7668018102768eb43083 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 10 Sep 2010 18:49:45 +0000 Subject: [PATCH 124/902] [1.2.X] Fixed #11158 - get_image_dimensions very slow/incorrect after 1 call Thanks to kua for the report, and to kua, SmileyChris and SAn for the patch Backport of [13715] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13716 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/files/images.py | 19 ++++++++++++------- tests/regressiontests/file_storage/tests.py | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/django/core/files/images.py b/django/core/files/images.py index 55008b548a9a..228a7118c51a 100644 --- a/django/core/files/images.py +++ b/django/core/files/images.py @@ -23,23 +23,26 @@ def _get_image_dimensions(self): if not hasattr(self, '_dimensions_cache'): close = self.closed self.open() - self._dimensions_cache = get_image_dimensions(self) - if close: - self.close() + self._dimensions_cache = get_image_dimensions(self, close=close) return self._dimensions_cache -def get_image_dimensions(file_or_path): - """Returns the (width, height) of an image, given an open file or a path.""" +def get_image_dimensions(file_or_path, close=False): + """ + Returns the (width, height) of an image, given an open file or a path. Set + 'close' to True to close the file at the end if it is initially in an open + state. + """ # Try to import PIL in either of the two ways it can end up installed. try: from PIL import ImageFile as PILImageFile except ImportError: import ImageFile as PILImageFile - + p = PILImageFile.Parser() - close = False if hasattr(file_or_path, 'read'): file = file_or_path + file_pos = file.tell() + file.seek(0) else: file = open(file_or_path, 'rb') close = True @@ -55,3 +58,5 @@ def get_image_dimensions(file_or_path): finally: if close: file.close() + else: + file.seek(file_pos) diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index 2c5f0f45513a..a1470f9f05b6 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -230,3 +230,19 @@ def catching_open(*args): finally: del images.open self.assert_(FileWrapper._closed) + + class InconsistentGetImageDimensionsBug(TestCase): + """ + Test that get_image_dimensions() works properly after various calls using a file handler (#11158) + """ + def test_multiple_calls(self): + """ + Multiple calls of get_image_dimensions() should return the same size. + """ + from django.core.files.images import ImageFile + img_path = os.path.join(os.path.dirname(__file__), "test.png") + image = ImageFile(open(img_path)) + image_pil = Image.open(img_path) + size_1, size_2 = get_image_dimensions(image), get_image_dimensions(image) + self.assertEqual(image_pil.size, size_1) + self.assertEqual(size_1, size_2) From 924ae1a09c0e0fd470f9f80debe7466a193bb54d Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Fri, 10 Sep 2010 19:19:08 +0000 Subject: [PATCH 125/902] [1.2.X] Pass commit=False to loaddata in tests, which will keep the DB connection open. Closing the connection has the side-effect on some DBs (Postgres, MySQL/InnoDB) of rolling back the transaction that was in progress, causing these tests to fail. r13717 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13718 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/fixtures_model_package/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/modeltests/fixtures_model_package/tests.py b/tests/modeltests/fixtures_model_package/tests.py index 328f8b849854..1fae5ee8078c 100644 --- a/tests/modeltests/fixtures_model_package/tests.py +++ b/tests/modeltests/fixtures_model_package/tests.py @@ -35,7 +35,7 @@ def test_initial_data(self): def test_loaddata(self): "Fixtures can load data into models defined in packages" # Load fixture 1. Single JSON file, with two objects - management.call_command("loaddata", "fixture1.json", verbosity=0) + management.call_command("loaddata", "fixture1.json", verbosity=0, commit=False) self.assertQuerysetEqual( Article.objects.all(), [ "Time to reform copyright", @@ -47,7 +47,7 @@ def test_loaddata(self): # Load fixture 2. JSON file imported by default. Overwrites some # existing objects - management.call_command("loaddata", "fixture2.json", verbosity=0) + management.call_command("loaddata", "fixture2.json", verbosity=0, commit=False) self.assertQuerysetEqual( Article.objects.all(), [ "Django conquers world!", @@ -59,7 +59,7 @@ def test_loaddata(self): ) # Load a fixture that doesn't exist - management.call_command("loaddata", "unknown.json", verbosity=0) + management.call_command("loaddata", "unknown.json", verbosity=0, commit=False) self.assertQuerysetEqual( Article.objects.all(), [ "Django conquers world!", From de91b33a7888640142d10e9a82db39597e01182d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 10 Sep 2010 19:22:06 +0000 Subject: [PATCH 126/902] [1.2.X] Fixed #14245 -- Added some files and paths missing from the MANIFEST.in. Thanks to pmclanahan for the report. Backport of r13719 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13720 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- MANIFEST.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 3fbad67b1d2c..be5f30b796a7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,9 +3,10 @@ include AUTHORS include INSTALL include LICENSE include MANIFEST.in -include django/utils/simplejson/LICENSE.txt include django/contrib/gis/gdal/LICENSE include django/contrib/gis/geos/LICENSE +include django/dispatch/LICENSE.txt +include django/utils/simplejson/LICENSE.txt recursive-include docs * recursive-include scripts * recursive-include examples * @@ -20,6 +21,8 @@ recursive-include django/contrib/auth/tests/templates * recursive-include django/contrib/comments/templates * recursive-include django/contrib/databrowse/templates * recursive-include django/contrib/formtools/templates * +recursive-include django/contrib/flatpages/fixtures * +recursive-include django/contrib/flatpages/tests/templates * recursive-include django/contrib/gis/templates * recursive-include django/contrib/gis/tests/data * recursive-include django/contrib/gis/tests/geoapp/fixtures * From 19cce7925337dcabd4108fe2813f9f2d9ac230f2 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Fri, 10 Sep 2010 19:46:25 +0000 Subject: [PATCH 127/902] [1.2.X] Fixed typos in es_AR translation. Backport of r13572 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13724 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../conf/locale/es_AR/LC_MESSAGES/django.mo | Bin 77722 -> 77720 bytes .../conf/locale/es_AR/LC_MESSAGES/django.po | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.mo b/django/conf/locale/es_AR/LC_MESSAGES/django.mo index c538f9472aec7449e844cb2182fc5ea3d13044e8..692171d7afcc011cbad418804b002141d507c024 100644 GIT binary patch delta 4094 zcmXZecW_lj6vy$sFA0I5A=Cgu8W0GNKe|>iM?%O?kcK6=5x;S!WapdmX9sJ79 zxt(Rs)xtwq6Tim#c-@S;?_53V4N%X!V;FNW1Lt8X7GXm?hfVN4K7dV}=i;y}Mq(d~ z!GX^6T}2AHG(_QO)C3bS8Vj%nzJNV(B|eN7%mmMK-Kmekez*ZOUMa@npEwvpAD+*bO5ie9vW3=oJyT9OqNNfz5Dgq*uPeH*pm8FL5h|qdYegzs23yzk=t6<6TsM z+0mY>gv(J2TaOiRi@C#|@Aa*41eKEG*5S0(&!I9=g381#?2Dm_o~w!jP!o(WpFmAC z5tV^ysD87t5iUUmwhy)C{!t40!CCVnDv%QMH`E^9!**D)lIOZ&H|&73u^R42O>_da z1s6~glwb<}i{mjd#&aXF5Nk8PyG%g=+(WG}R5>^#@mPg=J=F7NsK{HRGSUT;us7;Z zJ&6i@GAe*)Q2l&+{-V_vTK#pb#QbhmdBM4js8hNF75Nd2!0%84{a{|Q=Rc!vg?k{# zKz&r88TcT!M`fzQ%S;PD0C#VTeq6Ro^Ua(P~-fFb@2-7OqE$Zwkr2u4QW+_i84_u?twZ?kD|6@lsN@8;d7`r zXCWry5>%kOQ7b)Y&yQg`^^?|q535s;s1_LSQ_zHAR0=zz-hf%CL--UbL$gt-TVZZP z^?M(6NKd0CK8p&Z7&Yz<)LHn)p1bNnMk=D-4}L5Kt*}0N7)A}0j>5@?&vA(G16Rf@j+fm<%Lp5><1)bV<@t)g)%PfIrlhjz@?~wTGt6C%E7VJr(+?0gOhMrU9bErcnlT5HB?}c4+Y~UVgu^kF;OcU zPeCgy#4flK_3`-ymGV0Ef{#rK>V2>RJLB)z7@ODkTo25}R9uB>KZ*+Y24-VSLa=qC zQDN-A)8fQzQAFOB_4NBo&)V=;2b-f~zJU0vLpx)(+Q7c%38ekJnz^&FEn;d)vYoP*Z zf}OA>>Kf;ve!l<}&?29LI=+Fr|7%g#rU+B<2x<#TP=S@A`rkpV_&%zCR7#MsI8;3e zmD#Y_1+{fUP?;Hydhz*BQP9frQ9md|O|Tp_!8+87WHYMYK2%0NLJgSNz;hF^Cw9Ph za5I*mGW1HrAfUyl1*}G`d<(LrzS}`T5gkTl-~_5;anQltL`{4b)i1hHFmX*(Dih7d z*4_ejRx)uI_QL_V9=qX9RN&!M1<3ttOF=8nL_O$X4l3`!wL)d)F;r@&p#IV1V*;+m zMtBf=Vlh644H|oH46el-ypMXG)5LR0xC&e8{vV>C0B)fIxPwZOOAFegQSEW471u@W zT?(pSD=c3zDntFuT+|kfN8OT%sQv}!JoI(AUZ$WuUWDqn&N}QtrT!p#_^CZVjvDy9 zS&Yii74t7trlOk$U%6_itxZL3Z70-%`Zl%upGAWv9)~(~&shC=)DIV<2Hb?2co*tp zQiPiL1nNz90kxnqEdR`ggK^_gziW!hbaPZD+K0LSny@PkvKJ~715g7$jv6Qr({L{8 zhucy8_gMV^D!>m=D?EYPqBE$8ubHLhulD>8pMoL^H49Q*8MWftsEN~213YZ@MxBBF zs0p)Bdq2Y3C!0^BQe1!vcs_=3Au902_S}Dif+kppIy@Uud;YF@1U11IsD9_ni>Nbk z8TDqnic0-|s0@VCgFs_Z3#^3=F%cC=FJvCy^`)RxWutyL9u>%R)GK&4w!zm?nfV+Q z$VF5pE~7G4irTs|)Pz-<2P>^@CZICW5EW<`L%RPN6!e4Es6aZSQa8xVMNKdU75PkT zjrpj7wxa^sjSB35)el?!W2+xWjq??1o~xLiP2n~L9k!Gffo)I$^+8QA1hoa(s68Kt z+M=oUyZ{xz9Mr%s6}{bWXnss)ddrNYmhC#WX;l<)bYIt^LuFG__NOI#Gh+V-AjXWh delta 4098 zcmXZedr($I7{~EF+!Sw!X&@=QAfO;5BqBnhWZoUqykMnfmLyK1HmOnBF~e$VG%-sv zn-R;b(bN)^wDH!|bTOMTD!X_qL(NNchCE-Iv%n5c*NFA z1%4~%+~#Y}-H5xf10KfCc*cyn?p!D8si@~A*d0qT2cO1tti@FP5xb#tp40dl*cS6K z0taIY9N|3QHK$NYLnKZ?O;Co-@Cj^>RX6}&#S%PfCV8IgPyId|g6mM@ox?VG2}fdh znCA-dPMnWRupdT-`<@$3VPJURN?c6+EN0=X2(M{{Yj864FY#^6iuBwg_ziBwVNssD z3;#t0IIfxJqH!f^VKo?q8_do2e7kRjy{MFYW*zFS{v#?A4X8|9#K9Qe+;g!w95ult z^M2Gs<){qIM)mVC4XaUs?L=+4zn_9$IAR`01=3*tiQ26g5Eurr>}00ESw4ZW1oWcwTqEP*4Dks1=5{3=T;=#!&B!dY+AnJP(zTVhrIR z)S-G175GD_0Oq6mEwty)S$&DsU&d(WcdMHU&b^5`rJGTa@5ONZ7B$ch=Fj&0H`J~0 zS_K(MLRoD@iqYl+39EDM>JvS6faU9lQ zAH0lOac-O7EEJ*w>5om<4VAeO);=D!&`IcrC{$3;9=?d|iCc$?a5t*sN2o(qZ|y&z zCj8l+pF?e3BTmHFm|!a^Q2i=V<1IqX^SsqxkKz7n!u8g$$NU5};UUxj_2yA)Z?O7# z)XM(0`gJ@+y?NUp1K**>Ife;%3U#KgTRkS0`>%$~*kGbU)QSh74%1lFmfUO3L{0cO zw#3Dlgw?1(-$Si*mpwm#+0+kNdn4XJJ)&J;yiY+BW}#Bp7xe|a6LkouqcWs^O5G}R zBdXsX)FG`$O?(6u$O+WAXHjS2sy+8^2r?3l`abwE6tu!b^e_uG(9Ng}@0lqszA&v(Zu{TDw4^DS6CR1O5x+Oa> z1M@q0?hc%S`VrcSnfMcGkDJ{XeCbk9KVm~r&&#Y{jrr8K;3$pUKtZRrcbw47P3x z>deeQUwc?#59Xova-sPw>I1SAwWlwk_Wo5=U~iz_--H^l)}Gg)`X4}z_o=mih3fyk zwVzDn{_98M3=JCas&xoY3a({K)Brc3PHPHkqFih5g}N2Dq9(ipHO@HHp`L8*5240+ z7({jZ=_?4<<#o1s2r?W~@H z%51jT54ClpQJJ|1)!#3tpp{jkUU&vI!79`QHK-5C22{T~R7O5V4OrC0bJKAk7T{)F zkEc-?T9O(BSdCi18q~_)Mz+*Jm6?gCt*Jo$qp8G1T!U%2 z8wcP?d<@gNdTt7?#c}9$3!ax^i27@Ii|+r26coVkr~s~@QWTaEv`3@b+o4w63AJ~r zsD63av|?0-hM5ykTTq6&B{NX{7noJ(>u|k5L3_Lm)v?Ap)S^Q41QHY4?8&4Vriw>d-xE^(Rp;R-*=d3pMe3s2`K< zsEH4vzI4Y>3v%6qi6c?tc0|3Gh01geDieLWbN@AAe;VWgIgtvCTSaSzl01I)pwGcX)A z;TY83Pqy}%=3G>Y7oY-u7Q=8UD)4H1?ysbv32IP>XFY1qx0(A;6MTv4_mg=7btX=s zzHAMs)L%zsAR;>mv@L3Z@tBIqs6YlG^Z0Hk1*K{{>cuitAP=KH!3!}DUqNN&3sfK{ zP?X-+^*FclT~JnV^; zsDa)^1@b;Buw7Q)WA#t0{yA!#!>D;0FncV8e<(YPysAL4gCDJcZQ6b-=ZKpCqI;%UzpQtTll^m#a`jI53W_Dc!fK=CVO+*{13mR BiTMBk diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.po b/django/conf/locale/es_AR/LC_MESSAGES/django.po index ab79df3f5038..dad102ead76e 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/django.po +++ b/django/conf/locale/es_AR/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-08-06 16:06-0300\n" -"PO-Revision-Date: 2010-08-06 16:05-0300\n" +"PO-Revision-Date: 2010-09-10 16:42-0300\n" "Last-Translator: Ramiro \n" "Language-Team: Django-I18N \n" "Language: es_AR\n" @@ -881,7 +881,7 @@ msgstr "" #: contrib/admin/templates/admin/auth/user/change_password.html:35 #: contrib/auth/forms.py:17 contrib/auth/forms.py:61 contrib/auth/forms.py:186 msgid "Password" -msgstr "Contraseña:" +msgstr "Contraseña" #: contrib/admin/templates/admin/auth/user/change_password.html:41 #: contrib/admin/templates/registration/password_change_form.html:37 @@ -1251,7 +1251,7 @@ msgstr "Cambiar contraseña: %s" #: contrib/auth/forms.py:14 contrib/auth/forms.py:48 contrib/auth/forms.py:60 msgid "Username" -msgstr "Nombre de usuario:" +msgstr "Nombre de usuario" #: contrib/auth/forms.py:15 contrib/auth/forms.py:49 msgid "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only." From ebda0e014a49bd26d08436ba222e2047e6b071c3 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 10 Sep 2010 20:55:09 +0000 Subject: [PATCH 128/902] [1.2.X] Converted doctest to unittest. Patch by Alex Gaynor. Backport from trunk (r13725). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13726 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/choices/models.py | 26 -------------------------- tests/modeltests/choices/tests.py | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 26 deletions(-) create mode 100644 tests/modeltests/choices/tests.py diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py index e37826059879..27316f5deae7 100644 --- a/tests/modeltests/choices/models.py +++ b/tests/modeltests/choices/models.py @@ -22,29 +22,3 @@ class Person(models.Model): def __unicode__(self): return self.name - -__test__ = {'API_TESTS':""" ->>> a = Person(name='Adrian', gender='M') ->>> a.save() ->>> s = Person(name='Sara', gender='F') ->>> s.save() ->>> a.gender -'M' ->>> s.gender -'F' ->>> a.get_gender_display() -u'Male' ->>> s.get_gender_display() -u'Female' - -# If the value for the field doesn't correspond to a valid choice, -# the value itself is provided as a display value. ->>> a.gender = '' ->>> a.get_gender_display() -u'' - ->>> a.gender = 'U' ->>> a.get_gender_display() -u'U' - -"""} diff --git a/tests/modeltests/choices/tests.py b/tests/modeltests/choices/tests.py new file mode 100644 index 000000000000..09023d81133c --- /dev/null +++ b/tests/modeltests/choices/tests.py @@ -0,0 +1,23 @@ +from django.test import TestCase + +from models import Person + + +class ChoicesTests(TestCase): + def test_display(self): + a = Person.objects.create(name='Adrian', gender='M') + s = Person.objects.create(name='Sara', gender='F') + self.assertEqual(a.gender, 'M') + self.assertEqual(s.gender, 'F') + + self.assertEqual(a.get_gender_display(), 'Male') + self.assertEqual(s.get_gender_display(), 'Female') + + # If the value for the field doesn't correspond to a valid choice, + # the value itself is provided as a display value. + a.gender = '' + self.assertEqual(a.get_gender_display(), '') + + a.gender = 'U' + self.assertEqual(a.get_gender_display(), 'U') + From aec5cbc370f9146cdb2b78d825f308232da19d6d Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Fri, 10 Sep 2010 20:59:36 +0000 Subject: [PATCH 129/902] [1.2.X] Fixed #13799, a test failure on Postgres. Thanks, Alex. r13400 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13727 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../m2m_through_regress/tests.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/regressiontests/m2m_through_regress/tests.py b/tests/regressiontests/m2m_through_regress/tests.py index ff1d95020e9a..2eaf8861fc2a 100644 --- a/tests/regressiontests/m2m_through_regress/tests.py +++ b/tests/regressiontests/m2m_through_regress/tests.py @@ -66,11 +66,13 @@ def test_serialization(self): p = Person.objects.create(name="Bob") g = Group.objects.create(name="Roll") - Membership.objects.create(person=p, group=g) + m = Membership.objects.create(person=p, group=g) + + pks = {"p_pk": p.pk, "g_pk": g.pk, "m_pk": m.pk} out = StringIO() management.call_command("dumpdata", "m2m_through_regress", format="json", stdout=out) - self.assertEqual(out.getvalue().strip(), """[{"pk": 1, "model": "m2m_through_regress.membership", "fields": {"person": 1, "price": 100, "group": 1}}, {"pk": 1, "model": "m2m_through_regress.person", "fields": {"name": "Bob"}}, {"pk": 1, "model": "m2m_through_regress.group", "fields": {"name": "Roll"}}]""") + self.assertEqual(out.getvalue().strip(), """[{"pk": %(m_pk)s, "model": "m2m_through_regress.membership", "fields": {"person": %(p_pk)s, "price": 100, "group": %(g_pk)s}}, {"pk": %(p_pk)s, "model": "m2m_through_regress.person", "fields": {"name": "Bob"}}, {"pk": %(g_pk)s, "model": "m2m_through_regress.group", "fields": {"name": "Roll"}}]""" % pks) out = StringIO() management.call_command("dumpdata", "m2m_through_regress", format="xml", @@ -78,19 +80,19 @@ def test_serialization(self): self.assertEqual(out.getvalue().strip(), """ - - 1 - 1 + + %(p_pk)s + %(g_pk)s 100 - + Bob - + Roll - """.strip()) + """.strip() % pks) def test_join_trimming(self): "Check that we don't involve too many copies of the intermediate table when doing a join. Refs #8046, #8254" From 43988e98357b6055749dadf5bf2f34dd243abc78 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Fri, 10 Sep 2010 22:54:52 +0000 Subject: [PATCH 130/902] [1.2.X] Fixed #13095 -- `formfield_callback` keyword argument is now more sane and works with widgets defined in `ModelForm.Meta.widgets`. Thanks, hvdklauw for bug report, vung for initial patch, and carljm for review. Backport of r13730 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13731 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/forms/models.py | 20 +++--- .../model_forms_regress/tests.py | 44 +++++++++++++ .../model_formsets_regress/tests.py | 62 ++++++++++++++++++- 3 files changed, 118 insertions(+), 8 deletions(-) diff --git a/django/forms/models.py b/django/forms/models.py index a14a09f5534b..3a288203d39b 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -150,7 +150,7 @@ def model_to_dict(instance, fields=None, exclude=None): data[f.name] = f.value_from_object(instance) return data -def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): +def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None): """ Returns a ``SortedDict`` containing form fields for the given model. @@ -175,7 +175,14 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c kwargs = {'widget': widgets[f.name]} else: kwargs = {} - formfield = formfield_callback(f, **kwargs) + + if formfield_callback is None: + formfield = f.formfield(**kwargs) + elif not callable(formfield_callback): + raise TypeError('formfield_callback must be a function or callable') + else: + formfield = formfield_callback(f, **kwargs) + if formfield: field_list.append((f.name, formfield)) else: @@ -198,8 +205,7 @@ def __init__(self, options=None): class ModelFormMetaclass(type): def __new__(cls, name, bases, attrs): - formfield_callback = attrs.pop('formfield_callback', - lambda f, **kwargs: f.formfield(**kwargs)) + formfield_callback = attrs.pop('formfield_callback', None) try: parents = [b for b in bases if issubclass(b, ModelForm)] except NameError: @@ -376,7 +382,7 @@ class ModelForm(BaseModelForm): __metaclass__ = ModelFormMetaclass def modelform_factory(model, form=ModelForm, fields=None, exclude=None, - formfield_callback=lambda f: f.formfield()): + formfield_callback=None): # Create the inner Meta class. FIXME: ideally, we should be able to # construct a ModelForm without creating and passing in a temporary # inner class. @@ -658,7 +664,7 @@ def pk_is_not_editable(pk): form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=HiddenInput) super(BaseModelFormSet, self).add_fields(form, index) -def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(), +def modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None): @@ -813,7 +819,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, - formfield_callback=lambda f: f.formfield()): + formfield_callback=None): """ Returns an ``InlineFormSet`` for the given kwargs. diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py index 2e77a8897763..6908ceb4b593 100644 --- a/tests/regressiontests/model_forms_regress/tests.py +++ b/tests/regressiontests/model_forms_regress/tests.py @@ -250,3 +250,47 @@ def test_http_prefixing(self): form.is_valid() # self.assertTrue(form.is_valid()) # self.assertEquals(form.cleaned_data['url'], 'http://example.com/test') + + +class FormFieldCallbackTests(TestCase): + + def test_baseform_with_widgets_in_meta(self): + """Regression for #13095: Using base forms with widgets defined in Meta should not raise errors.""" + widget = forms.Textarea() + + class BaseForm(forms.ModelForm): + class Meta: + model = Person + widgets = {'name': widget} + + Form = modelform_factory(Person, form=BaseForm) + self.assertTrue(Form.base_fields['name'].widget is widget) + + def test_custom_callback(self): + """Test that a custom formfield_callback is used if provided""" + + callback_args = [] + + def callback(db_field, **kwargs): + callback_args.append((db_field, kwargs)) + return db_field.formfield(**kwargs) + + widget = forms.Textarea() + + class BaseForm(forms.ModelForm): + class Meta: + model = Person + widgets = {'name': widget} + + _ = modelform_factory(Person, form=BaseForm, + formfield_callback=callback) + id_field, name_field = Person._meta.fields + + self.assertEqual(callback_args, + [(id_field, {}), (name_field, {'widget': widget})]) + + def test_bad_callback(self): + # A bad callback provided by user still gives an error + self.assertRaises(TypeError, modelform_factory, Person, + formfield_callback='not a function or callable') + diff --git a/tests/regressiontests/model_formsets_regress/tests.py b/tests/regressiontests/model_formsets_regress/tests.py index 61bc5143246e..ee2a26f6c247 100644 --- a/tests/regressiontests/model_formsets_regress/tests.py +++ b/tests/regressiontests/model_formsets_regress/tests.py @@ -1,8 +1,10 @@ -from django.forms.models import modelform_factory, inlineformset_factory +from django import forms +from django.forms.models import modelform_factory, inlineformset_factory, modelformset_factory from django.test import TestCase from models import User, UserSite, Restaurant, Manager + class InlineFormsetTests(TestCase): def test_formset_over_to_field(self): "A formset over a ForeignKey with a to_field can be saved. Regression for #10243" @@ -156,3 +158,61 @@ def test_formset_with_none_instance(self): # you can create a formset with an instance of None form = Form(instance=None) formset = FormSet(instance=None) + + +class CustomWidget(forms.CharField): + pass + + +class UserSiteForm(forms.ModelForm): + class Meta: + model = UserSite + widgets = {'data': CustomWidget} + + +class Callback(object): + + def __init__(self): + self.log = [] + + def __call__(self, db_field, **kwargs): + self.log.append((db_field, kwargs)) + return db_field.formfield(**kwargs) + + +class FormfieldCallbackTests(TestCase): + """ + Regression for #13095: Using base forms with widgets + defined in Meta should not raise errors. + """ + + def test_inlineformset_factory_default(self): + Formset = inlineformset_factory(User, UserSite, form=UserSiteForm) + form = Formset({}).forms[0] + self.assertTrue(isinstance(form['data'].field.widget, CustomWidget)) + + def test_modelformset_factory_default(self): + Formset = modelformset_factory(UserSite, form=UserSiteForm) + form = Formset({}).forms[0] + self.assertTrue(isinstance(form['data'].field.widget, CustomWidget)) + + def assertCallbackCalled(self, callback): + id_field, user_field, data_field = UserSite._meta.fields + expected_log = [ + (id_field, {}), + (user_field, {}), + (data_field, {'widget': CustomWidget}), + ] + self.assertEqual(callback.log, expected_log) + + def test_inlineformset_custom_callback(self): + callback = Callback() + inlineformset_factory(User, UserSite, form=UserSiteForm, + formfield_callback=callback) + self.assertCallbackCalled(callback) + + def test_modelformset_custom_callback(self): + callback = Callback() + modelformset_factory(UserSite, form=UserSiteForm, + formfield_callback=callback) + self.assertCallbackCalled(callback) From 890b0b6234f3ae742a9cb97f27fe0bb7d900b93b Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 10 Sep 2010 23:11:24 +0000 Subject: [PATCH 131/902] [1.2.X] Fixed #14235 - UnicodeDecodeError in CSRF middleware Thanks to jbg for the report. This changeset essentially backs out [13698] in favour of a method that sanitizes the token rather than escaping it. Backport of [13732] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13733 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/middleware/csrf.py | 23 ++++++++++++++++----- django/template/defaulttags.py | 3 +-- tests/regressiontests/csrf_tests/tests.py | 25 ++++++++++++++++------- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index 59eef72315f8..4a80428e556e 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -13,7 +13,6 @@ from django.core.urlresolvers import get_callable from django.utils.cache import patch_vary_headers from django.utils.hashcompat import md5_constructor -from django.utils.html import escape from django.utils.safestring import mark_safe _POST_FORM_RE = \ @@ -53,8 +52,8 @@ def _make_legacy_session_token(session_id): def get_token(request): """ - Returns the the CSRF token required for a POST form. No assumptions should - be made about what characters might be in the CSRF token. + Returns the the CSRF token required for a POST form. The token is an + alphanumeric value. A side effect of calling this function is to make the the csrf_protect decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' @@ -65,6 +64,17 @@ def get_token(request): return request.META.get("CSRF_COOKIE", None) +def _sanitize_token(token): + # Allow only alphanum, and ensure we return a 'str' for the sake of the post + # processing middleware. + token = re.sub('[^a-zA-Z0-9]', '', str(token.decode('ascii', 'ignore'))) + if token == "": + # In case the cookie has been truncated to nothing at some point. + return _get_new_csrf_key() + else: + return token + + class CsrfViewMiddleware(object): """ Middleware that requires a present and correct csrfmiddlewaretoken @@ -90,7 +100,10 @@ def accept(): # request, so it's available to the view. We'll store it in a cookie when # we reach the response. try: - request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME] + # In case of cookies from untrusted sources, we strip anything + # dangerous at this point, so that the cookie + token will have the + # same, sanitized value. + request.META["CSRF_COOKIE"] = _sanitize_token(request.COOKIES[settings.CSRF_COOKIE_NAME]) cookie_is_new = False except KeyError: # No cookie, so create one. This will be sent with the next @@ -235,7 +248,7 @@ def add_csrf_field(match): """Returns the matched tag plus the added element""" return mark_safe(match.group() + "
        " + \ "
        ") # Modify any POST forms diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 0914b1c3b19b..1b0741353037 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -9,7 +9,6 @@ from django.template import get_library, Library, InvalidTemplateLibrary from django.template.smartif import IfParser, Literal from django.conf import settings -from django.utils.html import escape from django.utils.encoding import smart_str, smart_unicode from django.utils.safestring import mark_safe @@ -43,7 +42,7 @@ def render(self, context): if csrf_token == 'NOTPROVIDED': return mark_safe(u"") else: - return mark_safe(u"
        " % escape(csrf_token)) + return mark_safe(u"
        " % csrf_token) else: # It's very probable that the token is missing because of # misconfiguration, so we raise a warning diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py index fadecf565efd..8fbc7669a57a 100644 --- a/tests/regressiontests/csrf_tests/tests.py +++ b/tests/regressiontests/csrf_tests/tests.py @@ -6,15 +6,14 @@ from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt from django.core.context_processors import csrf from django.contrib.sessions.middleware import SessionMiddleware -from django.utils.html import escape from django.utils.importlib import import_module from django.conf import settings from django.template import RequestContext, Template # Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests def post_form_response(): - resp = HttpResponse(content=""" - + resp = HttpResponse(content=u""" +

        \u00a1Unicode!
        """, mimetype="text/html") return resp @@ -58,8 +57,9 @@ def is_secure(self): class CsrfMiddlewareTest(TestCase): # The csrf token is potentially from an untrusted source, so could have - # characters that need escaping - _csrf_id = "<1>" + # characters that need dealing with. + _csrf_id_cookie = "<1>\xc2\xa1" + _csrf_id = "1" # This is a valid session token for this ID and secret key. This was generated using # the old code that we're to be backwards-compatible with. Don't use the CSRF code @@ -74,7 +74,7 @@ def _get_GET_no_csrf_cookie_request(self): def _get_GET_csrf_cookie_request(self): req = TestingHttpRequest() - req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id + req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id_cookie return req def _get_POST_csrf_cookie_request(self): @@ -104,7 +104,7 @@ def _get_POST_session_request_no_token(self): return req def _check_token_present(self, response, csrf_id=None): - self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % escape(csrf_id or self._csrf_id)) + self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id)) # Check the post processing and outgoing cookie def test_process_response_no_csrf_cookie(self): @@ -290,6 +290,17 @@ def test_token_node_no_csrf_cookie(self): resp = token_view(req) self.assertEquals(u"", resp.content) + def test_token_node_empty_csrf_cookie(self): + """ + Check that we get a new token if the csrf_cookie is the empty string + """ + req = self._get_GET_no_csrf_cookie_request() + req.COOKIES[settings.CSRF_COOKIE_NAME] = "" + CsrfViewMiddleware().process_view(req, token_view, (), {}) + resp = token_view(req) + + self.assertNotEqual(u"", resp.content) + def test_token_node_with_csrf_cookie(self): """ Check that CsrfTokenNode works when a CSRF cookie is set From 260eff568423fca4af6456290a8c17a265a04038 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 10 Sep 2010 23:58:52 +0000 Subject: [PATCH 132/902] [1.2.X] Fixed a test so that it actually tests what it's supposed to test. Previously it passed whether or not the view was 'csrf_exempt'ed. Backport of [13735] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13736 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/csrf_tests/tests.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py index 8fbc7669a57a..9030d397ab65 100644 --- a/tests/regressiontests/csrf_tests/tests.py +++ b/tests/regressiontests/csrf_tests/tests.py @@ -207,8 +207,11 @@ def test_process_response_exempt_view(self): """ Check that no post processing is done for an exempt view """ - req = self._get_POST_csrf_cookie_request() - resp = csrf_exempt(post_form_view)(req) + req = self._get_GET_csrf_cookie_request() + view = csrf_exempt(post_form_view) + CsrfMiddleware().process_view(req, view, (), {}) + + resp = view(req) resp_content = resp.content resp2 = CsrfMiddleware().process_response(req, resp) self.assertEquals(resp_content, resp2.content) From 8829b7d9567a63c5c548272e61447bc2bfdddb12 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 11 Sep 2010 02:30:39 +0000 Subject: [PATCH 133/902] [1.2.X] Fixed #12632 -- Improved performance of `SortedDict`. Thanks, Alex Gaynor. Backport of r13742 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13743 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/datastructures.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index 3cbbe27b91c7..d73963fdce7a 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -99,9 +99,11 @@ def __init__(self, data=None): self.keyOrder = data.keys() else: self.keyOrder = [] + seen = set() for key, value in data: - if key not in self.keyOrder: + if key not in seen: self.keyOrder.append(key) + seen.add(key) def __deepcopy__(self, memo): return self.__class__([(key, deepcopy(value, memo)) From ff0cb11c1b26f962f3ef522c9b6021482dffa1a3 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 11 Sep 2010 03:04:28 +0000 Subject: [PATCH 134/902] [1.2.X] Fixed #13599 -- No longer embed hidden `

        - -# The error_html_class and required_html_class attributes #################### - ->>> class Person(Form): -... name = CharField() -... is_cool = NullBooleanField() -... email = EmailField(required=False) -... age = IntegerField() - ->>> p = Person({}) ->>> p.error_css_class = 'error' ->>> p.required_css_class = 'required' - ->>> print p.as_ul() -
        • This field is required.
      • -
      • -
      • -
        • This field is required.
      • - ->>> print p.as_p() -
        • This field is required.
        -

        -

        -

        -
        • This field is required.
        -

        - ->>> print p.as_table() - - - - - - - -# Checking that the label for SplitDateTimeField is not being displayed ##### - ->>> class EventForm(Form): -... happened_at = SplitDateTimeField(widget=widgets.SplitHiddenDateTimeWidget) -... ->>> form = EventForm() ->>> form.as_ul() -u'' - -""" diff --git a/tests/regressiontests/forms/formsets.py b/tests/regressiontests/forms/formsets.py deleted file mode 100644 index fade987c26ea..000000000000 --- a/tests/regressiontests/forms/formsets.py +++ /dev/null @@ -1,724 +0,0 @@ -# -*- coding: utf-8 -*- -tests = """ -# Basic FormSet creation and usage ############################################ - -FormSet allows us to use multiple instance of the same form on 1 page. For now, -the best way to create a FormSet is by using the formset_factory function. - ->>> from django.forms import Form, CharField, IntegerField, ValidationError ->>> from django.forms.formsets import formset_factory, BaseFormSet - ->>> class Choice(Form): -... choice = CharField() -... votes = IntegerField() - ->>> ChoiceFormSet = formset_factory(Choice) - -A FormSet constructor takes the same arguments as Form. Let's create a FormSet -for adding data. By default, it displays 1 blank form. It can display more, -but we'll look at how to do so later. - ->>> formset = ChoiceFormSet(auto_id=False, prefix='choices') ->>> print formset - - - - - -On thing to note is that there needs to be a special value in the data. This -value tells the FormSet how many forms were displayed so it can tell how -many forms it needs to clean and validate. You could use javascript to create -new forms on the client side, but they won't get validated unless you increment -the TOTAL_FORMS field appropriately. - ->>> data = { -... 'choices-TOTAL_FORMS': '1', # the number of forms rendered -... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '100', -... } - -We treat FormSet pretty much like we would treat a normal Form. FormSet has an -is_valid method, and a cleaned_data or errors attribute depending on whether all -the forms passed validation. However, unlike a Form instance, cleaned_data and -errors will be a list of dicts rather than just a single dict. - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -True ->>> [form.cleaned_data for form in formset.forms] -[{'votes': 100, 'choice': u'Calexico'}] - -If a FormSet was not passed any data, its is_valid method should return False. ->>> formset = ChoiceFormSet() ->>> formset.is_valid() -False - -FormSet instances can also have an error attribute if validation failed for -any of the forms. - ->>> data = { -... 'choices-TOTAL_FORMS': '1', # the number of forms rendered -... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -False ->>> formset.errors -[{'votes': [u'This field is required.']}] - - -We can also prefill a FormSet with existing data by providing an ``initial`` -argument to the constructor. ``initial`` should be a list of dicts. By default, -an extra blank form is included. - ->>> initial = [{'choice': u'Calexico', 'votes': 100}] ->>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices') ->>> for form in formset.forms: -... print form.as_ul() -
      • Choice:
      • -
      • Votes:
      • -
      • Choice:
      • -
      • Votes:
      • - - -Let's simulate what would happen if we submitted this form. - ->>> data = { -... 'choices-TOTAL_FORMS': '2', # the number of forms rendered -... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '100', -... 'choices-1-choice': '', -... 'choices-1-votes': '', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -True ->>> [form.cleaned_data for form in formset.forms] -[{'votes': 100, 'choice': u'Calexico'}, {}] - -But the second form was blank! Shouldn't we get some errors? No. If we display -a form as blank, it's ok for it to be submitted as blank. If we fill out even -one of the fields of a blank form though, it will be validated. We may want to -required that at least x number of forms are completed, but we'll show how to -handle that later. - ->>> data = { -... 'choices-TOTAL_FORMS': '2', # the number of forms rendered -... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '100', -... 'choices-1-choice': 'The Decemberists', -... 'choices-1-votes': '', # missing value -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -False ->>> formset.errors -[{}, {'votes': [u'This field is required.']}] - -If we delete data that was pre-filled, we should get an error. Simply removing -data from form fields isn't the proper way to delete it. We'll see how to -handle that case later. - ->>> data = { -... 'choices-TOTAL_FORMS': '2', # the number of forms rendered -... 'choices-INITIAL_FORMS': '1', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': '', # deleted value -... 'choices-0-votes': '', # deleted value -... 'choices-1-choice': '', -... 'choices-1-votes': '', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -False ->>> formset.errors -[{'votes': [u'This field is required.'], 'choice': [u'This field is required.']}, {}] - - -# Displaying more than 1 blank form ########################################### - -We can also display more than 1 empty form at a time. To do so, pass a -extra argument to formset_factory. - ->>> ChoiceFormSet = formset_factory(Choice, extra=3) - ->>> formset = ChoiceFormSet(auto_id=False, prefix='choices') ->>> for form in formset.forms: -... print form.as_ul() -
      • Choice:
      • -
      • Votes:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Choice:
      • -
      • Votes:
      • - -Since we displayed every form as blank, we will also accept them back as blank. -This may seem a little strange, but later we will show how to require a minimum -number of forms to be completed. - ->>> data = { -... 'choices-TOTAL_FORMS': '3', # the number of forms rendered -... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': '', -... 'choices-0-votes': '', -... 'choices-1-choice': '', -... 'choices-1-votes': '', -... 'choices-2-choice': '', -... 'choices-2-votes': '', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -True ->>> [form.cleaned_data for form in formset.forms] -[{}, {}, {}] - - -We can just fill out one of the forms. - ->>> data = { -... 'choices-TOTAL_FORMS': '3', # the number of forms rendered -... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '100', -... 'choices-1-choice': '', -... 'choices-1-votes': '', -... 'choices-2-choice': '', -... 'choices-2-votes': '', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -True ->>> [form.cleaned_data for form in formset.forms] -[{'votes': 100, 'choice': u'Calexico'}, {}, {}] - - -And once again, if we try to partially complete a form, validation will fail. - ->>> data = { -... 'choices-TOTAL_FORMS': '3', # the number of forms rendered -... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '100', -... 'choices-1-choice': 'The Decemberists', -... 'choices-1-votes': '', # missing value -... 'choices-2-choice': '', -... 'choices-2-votes': '', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -False ->>> formset.errors -[{}, {'votes': [u'This field is required.']}, {}] - - -The extra argument also works when the formset is pre-filled with initial -data. - ->>> initial = [{'choice': u'Calexico', 'votes': 100}] ->>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices') ->>> for form in formset.forms: -... print form.as_ul() -
      • Choice:
      • -
      • Votes:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Choice:
      • -
      • Votes:
      • - -Make sure retrieving an empty form works, and it shows up in the form list - ->>> formset.empty_form.empty_permitted -True ->>> print formset.empty_form.as_ul() -
      • Choice:
      • -
      • Votes:
      • - -# FormSets with deletion ###################################################### - -We can easily add deletion ability to a FormSet with an argument to -formset_factory. This will add a boolean field to each form instance. When -that boolean field is True, the form will be in formset.deleted_forms - ->>> ChoiceFormSet = formset_factory(Choice, can_delete=True) - ->>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}] ->>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices') ->>> for form in formset.forms: -... print form.as_ul() -
      • Choice:
      • -
      • Votes:
      • -
      • Delete:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Delete:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Delete:
      • - -To delete something, we just need to set that form's special delete field to -'on'. Let's go ahead and delete Fergie. - ->>> data = { -... 'choices-TOTAL_FORMS': '3', # the number of forms rendered -... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '100', -... 'choices-0-DELETE': '', -... 'choices-1-choice': 'Fergie', -... 'choices-1-votes': '900', -... 'choices-1-DELETE': 'on', -... 'choices-2-choice': '', -... 'choices-2-votes': '', -... 'choices-2-DELETE': '', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -True ->>> [form.cleaned_data for form in formset.forms] -[{'votes': 100, 'DELETE': False, 'choice': u'Calexico'}, {'votes': 900, 'DELETE': True, 'choice': u'Fergie'}, {}] ->>> [form.cleaned_data for form in formset.deleted_forms] -[{'votes': 900, 'DELETE': True, 'choice': u'Fergie'}] - -If we fill a form with something and then we check the can_delete checkbox for -that form, that form's errors should not make the entire formset invalid since -it's going to be deleted. - ->>> class CheckForm(Form): -... field = IntegerField(min_value=100) - ->>> data = { -... 'check-TOTAL_FORMS': '3', # the number of forms rendered -... 'check-INITIAL_FORMS': '2', # the number of forms with initial data -... 'check-MAX_NUM_FORMS': '0', # max number of forms -... 'check-0-field': '200', -... 'check-0-DELETE': '', -... 'check-1-field': '50', -... 'check-1-DELETE': 'on', -... 'check-2-field': '', -... 'check-2-DELETE': '', -... } ->>> CheckFormSet = formset_factory(CheckForm, can_delete=True) ->>> formset = CheckFormSet(data, prefix='check') ->>> formset.is_valid() -True - -If we remove the deletion flag now we will have our validation back. - ->>> data['check-1-DELETE'] = '' ->>> formset = CheckFormSet(data, prefix='check') ->>> formset.is_valid() -False - -Should be able to get deleted_forms from a valid formset even if a -deleted form would have been invalid. - ->>> class Person(Form): -... name = CharField() - ->>> PeopleForm = formset_factory( -... form=Person, -... can_delete=True) - ->>> p = PeopleForm( -... {'form-0-name': u'', 'form-0-DELETE': u'on', # no name! -... 'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1, -... 'form-MAX_NUM_FORMS': 1}) - ->>> p.is_valid() -True ->>> len(p.deleted_forms) -1 - -# FormSets with ordering ###################################################### - -We can also add ordering ability to a FormSet with an agrument to -formset_factory. This will add a integer field to each form instance. When -form validation succeeds, [form.cleaned_data for form in formset.forms] will have the data in the correct -order specified by the ordering fields. If a number is duplicated in the set -of ordering fields, for instance form 0 and form 3 are both marked as 1, then -the form index used as a secondary ordering criteria. In order to put -something at the front of the list, you'd need to set it's order to 0. - ->>> ChoiceFormSet = formset_factory(Choice, can_order=True) - ->>> initial = [{'choice': u'Calexico', 'votes': 100}, {'choice': u'Fergie', 'votes': 900}] ->>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices') ->>> for form in formset.forms: -... print form.as_ul() -
      • Choice:
      • -
      • Votes:
      • -
      • Order:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Order:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Order:
      • - ->>> data = { -... 'choices-TOTAL_FORMS': '3', # the number of forms rendered -... 'choices-INITIAL_FORMS': '2', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '100', -... 'choices-0-ORDER': '1', -... 'choices-1-choice': 'Fergie', -... 'choices-1-votes': '900', -... 'choices-1-ORDER': '2', -... 'choices-2-choice': 'The Decemberists', -... 'choices-2-votes': '500', -... 'choices-2-ORDER': '0', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -True ->>> for form in formset.ordered_forms: -... print form.cleaned_data -{'votes': 500, 'ORDER': 0, 'choice': u'The Decemberists'} -{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'} -{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'} - -Ordering fields are allowed to be left blank, and if they *are* left blank, -they will be sorted below everything else. - ->>> data = { -... 'choices-TOTAL_FORMS': '4', # the number of forms rendered -... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '100', -... 'choices-0-ORDER': '1', -... 'choices-1-choice': 'Fergie', -... 'choices-1-votes': '900', -... 'choices-1-ORDER': '2', -... 'choices-2-choice': 'The Decemberists', -... 'choices-2-votes': '500', -... 'choices-2-ORDER': '', -... 'choices-3-choice': 'Basia Bulat', -... 'choices-3-votes': '50', -... 'choices-3-ORDER': '', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -True ->>> for form in formset.ordered_forms: -... print form.cleaned_data -{'votes': 100, 'ORDER': 1, 'choice': u'Calexico'} -{'votes': 900, 'ORDER': 2, 'choice': u'Fergie'} -{'votes': 500, 'ORDER': None, 'choice': u'The Decemberists'} -{'votes': 50, 'ORDER': None, 'choice': u'Basia Bulat'} - -Ordering should work with blank fieldsets. - ->>> data = { -... 'choices-TOTAL_FORMS': '3', # the number of forms rendered -... 'choices-INITIAL_FORMS': '0', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -True ->>> for form in formset.ordered_forms: -... print form.cleaned_data - -# FormSets with ordering + deletion ########################################### - -Let's try throwing ordering and deletion into the same form. - ->>> ChoiceFormSet = formset_factory(Choice, can_order=True, can_delete=True) - ->>> initial = [ -... {'choice': u'Calexico', 'votes': 100}, -... {'choice': u'Fergie', 'votes': 900}, -... {'choice': u'The Decemberists', 'votes': 500}, -... ] ->>> formset = ChoiceFormSet(initial=initial, auto_id=False, prefix='choices') ->>> for form in formset.forms: -... print form.as_ul() -
      • Choice:
      • -
      • Votes:
      • -
      • Order:
      • -
      • Delete:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Order:
      • -
      • Delete:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Order:
      • -
      • Delete:
      • -
      • Choice:
      • -
      • Votes:
      • -
      • Order:
      • -
      • Delete:
      • - -Let's delete Fergie, and put The Decemberists ahead of Calexico. - ->>> data = { -... 'choices-TOTAL_FORMS': '4', # the number of forms rendered -... 'choices-INITIAL_FORMS': '3', # the number of forms with initial data -... 'choices-MAX_NUM_FORMS': '0', # max number of forms -... 'choices-0-choice': 'Calexico', -... 'choices-0-votes': '100', -... 'choices-0-ORDER': '1', -... 'choices-0-DELETE': '', -... 'choices-1-choice': 'Fergie', -... 'choices-1-votes': '900', -... 'choices-1-ORDER': '2', -... 'choices-1-DELETE': 'on', -... 'choices-2-choice': 'The Decemberists', -... 'choices-2-votes': '500', -... 'choices-2-ORDER': '0', -... 'choices-2-DELETE': '', -... 'choices-3-choice': '', -... 'choices-3-votes': '', -... 'choices-3-ORDER': '', -... 'choices-3-DELETE': '', -... } - ->>> formset = ChoiceFormSet(data, auto_id=False, prefix='choices') ->>> formset.is_valid() -True ->>> for form in formset.ordered_forms: -... print form.cleaned_data -{'votes': 500, 'DELETE': False, 'ORDER': 0, 'choice': u'The Decemberists'} -{'votes': 100, 'DELETE': False, 'ORDER': 1, 'choice': u'Calexico'} ->>> [form.cleaned_data for form in formset.deleted_forms] -[{'votes': 900, 'DELETE': True, 'ORDER': 2, 'choice': u'Fergie'}] - -Should be able to get ordered forms from a valid formset even if a -deleted form would have been invalid. - ->>> class Person(Form): -... name = CharField() - ->>> PeopleForm = formset_factory( -... form=Person, -... can_delete=True, -... can_order=True) - ->>> p = PeopleForm( -... {'form-0-name': u'', 'form-0-DELETE': u'on', # no name! -... 'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1, -... 'form-MAX_NUM_FORMS': 1}) - ->>> p.is_valid() -True ->>> p.ordered_forms -[] - -# FormSet clean hook ########################################################## - -FormSets have a hook for doing extra validation that shouldn't be tied to any -particular form. It follows the same pattern as the clean hook on Forms. - -Let's define a FormSet that takes a list of favorite drinks, but raises am -error if there are any duplicates. - ->>> class FavoriteDrinkForm(Form): -... name = CharField() -... - ->>> class BaseFavoriteDrinksFormSet(BaseFormSet): -... def clean(self): -... seen_drinks = [] -... for drink in self.cleaned_data: -... if drink['name'] in seen_drinks: -... raise ValidationError('You may only specify a drink once.') -... seen_drinks.append(drink['name']) -... - ->>> FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm, -... formset=BaseFavoriteDrinksFormSet, extra=3) - -We start out with a some duplicate data. - ->>> data = { -... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered -... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data -... 'drinks-MAX_NUM_FORMS': '0', # max number of forms -... 'drinks-0-name': 'Gin and Tonic', -... 'drinks-1-name': 'Gin and Tonic', -... } - ->>> formset = FavoriteDrinksFormSet(data, prefix='drinks') ->>> formset.is_valid() -False - -Any errors raised by formset.clean() are available via the -formset.non_form_errors() method. - ->>> for error in formset.non_form_errors(): -... print error -You may only specify a drink once. - - -Make sure we didn't break the valid case. - ->>> data = { -... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered -... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data -... 'drinks-MAX_NUM_FORMS': '0', # max number of forms -... 'drinks-0-name': 'Gin and Tonic', -... 'drinks-1-name': 'Bloody Mary', -... } - ->>> formset = FavoriteDrinksFormSet(data, prefix='drinks') ->>> formset.is_valid() -True ->>> for error in formset.non_form_errors(): -... print error - -# Limiting the maximum number of forms ######################################## - -# Base case for max_num. - -# When not passed, max_num will take its default value of None, i.e. unlimited -# number of forms, only controlled by the value of the extra parameter. - ->>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3) ->>> formset = LimitedFavoriteDrinkFormSet() ->>> for form in formset.forms: -... print form - - - - -# If max_num is 0 then no form is rendered at all. - ->>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=0) ->>> formset = LimitedFavoriteDrinkFormSet() ->>> for form in formset.forms: -... print form - ->>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=5, max_num=2) ->>> formset = LimitedFavoriteDrinkFormSet() ->>> for form in formset.forms: -... print form - - - -# Ensure that max_num has no effect when extra is less than max_num. - ->>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2) ->>> formset = LimitedFavoriteDrinkFormSet() ->>> for form in formset.forms: -... print form - - -# max_num with initial data - -# When not passed, max_num will take its default value of None, i.e. unlimited -# number of forms, only controlled by the values of the initial and extra -# parameters. - ->>> initial = [ -... {'name': 'Fernet and Coke'}, -... ] ->>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1) ->>> formset = LimitedFavoriteDrinkFormSet(initial=initial) ->>> for form in formset.forms: -... print form - - - -# If max_num is 0 then no form is rendered at all, even if extra and initial -# are specified. - ->>> initial = [ -... {'name': 'Fernet and Coke'}, -... {'name': 'Bloody Mary'}, -... ] ->>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=0) ->>> formset = LimitedFavoriteDrinkFormSet(initial=initial) ->>> for form in formset.forms: -... print form - -# More initial forms than max_num will result in only the first max_num of -# them to be displayed with no extra forms. - ->>> initial = [ -... {'name': 'Gin Tonic'}, -... {'name': 'Bloody Mary'}, -... {'name': 'Jack and Coke'}, -... ] ->>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, max_num=2) ->>> formset = LimitedFavoriteDrinkFormSet(initial=initial) ->>> for form in formset.forms: -... print form - - - -# One form from initial and extra=3 with max_num=2 should result in the one -# initial form and one extra. - ->>> initial = [ -... {'name': 'Gin Tonic'}, -... ] ->>> LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3, max_num=2) ->>> formset = LimitedFavoriteDrinkFormSet(initial=initial) ->>> for form in formset.forms: -... print form - - - - -# Regression test for #6926 ################################################## - -Make sure the management form has the correct prefix. - ->>> formset = FavoriteDrinksFormSet() ->>> formset.management_form.prefix -'form' - ->>> formset = FavoriteDrinksFormSet(data={}) ->>> formset.management_form.prefix -'form' - ->>> formset = FavoriteDrinksFormSet(initial={}) ->>> formset.management_form.prefix -'form' - -# Regression test for #12878 ################################################# - ->>> data = { -... 'drinks-TOTAL_FORMS': '2', # the number of forms rendered -... 'drinks-INITIAL_FORMS': '0', # the number of forms with initial data -... 'drinks-MAX_NUM_FORMS': '0', # max number of forms -... 'drinks-0-name': 'Gin and Tonic', -... 'drinks-1-name': 'Gin and Tonic', -... } - ->>> formset = FavoriteDrinksFormSet(data, prefix='drinks') ->>> formset.is_valid() -False ->>> print formset.non_form_errors() -
        • You may only specify a drink once.
        - -""" diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/localflavortests.py similarity index 80% rename from tests/regressiontests/forms/tests.py rename to tests/regressiontests/forms/localflavortests.py index 7a91cb701e2c..f97e67342a71 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/localflavortests.py @@ -1,7 +1,4 @@ # -*- coding: utf-8 -*- -from extra import tests as extra_tests -from forms import tests as form_tests -from error_messages import tests as custom_error_message_tests from localflavor.ar import tests as localflavor_ar_tests from localflavor.at import tests as localflavor_at_tests from localflavor.au import tests as localflavor_au_tests @@ -31,22 +28,9 @@ from localflavor.us import tests as localflavor_us_tests from localflavor.uy import tests as localflavor_uy_tests from localflavor.za import tests as localflavor_za_tests -from regressions import tests as regression_tests -from util import tests as util_tests -from widgets import tests as widgets_tests -from formsets import tests as formset_tests -from media import media_tests -from fields import FieldsTests -from validators import TestFieldWithValidators -from widgets import WidgetTests - -from input_formats import * __test__ = { - 'extra_tests': extra_tests, - 'form_tests': form_tests, - 'custom_error_message_tests': custom_error_message_tests, 'localflavor_ar_tests': localflavor_ar_tests, 'localflavor_at_tests': localflavor_at_tests, 'localflavor_au_tests': localflavor_au_tests, @@ -76,11 +60,6 @@ 'localflavor_us_tests': localflavor_us_tests, 'localflavor_uy_tests': localflavor_uy_tests, 'localflavor_za_tests': localflavor_za_tests, - 'regression_tests': regression_tests, - 'formset_tests': formset_tests, - 'media_tests': media_tests, - 'util_tests': util_tests, - 'widgets_tests': widgets_tests, } if __name__ == "__main__": diff --git a/tests/regressiontests/forms/media.py b/tests/regressiontests/forms/media.py deleted file mode 100644 index d715fb4d801f..000000000000 --- a/tests/regressiontests/forms/media.py +++ /dev/null @@ -1,385 +0,0 @@ -# -*- coding: utf-8 -*- -# Tests for the media handling on widgets and forms - -media_tests = r""" ->>> from django.forms import TextInput, Media, TextInput, CharField, Form, MultiWidget ->>> from django.conf import settings ->>> ORIGINAL_MEDIA_URL = settings.MEDIA_URL ->>> settings.MEDIA_URL = 'http://media.example.com/media/' - -# Check construction of media objects ->>> m = Media(css={'all': ('path/to/css1','/path/to/css2')}, js=('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3')) ->>> print m - - - - - - ->>> class Foo: -... css = { -... 'all': ('path/to/css1','/path/to/css2') -... } -... js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') ->>> m3 = Media(Foo) ->>> print m3 - - - - - - ->>> m3 = Media(Foo) ->>> print m3 - - - - - - -# A widget can exist without a media definition ->>> class MyWidget(TextInput): -... pass - ->>> w = MyWidget() ->>> print w.media - - -############################################################### -# DSL Class-based media definitions -############################################################### - -# A widget can define media if it needs to. -# Any absolute path will be preserved; relative paths are combined -# with the value of settings.MEDIA_URL ->>> class MyWidget1(TextInput): -... class Media: -... css = { -... 'all': ('path/to/css1','/path/to/css2') -... } -... js = ('/path/to/js1','http://media.other.com/path/to/js2','https://secure.other.com/path/to/js3') - ->>> w1 = MyWidget1() ->>> print w1.media - - - - - - -# Media objects can be interrogated by media type ->>> print w1.media['css'] - - - ->>> print w1.media['js'] - - - - -# Media objects can be combined. Any given media resource will appear only -# once. Duplicated media definitions are ignored. ->>> class MyWidget2(TextInput): -... class Media: -... css = { -... 'all': ('/path/to/css2','/path/to/css3') -... } -... js = ('/path/to/js1','/path/to/js4') - ->>> class MyWidget3(TextInput): -... class Media: -... css = { -... 'all': ('/path/to/css3','path/to/css1') -... } -... js = ('/path/to/js1','/path/to/js4') - ->>> w2 = MyWidget2() ->>> w3 = MyWidget3() ->>> print w1.media + w2.media + w3.media - - - - - - - - -# Check that media addition hasn't affected the original objects ->>> print w1.media - - - - - - -# Regression check for #12879: specifying the same CSS or JS file -# multiple times in a single Media instance should result in that file -# only being included once. ->>> class MyWidget4(TextInput): -... class Media: -... css = {'all': ('/path/to/css1', '/path/to/css1')} -... js = ('/path/to/js1', '/path/to/js1') - ->>> w4 = MyWidget4() ->>> print w4.media - - - - -############################################################### -# Property-based media definitions -############################################################### - -# Widget media can be defined as a property ->>> class MyWidget4(TextInput): -... def _media(self): -... return Media(css={'all': ('/some/path',)}, js = ('/some/js',)) -... media = property(_media) - ->>> w4 = MyWidget4() ->>> print w4.media - - - -# Media properties can reference the media of their parents ->>> class MyWidget5(MyWidget4): -... def _media(self): -... return super(MyWidget5, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',)) -... media = property(_media) - ->>> w5 = MyWidget5() ->>> print w5.media - - - - - -# Media properties can reference the media of their parents, -# even if the parent media was defined using a class ->>> class MyWidget6(MyWidget1): -... def _media(self): -... return super(MyWidget6, self).media + Media(css={'all': ('/other/path',)}, js = ('/other/js',)) -... media = property(_media) - ->>> w6 = MyWidget6() ->>> print w6.media - - - - - - - - -############################################################### -# Inheritance of media -############################################################### - -# If a widget extends another but provides no media definition, it inherits the parent widget's media ->>> class MyWidget7(MyWidget1): -... pass - ->>> w7 = MyWidget7() ->>> print w7.media - - - - - - -# If a widget extends another but defines media, it extends the parent widget's media by default ->>> class MyWidget8(MyWidget1): -... class Media: -... css = { -... 'all': ('/path/to/css3','path/to/css1') -... } -... js = ('/path/to/js1','/path/to/js4') - ->>> w8 = MyWidget8() ->>> print w8.media - - - - - - - - -# If a widget extends another but defines media, it extends the parents widget's media, -# even if the parent defined media using a property. ->>> class MyWidget9(MyWidget4): -... class Media: -... css = { -... 'all': ('/other/path',) -... } -... js = ('/other/js',) - ->>> w9 = MyWidget9() ->>> print w9.media - - - - - -# A widget can disable media inheritance by specifying 'extend=False' ->>> class MyWidget10(MyWidget1): -... class Media: -... extend = False -... css = { -... 'all': ('/path/to/css3','path/to/css1') -... } -... js = ('/path/to/js1','/path/to/js4') - ->>> w10 = MyWidget10() ->>> print w10.media - - - - - -# A widget can explicitly enable full media inheritance by specifying 'extend=True' ->>> class MyWidget11(MyWidget1): -... class Media: -... extend = True -... css = { -... 'all': ('/path/to/css3','path/to/css1') -... } -... js = ('/path/to/js1','/path/to/js4') - ->>> w11 = MyWidget11() ->>> print w11.media - - - - - - - - -# A widget can enable inheritance of one media type by specifying extend as a tuple ->>> class MyWidget12(MyWidget1): -... class Media: -... extend = ('css',) -... css = { -... 'all': ('/path/to/css3','path/to/css1') -... } -... js = ('/path/to/js1','/path/to/js4') - ->>> w12 = MyWidget12() ->>> print w12.media - - - - - - -############################################################### -# Multi-media handling for CSS -############################################################### - -# A widget can define CSS media for multiple output media types ->>> class MultimediaWidget(TextInput): -... class Media: -... css = { -... 'screen, print': ('/file1','/file2'), -... 'screen': ('/file3',), -... 'print': ('/file4',) -... } -... js = ('/path/to/js1','/path/to/js4') - ->>> multimedia = MultimediaWidget() ->>> print multimedia.media - - - - - - - -############################################################### -# Multiwidget media handling -############################################################### - -# MultiWidgets have a default media definition that gets all the -# media from the component widgets ->>> class MyMultiWidget(MultiWidget): -... def __init__(self, attrs=None): -... widgets = [MyWidget1, MyWidget2, MyWidget3] -... super(MyMultiWidget, self).__init__(widgets, attrs) - ->>> mymulti = MyMultiWidget() ->>> print mymulti.media - - - - - - - - -############################################################### -# Media processing for forms -############################################################### - -# You can ask a form for the media required by its widgets. ->>> class MyForm(Form): -... field1 = CharField(max_length=20, widget=MyWidget1()) -... field2 = CharField(max_length=20, widget=MyWidget2()) ->>> f1 = MyForm() ->>> print f1.media - - - - - - - - -# Form media can be combined to produce a single media definition. ->>> class AnotherForm(Form): -... field3 = CharField(max_length=20, widget=MyWidget3()) ->>> f2 = AnotherForm() ->>> print f1.media + f2.media - - - - - - - - -# Forms can also define media, following the same rules as widgets. ->>> class FormWithMedia(Form): -... field1 = CharField(max_length=20, widget=MyWidget1()) -... field2 = CharField(max_length=20, widget=MyWidget2()) -... class Media: -... js = ('/some/form/javascript',) -... css = { -... 'all': ('/some/form/css',) -... } ->>> f3 = FormWithMedia() ->>> print f3.media - - - - - - - - - - -# Media works in templates ->>> from django.template import Template, Context ->>> Template("{{ form.media.js }}{{ form.media.css }}").render(Context({'form': f3})) -u' - - - - - - -' - ->>> settings.MEDIA_URL = ORIGINAL_MEDIA_URL -""" diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py index 548f75d4ee6f..0a0fea45ad4e 100644 --- a/tests/regressiontests/forms/models.py +++ b/tests/regressiontests/forms/models.py @@ -1,15 +1,9 @@ # -*- coding: utf-8 -*- import datetime import tempfile -import shutil -from django.db import models, connection -from django.conf import settings -# Can't import as "forms" due to implementation details in the test suite (the -# current file is called "forms" and is already imported). -from django import forms as django_forms +from django.db import models from django.core.files.storage import FileSystemStorage -from django.test import TestCase temp_storage_location = tempfile.mkdtemp() temp_storage = FileSystemStorage(location=temp_storage_location) @@ -57,188 +51,11 @@ class ChoiceFieldModel(models.Model): multi_choice_int = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='multi_choice_int', default=lambda: [1]) -class ChoiceFieldForm(django_forms.ModelForm): - class Meta: - model = ChoiceFieldModel - class FileModel(models.Model): file = models.FileField(storage=temp_storage, upload_to='tests') -class FileForm(django_forms.Form): - file1 = django_forms.FileField() - class Group(models.Model): name = models.CharField(max_length=10) def __unicode__(self): return u'%s' % self.name - -class TestTicket12510(TestCase): - ''' It is not necessary to generate choices for ModelChoiceField (regression test for #12510). ''' - def setUp(self): - self.groups = [Group.objects.create(name=name) for name in 'abc'] - self.old_debug = settings.DEBUG - # turn debug on to get access to connection.queries - settings.DEBUG = True - - def tearDown(self): - settings.DEBUG = self.old_debug - - def test_choices_not_fetched_when_not_rendering(self): - initial_queries = len(connection.queries) - field = django_forms.ModelChoiceField(Group.objects.order_by('-name')) - self.assertEqual('a', field.clean(self.groups[0].pk).name) - # only one query is required to pull the model from DB - self.assertEqual(initial_queries+1, len(connection.queries)) - -class ModelFormCallableModelDefault(TestCase): - def test_no_empty_option(self): - "If a model's ForeignKey has blank=False and a default, no empty option is created (Refs #10792)." - option = ChoiceOptionModel.objects.create(name='default') - - choices = list(ChoiceFieldForm().fields['choice'].choices) - self.assertEquals(len(choices), 1) - self.assertEquals(choices[0], (option.pk, unicode(option))) - - def test_callable_initial_value(self): - "The initial value for a callable default returning a queryset is the pk (refs #13769)" - obj1 = ChoiceOptionModel.objects.create(id=1, name='default') - obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2') - obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3') - self.assertEquals(ChoiceFieldForm().as_p(), """

        -

        -

        Hold down "Control", or "Command" on a Mac, to select more than one.

        -

        Hold down "Control", or "Command" on a Mac, to select more than one.

        """) - - def test_initial_instance_value(self): - "Initial instances for model fields may also be instances (refs #7287)" - obj1 = ChoiceOptionModel.objects.create(id=1, name='default') - obj2 = ChoiceOptionModel.objects.create(id=2, name='option 2') - obj3 = ChoiceOptionModel.objects.create(id=3, name='option 3') - self.assertEquals(ChoiceFieldForm(initial={ - 'choice': obj2, - 'choice_int': obj2, - 'multi_choice': [obj2,obj3], - 'multi_choice_int': ChoiceOptionModel.objects.exclude(name="default"), - }).as_p(), """

        -

        -

        - Hold down "Control", or "Command" on a Mac, to select more than one.

        -

        - Hold down "Control", or "Command" on a Mac, to select more than one.

        """) - - -__test__ = {'API_TESTS': """ ->>> from django.forms.models import ModelForm ->>> from django.core.files.uploadedfile import SimpleUploadedFile - -# FileModel with unicode filename and data ######################### ->>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False) ->>> f.is_valid() -True ->>> f.cleaned_data -{'file1': } ->>> m = FileModel.objects.create(file=f.cleaned_data['file1']) - -# It's enough that m gets created without error. Preservation of the exotic name is checked -# in a file_uploads test; it's hard to do that correctly with doctest's unicode issues. So -# we create and then immediately delete m so as to not leave the exotically named file around -# for shutil.rmtree (on Windows) to have trouble with later. ->>> m.delete() - -# Boundary conditions on a PostitiveIntegerField ######################### ->>> class BoundaryForm(ModelForm): -... class Meta: -... model = BoundaryModel ->>> f = BoundaryForm({'positive_integer': 100}) ->>> f.is_valid() -True ->>> f = BoundaryForm({'positive_integer': 0}) ->>> f.is_valid() -True ->>> f = BoundaryForm({'positive_integer': -100}) ->>> f.is_valid() -False - -# Formfield initial values ######## -If the model has default values for some fields, they are used as the formfield -initial values. ->>> class DefaultsForm(ModelForm): -... class Meta: -... model = Defaults ->>> DefaultsForm().fields['name'].initial -u'class default value' ->>> DefaultsForm().fields['def_date'].initial -datetime.date(1980, 1, 1) ->>> DefaultsForm().fields['value'].initial -42 ->>> r1 = DefaultsForm()['callable_default'].as_widget() ->>> r2 = DefaultsForm()['callable_default'].as_widget() ->>> r1 == r2 -False - -In a ModelForm that is passed an instance, the initial values come from the -instance's values, not the model's defaults. ->>> foo_instance = Defaults(name=u'instance value', def_date=datetime.date(1969, 4, 4), value=12) ->>> instance_form = DefaultsForm(instance=foo_instance) ->>> instance_form.initial['name'] -u'instance value' ->>> instance_form.initial['def_date'] -datetime.date(1969, 4, 4) ->>> instance_form.initial['value'] -12 - ->>> from django.forms import CharField ->>> class ExcludingForm(ModelForm): -... name = CharField(max_length=255) -... class Meta: -... model = Defaults -... exclude = ['name', 'callable_default'] ->>> f = ExcludingForm({'name': u'Hello', 'value': 99, 'def_date': datetime.date(1999, 3, 2)}) ->>> f.is_valid() -True ->>> f.cleaned_data['name'] -u'Hello' ->>> obj = f.save() ->>> obj.name -u'class default value' ->>> obj.value -99 ->>> obj.def_date -datetime.date(1999, 3, 2) ->>> shutil.rmtree(temp_storage_location) - - -"""} diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py deleted file mode 100644 index 9471932057d2..000000000000 --- a/tests/regressiontests/forms/regressions.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- -# Tests to prevent against recurrences of earlier bugs. - -tests = r""" -It should be possible to re-use attribute dictionaries (#3810) ->>> from django.forms import * ->>> extra_attrs = {'class': 'special'} ->>> class TestForm(Form): -... f1 = CharField(max_length=10, widget=TextInput(attrs=extra_attrs)) -... f2 = CharField(widget=TextInput(attrs=extra_attrs)) ->>> TestForm(auto_id=False).as_p() -u'

        F1:

        \n

        F2:

        ' - -####################### -# Tests for form i18n # -####################### -There were some problems with form translations in #3600 - ->>> from django.utils.translation import ugettext_lazy, activate, deactivate ->>> class SomeForm(Form): -... username = CharField(max_length=10, label=ugettext_lazy('Username')) ->>> f = SomeForm() ->>> print f.as_p() -

        - -Translations are done at rendering time, so multi-lingual apps can define forms -early and still send back the right translation. - ->>> activate('de') ->>> print f.as_p() -

        ->>> activate('pl') ->>> f.as_p() -u'

        ' ->>> deactivate() - -There was some problems with form translations in #5216 ->>> class SomeForm(Form): -... field_1 = CharField(max_length=10, label=ugettext_lazy('field_1')) -... field_2 = CharField(max_length=10, label=ugettext_lazy('field_2'), widget=TextInput(attrs={'id': 'field_2_id'})) ->>> f = SomeForm() ->>> print f['field_1'].label_tag() - ->>> print f['field_2'].label_tag() - - -Unicode decoding problems... ->>> GENDERS = ((u'\xc5', u'En tied\xe4'), (u'\xf8', u'Mies'), (u'\xdf', u'Nainen')) ->>> class SomeForm(Form): -... somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect(), label=u'\xc5\xf8\xdf') ->>> f = SomeForm() ->>> f.as_p() -u'

          \n
        • \n
        • \n
        • \n

        ' - -Testing choice validation with UTF-8 bytestrings as input (these are the -Russian abbreviations "мес." and "шт.". - ->>> UNITS = (('\xd0\xbc\xd0\xb5\xd1\x81.', '\xd0\xbc\xd0\xb5\xd1\x81.'), ('\xd1\x88\xd1\x82.', '\xd1\x88\xd1\x82.')) ->>> f = ChoiceField(choices=UNITS) ->>> f.clean(u'\u0448\u0442.') -u'\u0448\u0442.' ->>> f.clean('\xd1\x88\xd1\x82.') -u'\u0448\u0442.' - -Translated error messages used to be buggy. ->>> activate('ru') ->>> f = SomeForm({}) ->>> f.as_p() -u'
        • \u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435.
        \n

          \n
        • \n
        • \n
        • \n

        ' ->>> deactivate() - -Deep copying translated text shouldn't raise an error ->>> from django.utils.translation import gettext_lazy ->>> class CopyForm(Form): -... degree = IntegerField(widget=Select(choices=((1, gettext_lazy('test')),))) ->>> f = CopyForm() - -####################### -# Miscellaneous Tests # -####################### - -There once was a problem with Form fields called "data". Let's make sure that -doesn't come back. ->>> class DataForm(Form): -... data = CharField(max_length=10) ->>> f = DataForm({'data': 'xyzzy'}) ->>> f.is_valid() -True ->>> f.cleaned_data -{'data': u'xyzzy'} - -A form with *only* hidden fields that has errors is going to be very unusual. -But we can try to make sure it doesn't generate invalid XHTML. In this case, -the as_p() method is the tricky one, since error lists cannot be nested -(validly) inside p elements. - ->>> class HiddenForm(Form): -... data = IntegerField(widget=HiddenInput) ->>> f = HiddenForm({}) ->>> f.as_p() -u'
        • (Hidden field data) This field is required.
        \n

        ' ->>> f.as_table() -u'
        ' - -################################################### -# Tests for XSS vulnerabilities in error messages # -################################################### - -# The forms layer doesn't escape input values directly because error messages -# might be presented in non-HTML contexts. Instead, the message is just marked -# for escaping by the template engine. So we'll need to construct a little -# silly template to trigger the escaping. - ->>> from django.template import Template, Context ->>> t = Template('{{ form.errors }}') - ->>> class SomeForm(Form): -... field = ChoiceField(choices=[('one', 'One')]) ->>> f = SomeForm({'field': '", + 'special_safe_name': "Do not escape" + }, auto_id=False) + self.assertEqual(f.as_table(), """ +""") + + def test_validating_multiple_fields(self): + # There are a couple of ways to do multiple-field validation. If you want the + # validation message to be associated with a particular field, implement the + # clean_XXX() method on the Form, where XXX is the field name. As in + # Field.clean(), the clean_XXX() method should return the cleaned value. In the + # clean_XXX() method, you have access to self.cleaned_data, which is a dictionary + # of all the data that has been cleaned *so far*, in order by the fields, + # including the current field (e.g., the field XXX if you're in clean_XXX()). + class UserRegistration(Form): + username = CharField(max_length=10) + password1 = CharField(widget=PasswordInput) + password2 = CharField(widget=PasswordInput) + + def clean_password2(self): + if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: + raise ValidationError(u'Please make sure your passwords match.') + + return self.cleaned_data['password2'] + + f = UserRegistration(auto_id=False) + self.assertEqual(f.errors, {}) + f = UserRegistration({}, auto_id=False) + self.assertEqual(f.errors['username'], [u'This field is required.']) + self.assertEqual(f.errors['password1'], [u'This field is required.']) + self.assertEqual(f.errors['password2'], [u'This field is required.']) + f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False) + self.assertEqual(f.errors['password2'], [u'Please make sure your passwords match.']) + f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) + self.assertEqual(f.errors, {}) + self.assertEqual(f.cleaned_data['username'], u'adrian') + self.assertEqual(f.cleaned_data['password1'], u'foo') + self.assertEqual(f.cleaned_data['password2'], u'foo') + + # Another way of doing multiple-field validation is by implementing the + # Form's clean() method. If you do this, any ValidationError raised by that + # method will not be associated with a particular field; it will have a + # special-case association with the field named '__all__'. + # Note that in Form.clean(), you have access to self.cleaned_data, a dictionary of + # all the fields/values that have *not* raised a ValidationError. Also note + # Form.clean() is required to return a dictionary of all clean data. + class UserRegistration(Form): + username = CharField(max_length=10) + password1 = CharField(widget=PasswordInput) + password2 = CharField(widget=PasswordInput) + + def clean(self): + if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: + raise ValidationError(u'Please make sure your passwords match.') + + return self.cleaned_data + + f = UserRegistration(auto_id=False) + self.assertEqual(f.errors, {}) + f = UserRegistration({}, auto_id=False) + self.assertEqual(f.as_table(), """ + +""") + self.assertEqual(f.errors['username'], [u'This field is required.']) + self.assertEqual(f.errors['password1'], [u'This field is required.']) + self.assertEqual(f.errors['password2'], [u'This field is required.']) + f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False) + self.assertEqual(f.errors['__all__'], [u'Please make sure your passwords match.']) + self.assertEqual(f.as_table(), """ + + +""") + self.assertEqual(f.as_ul(), """
        • Please make sure your passwords match.
      • +
      • Username:
      • +
      • Password1:
      • +
      • Password2:
      • """) + f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) + self.assertEqual(f.errors, {}) + self.assertEqual(f.cleaned_data['username'], u'adrian') + self.assertEqual(f.cleaned_data['password1'], u'foo') + self.assertEqual(f.cleaned_data['password2'], u'foo') + + def test_dynamic_construction(self): + # It's possible to construct a Form dynamically by adding to the self.fields + # dictionary in __init__(). Don't forget to call Form.__init__() within the + # subclass' __init__(). + class Person(Form): + first_name = CharField() + last_name = CharField() + + def __init__(self, *args, **kwargs): + super(Person, self).__init__(*args, **kwargs) + self.fields['birthday'] = DateField() + + p = Person(auto_id=False) + self.assertEqual(p.as_table(), """ + +""") + + # Instances of a dynamic Form do not persist fields from one Form instance to + # the next. + class MyForm(Form): + def __init__(self, data=None, auto_id=False, field_list=[]): + Form.__init__(self, data, auto_id=auto_id) + + for field in field_list: + self.fields[field[0]] = field[1] + + field_list = [('field1', CharField()), ('field2', CharField())] + my_form = MyForm(field_list=field_list) + self.assertEqual(my_form.as_table(), """ +""") + field_list = [('field3', CharField()), ('field4', CharField())] + my_form = MyForm(field_list=field_list) + self.assertEqual(my_form.as_table(), """ +""") + + class MyForm(Form): + default_field_1 = CharField() + default_field_2 = CharField() + + def __init__(self, data=None, auto_id=False, field_list=[]): + Form.__init__(self, data, auto_id=auto_id) + + for field in field_list: + self.fields[field[0]] = field[1] + + field_list = [('field1', CharField()), ('field2', CharField())] + my_form = MyForm(field_list=field_list) + self.assertEqual(my_form.as_table(), """ + + +""") + field_list = [('field3', CharField()), ('field4', CharField())] + my_form = MyForm(field_list=field_list) + self.assertEqual(my_form.as_table(), """ + + +""") + + # Similarly, changes to field attributes do not persist from one Form instance + # to the next. + class Person(Form): + first_name = CharField(required=False) + last_name = CharField(required=False) + + def __init__(self, names_required=False, *args, **kwargs): + super(Person, self).__init__(*args, **kwargs) + + if names_required: + self.fields['first_name'].required = True + self.fields['first_name'].widget.attrs['class'] = 'required' + self.fields['last_name'].required = True + self.fields['last_name'].widget.attrs['class'] = 'required' + + f = Person(names_required=False) + self.assertEqual(f['first_name'].field.required, f['last_name'].field.required, (False, False)) + self.assertEqual(f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs, ({}, {})) + f = Person(names_required=True) + self.assertEqual(f['first_name'].field.required, f['last_name'].field.required, (True, True)) + self.assertEqual(f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs, ({'class': 'required'}, {'class': 'required'})) + f = Person(names_required=False) + self.assertEqual(f['first_name'].field.required, f['last_name'].field.required, (False, False)) + self.assertEqual(f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs, ({}, {})) + + class Person(Form): + first_name = CharField(max_length=30) + last_name = CharField(max_length=30) + + def __init__(self, name_max_length=None, *args, **kwargs): + super(Person, self).__init__(*args, **kwargs) + + if name_max_length: + self.fields['first_name'].max_length = name_max_length + self.fields['last_name'].max_length = name_max_length + + f = Person(name_max_length=None) + self.assertEqual(f['first_name'].field.max_length, f['last_name'].field.max_length, (30, 30)) + f = Person(name_max_length=20) + self.assertEqual(f['first_name'].field.max_length, f['last_name'].field.max_length, (20, 20)) + f = Person(name_max_length=None) + self.assertEqual(f['first_name'].field.max_length, f['last_name'].field.max_length, (30, 30)) + + def test_hidden_widget(self): + # HiddenInput widgets are displayed differently in the as_table(), as_ul()) + # and as_p() output of a Form -- their verbose names are not displayed, and a + # separate row is not displayed. They're displayed in the last row of the + # form, directly after that row's form element. + class Person(Form): + first_name = CharField() + last_name = CharField() + hidden_text = CharField(widget=HiddenInput) + birthday = DateField() + + p = Person(auto_id=False) + self.assertEqual(p.as_table(), """ + +""") + self.assertEqual(p.as_ul(), """
      • First name:
      • +
      • Last name:
      • +
      • Birthday:
      • """) + self.assertEqual(p.as_p(), """

        First name:

        +

        Last name:

        +

        Birthday:

        """) + + # With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label. + p = Person(auto_id='id_%s') + self.assertEqual(p.as_table(), """ + +""") + self.assertEqual(p.as_ul(), """
      • +
      • +
      • """) + self.assertEqual(p.as_p(), """

        +

        +

        """) + + # If a field with a HiddenInput has errors, the as_table() and as_ul() output + # will include the error message(s) with the text "(Hidden field [fieldname]) " + # prepended. This message is displayed at the top of the output, regardless of + # its field's order in the form. + p = Person({'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9'}, auto_id=False) + self.assertEqual(p.as_table(), """ + + +""") + self.assertEqual(p.as_ul(), """
        • (Hidden field hidden_text) This field is required.
      • +
      • First name:
      • +
      • Last name:
      • +
      • Birthday:
      • """) + self.assertEqual(p.as_p(), """
        • (Hidden field hidden_text) This field is required.
        +

        First name:

        +

        Last name:

        +

        Birthday:

        """) + + # A corner case: It's possible for a form to have only HiddenInputs. + class TestForm(Form): + foo = CharField(widget=HiddenInput) + bar = CharField(widget=HiddenInput) + + p = TestForm(auto_id=False) + self.assertEqual(p.as_table(), '') + self.assertEqual(p.as_ul(), '') + self.assertEqual(p.as_p(), '') + + def test_field_order(self): + # A Form's fields are displayed in the same order in which they were defined. + class TestForm(Form): + field1 = CharField() + field2 = CharField() + field3 = CharField() + field4 = CharField() + field5 = CharField() + field6 = CharField() + field7 = CharField() + field8 = CharField() + field9 = CharField() + field10 = CharField() + field11 = CharField() + field12 = CharField() + field13 = CharField() + field14 = CharField() + + p = TestForm(auto_id=False) + self.assertEqual(p.as_table(), """ + + + + + + + + + + + + +""") + + def test_form_html_attributes(self): + # Some Field classes have an effect on the HTML attributes of their associated + # Widget. If you set max_length in a CharField and its associated widget is + # either a TextInput or PasswordInput, then the widget's rendered HTML will + # include the "maxlength" attribute. + class UserRegistration(Form): + username = CharField(max_length=10) # uses TextInput by default + password = CharField(max_length=10, widget=PasswordInput) + realname = CharField(max_length=10, widget=TextInput) # redundantly define widget, just to test + address = CharField() # no max_length defined here + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • +
      • Realname:
      • +
      • Address:
      • """) + + # If you specify a custom "attrs" that includes the "maxlength" attribute, + # the Field's max_length attribute will override whatever "maxlength" you specify + # in "attrs". + class UserRegistration(Form): + username = CharField(max_length=10, widget=TextInput(attrs={'maxlength': 20})) + password = CharField(max_length=10, widget=PasswordInput) + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • """) + + def test_specifying_labels(self): + # You can specify the label for a field by using the 'label' argument to a Field + # class. If you don't specify 'label', Django will use the field name with + # underscores converted to spaces, and the initial letter capitalized. + class UserRegistration(Form): + username = CharField(max_length=10, label='Your username') + password1 = CharField(widget=PasswordInput) + password2 = CharField(widget=PasswordInput, label='Password (again)') + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), """
      • Your username:
      • +
      • Password1:
      • +
      • Password (again):
      • """) + + # Labels for as_* methods will only end in a colon if they don't end in other + # punctuation already. + class Questions(Form): + q1 = CharField(label='The first question') + q2 = CharField(label='What is your name?') + q3 = CharField(label='The answer to life is:') + q4 = CharField(label='Answer this question!') + q5 = CharField(label='The last question. Period.') + + self.assertEqual(Questions(auto_id=False).as_p(), """

        The first question:

        +

        What is your name?

        +

        The answer to life is:

        +

        Answer this question!

        +

        The last question. Period.

        """) + self.assertEqual(Questions().as_p(), """

        +

        +

        +

        +

        """) + + # A label can be a Unicode object or a bytestring with special characters. + class UserRegistration(Form): + username = CharField(max_length=10, label='ŠĐĆŽćžšđ') + password = CharField(widget=PasswordInput, label=u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), u'
      • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
      • \n
      • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
      • ') + + # If a label is set to the empty string for a field, that field won't get a label. + class UserRegistration(Form): + username = CharField(max_length=10, label='') + password = CharField(widget=PasswordInput) + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), """
      • +
      • Password:
      • """) + p = UserRegistration(auto_id='id_%s') + self.assertEqual(p.as_ul(), """
      • +
      • """) + + # If label is None, Django will auto-create the label from the field name. This + # is default behavior. + class UserRegistration(Form): + username = CharField(max_length=10, label=None) + password = CharField(widget=PasswordInput) + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • """) + p = UserRegistration(auto_id='id_%s') + self.assertEqual(p.as_ul(), """
      • +
      • """) + + def test_label_suffix(self): + # You can specify the 'label_suffix' argument to a Form class to modify the + # punctuation symbol used at the end of a label. By default, the colon (:) is + # used, and is only appended to the label if the label doesn't already end with a + # punctuation symbol: ., !, ? or :. If you specify a different suffix, it will + # be appended regardless of the last character of the label. + class FavoriteForm(Form): + color = CharField(label='Favorite color?') + animal = CharField(label='Favorite animal') + + f = FavoriteForm(auto_id=False) + self.assertEqual(f.as_ul(), """
      • Favorite color?
      • +
      • Favorite animal:
      • """) + f = FavoriteForm(auto_id=False, label_suffix='?') + self.assertEqual(f.as_ul(), """
      • Favorite color?
      • +
      • Favorite animal?
      • """) + f = FavoriteForm(auto_id=False, label_suffix='') + self.assertEqual(f.as_ul(), """
      • Favorite color?
      • +
      • Favorite animal
      • """) + f = FavoriteForm(auto_id=False, label_suffix=u'\u2192') + self.assertEqual(f.as_ul(), u'
      • Favorite color?
      • \n
      • Favorite animal\u2192
      • ') + + def test_initial_data(self): + # You can specify initial data for a field by using the 'initial' argument to a + # Field class. This initial data is displayed when a Form is rendered with *no* + # data. It is not displayed when a Form is rendered with any data (including an + # empty dictionary). Also, the initial value is *not* used if data for a + # particular required field isn't provided. + class UserRegistration(Form): + username = CharField(max_length=10, initial='django') + password = CharField(widget=PasswordInput) + + # Here, we're not submitting any data, so the initial value will be displayed.) + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • """) + + # Here, we're submitting data, so the initial value will *not* be displayed. + p = UserRegistration({}, auto_id=False) + self.assertEqual(p.as_ul(), """
        • This field is required.
        Username:
      • +
        • This field is required.
        Password:
      • """) + p = UserRegistration({'username': u''}, auto_id=False) + self.assertEqual(p.as_ul(), """
        • This field is required.
        Username:
      • +
        • This field is required.
        Password:
      • """) + p = UserRegistration({'username': u'foo'}, auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
        • This field is required.
        Password:
      • """) + + # An 'initial' value is *not* used as a fallback if data is not provided. In this + # example, we don't provide a value for 'username', and the form raises a + # validation error rather than using the initial value for 'username'. + p = UserRegistration({'password': 'secret'}) + self.assertEqual(p.errors['username'], [u'This field is required.']) + self.assertFalse(p.is_valid()) + + def test_dynamic_initial_data(self): + # The previous technique dealt with "hard-coded" initial data, but it's also + # possible to specify initial data after you've already created the Form class + # (i.e., at runtime). Use the 'initial' parameter to the Form constructor. This + # should be a dictionary containing initial values for one or more fields in the + # form, keyed by field name. + class UserRegistration(Form): + username = CharField(max_length=10) + password = CharField(widget=PasswordInput) + + # Here, we're not submitting any data, so the initial value will be displayed.) + p = UserRegistration(initial={'username': 'django'}, auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • """) + p = UserRegistration(initial={'username': 'stephane'}, auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • """) + + # The 'initial' parameter is meaningless if you pass data. + p = UserRegistration({}, initial={'username': 'django'}, auto_id=False) + self.assertEqual(p.as_ul(), """
        • This field is required.
        Username:
      • +
        • This field is required.
        Password:
      • """) + p = UserRegistration({'username': u''}, initial={'username': 'django'}, auto_id=False) + self.assertEqual(p.as_ul(), """
        • This field is required.
        Username:
      • +
        • This field is required.
        Password:
      • """) + p = UserRegistration({'username': u'foo'}, initial={'username': 'django'}, auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
        • This field is required.
        Password:
      • """) + + # A dynamic 'initial' value is *not* used as a fallback if data is not provided. + # In this example, we don't provide a value for 'username', and the form raises a + # validation error rather than using the initial value for 'username'. + p = UserRegistration({'password': 'secret'}, initial={'username': 'django'}) + self.assertEqual(p.errors['username'], [u'This field is required.']) + self.assertFalse(p.is_valid()) + + # If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(), + # then the latter will get precedence. + class UserRegistration(Form): + username = CharField(max_length=10, initial='django') + password = CharField(widget=PasswordInput) + + p = UserRegistration(initial={'username': 'babik'}, auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • """) + + def test_callable_initial_data(self): + # The previous technique dealt with raw values as initial data, but it's also + # possible to specify callable data. + class UserRegistration(Form): + username = CharField(max_length=10) + password = CharField(widget=PasswordInput) + options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')]) + + # We need to define functions that get called later.) + def initial_django(): + return 'django' + + def initial_stephane(): + return 'stephane' + + def initial_options(): + return ['f','b'] + + def initial_other_options(): + return ['b','w'] + + # Here, we're not submitting any data, so the initial value will be displayed.) + p = UserRegistration(initial={'username': initial_django, 'options': initial_options}, auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • +
      • Options:
      • """) + + # The 'initial' parameter is meaningless if you pass data. + p = UserRegistration({}, initial={'username': initial_django, 'options': initial_options}, auto_id=False) + self.assertEqual(p.as_ul(), """
        • This field is required.
        Username:
      • +
        • This field is required.
        Password:
      • +
        • This field is required.
        Options:
      • """) + p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False) + self.assertEqual(p.as_ul(), """
        • This field is required.
        Username:
      • +
        • This field is required.
        Password:
      • +
        • This field is required.
        Options:
      • """) + p = UserRegistration({'username': u'foo', 'options':['f','b']}, initial={'username': initial_django}, auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
        • This field is required.
        Password:
      • +
      • Options:
      • """) + + # A callable 'initial' value is *not* used as a fallback if data is not provided. + # In this example, we don't provide a value for 'username', and the form raises a + # validation error rather than using the initial value for 'username'. + p = UserRegistration({'password': 'secret'}, initial={'username': initial_django, 'options': initial_options}) + self.assertEqual(p.errors['username'], [u'This field is required.']) + self.assertFalse(p.is_valid()) + + # If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(), + # then the latter will get precedence. + class UserRegistration(Form): + username = CharField(max_length=10, initial=initial_django) + password = CharField(widget=PasswordInput) + options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')], initial=initial_other_options) + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • +
      • Options:
      • """) + p = UserRegistration(initial={'username': initial_stephane, 'options': initial_options}, auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username:
      • +
      • Password:
      • +
      • Options:
      • """) + + def test_help_text(self): + # You can specify descriptive text for a field by using the 'help_text' argument) + class UserRegistration(Form): + username = CharField(max_length=10, help_text='e.g., user@example.com') + password = CharField(widget=PasswordInput, help_text='Choose wisely.') + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username: e.g., user@example.com
      • +
      • Password: Choose wisely.
      • """) + self.assertEqual(p.as_p(), """

        Username: e.g., user@example.com

        +

        Password: Choose wisely.

        """) + self.assertEqual(p.as_table(), """ +""") + + # The help text is displayed whether or not data is provided for the form. + p = UserRegistration({'username': u'foo'}, auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username: e.g., user@example.com
      • +
        • This field is required.
        Password: Choose wisely.
      • """) + + # help_text is not displayed for hidden fields. It can be used for documentation + # purposes, though. + class UserRegistration(Form): + username = CharField(max_length=10, help_text='e.g., user@example.com') + password = CharField(widget=PasswordInput) + next = CharField(widget=HiddenInput, initial='/', help_text='Redirect destination') + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), """
      • Username: e.g., user@example.com
      • +
      • Password:
      • """) + + # Help text can include arbitrary Unicode characters. + class UserRegistration(Form): + username = CharField(max_length=10, help_text='ŠĐĆŽćžšđ') + + p = UserRegistration(auto_id=False) + self.assertEqual(p.as_ul(), u'
      • Username: \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111
      • ') + + def test_subclassing_forms(self): + # You can subclass a Form to add fields. The resulting form subclass will have + # all of the fields of the parent Form, plus whichever fields you define in the + # subclass. + class Person(Form): + first_name = CharField() + last_name = CharField() + birthday = DateField() + + class Musician(Person): + instrument = CharField() + + p = Person(auto_id=False) + self.assertEqual(p.as_ul(), """
      • First name:
      • +
      • Last name:
      • +
      • Birthday:
      • """) + m = Musician(auto_id=False) + self.assertEqual(m.as_ul(), """
      • First name:
      • +
      • Last name:
      • +
      • Birthday:
      • +
      • Instrument:
      • """) + + # Yes, you can subclass multiple forms. The fields are added in the order in + # which the parent classes are listed. + class Person(Form): + first_name = CharField() + last_name = CharField() + birthday = DateField() + + class Instrument(Form): + instrument = CharField() + + class Beatle(Person, Instrument): + haircut_type = CharField() + + b = Beatle(auto_id=False) + self.assertEqual(b.as_ul(), """
      • First name:
      • +
      • Last name:
      • +
      • Birthday:
      • +
      • Instrument:
      • +
      • Haircut type:
      • """) + + def test_forms_with_prefixes(self): + # Sometimes it's necessary to have multiple forms display on the same HTML page, + # or multiple copies of the same form. We can accomplish this with form prefixes. + # Pass the keyword argument 'prefix' to the Form constructor to use this feature. + # This value will be prepended to each HTML form field name. One way to think + # about this is "namespaces for HTML forms". Notice that in the data argument, + # each field's key has the prefix, in this case 'person1', prepended to the + # actual field name. + class Person(Form): + first_name = CharField() + last_name = CharField() + birthday = DateField() + + data = { + 'person1-first_name': u'John', + 'person1-last_name': u'Lennon', + 'person1-birthday': u'1940-10-9' + } + p = Person(data, prefix='person1') + self.assertEqual(p.as_ul(), """
      • +
      • +
      • """) + self.assertEqual(str(p['first_name']), '') + self.assertEqual(str(p['last_name']), '') + self.assertEqual(str(p['birthday']), '') + self.assertEqual(p.errors, {}) + self.assertTrue(p.is_valid()) + self.assertEqual(p.cleaned_data['first_name'], u'John') + self.assertEqual(p.cleaned_data['last_name'], u'Lennon') + self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9)) + + # Let's try submitting some bad data to make sure form.errors and field.errors + # work as expected. + data = { + 'person1-first_name': u'', + 'person1-last_name': u'', + 'person1-birthday': u'' + } + p = Person(data, prefix='person1') + self.assertEqual(p.errors['first_name'], [u'This field is required.']) + self.assertEqual(p.errors['last_name'], [u'This field is required.']) + self.assertEqual(p.errors['birthday'], [u'This field is required.']) + self.assertEqual(p['first_name'].errors, [u'This field is required.']) + try: + p['person1-first_name'].errors + self.fail('Attempts to access non-existent fields should fail.') + except KeyError: + pass + + # In this example, the data doesn't have a prefix, but the form requires it, so + # the form doesn't "see" the fields. + data = { + 'first_name': u'John', + 'last_name': u'Lennon', + 'birthday': u'1940-10-9' + } + p = Person(data, prefix='person1') + self.assertEqual(p.errors['first_name'], [u'This field is required.']) + self.assertEqual(p.errors['last_name'], [u'This field is required.']) + self.assertEqual(p.errors['birthday'], [u'This field is required.']) + + # With prefixes, a single data dictionary can hold data for multiple instances + # of the same form. + data = { + 'person1-first_name': u'John', + 'person1-last_name': u'Lennon', + 'person1-birthday': u'1940-10-9', + 'person2-first_name': u'Jim', + 'person2-last_name': u'Morrison', + 'person2-birthday': u'1943-12-8' + } + p1 = Person(data, prefix='person1') + self.assertTrue(p1.is_valid()) + self.assertEqual(p1.cleaned_data['first_name'], u'John') + self.assertEqual(p1.cleaned_data['last_name'], u'Lennon') + self.assertEqual(p1.cleaned_data['birthday'], datetime.date(1940, 10, 9)) + p2 = Person(data, prefix='person2') + self.assertTrue(p2.is_valid()) + self.assertEqual(p2.cleaned_data['first_name'], u'Jim') + self.assertEqual(p2.cleaned_data['last_name'], u'Morrison') + self.assertEqual(p2.cleaned_data['birthday'], datetime.date(1943, 12, 8)) + + # By default, forms append a hyphen between the prefix and the field name, but a + # form can alter that behavior by implementing the add_prefix() method. This + # method takes a field name and returns the prefixed field, according to + # self.prefix. + class Person(Form): + first_name = CharField() + last_name = CharField() + birthday = DateField() + + def add_prefix(self, field_name): + return self.prefix and '%s-prefix-%s' % (self.prefix, field_name) or field_name + + p = Person(prefix='foo') + self.assertEqual(p.as_ul(), """
      • +
      • +
      • """) + data = { + 'foo-prefix-first_name': u'John', + 'foo-prefix-last_name': u'Lennon', + 'foo-prefix-birthday': u'1940-10-9' + } + p = Person(data, prefix='foo') + self.assertTrue(p.is_valid()) + self.assertEqual(p.cleaned_data['first_name'], u'John') + self.assertEqual(p.cleaned_data['last_name'], u'Lennon') + self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9)) + + def test_forms_with_null_boolean(self): + # NullBooleanField is a bit of a special case because its presentation (widget) + # is different than its data. This is handled transparently, though. + class Person(Form): + name = CharField() + is_cool = NullBooleanField() + + p = Person({'name': u'Joe'}, auto_id=False) + self.assertEqual(str(p['is_cool']), """""") + p = Person({'name': u'Joe', 'is_cool': u'1'}, auto_id=False) + self.assertEqual(str(p['is_cool']), """""") + p = Person({'name': u'Joe', 'is_cool': u'2'}, auto_id=False) + self.assertEqual(str(p['is_cool']), """""") + p = Person({'name': u'Joe', 'is_cool': u'3'}, auto_id=False) + self.assertEqual(str(p['is_cool']), """""") + p = Person({'name': u'Joe', 'is_cool': True}, auto_id=False) + self.assertEqual(str(p['is_cool']), """""") + p = Person({'name': u'Joe', 'is_cool': False}, auto_id=False) + self.assertEqual(str(p['is_cool']), """""") + + def test_forms_with_file_fields(self): + # FileFields are a special case because they take their data from the request.FILES, + # not request.POST. + class FileForm(Form): + file1 = FileField() + + f = FileForm(auto_id=False) + self.assertEqual(f.as_table(), '') + + f = FileForm(data={}, files={}, auto_id=False) + self.assertEqual(f.as_table(), '') + + f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False) + self.assertEqual(f.as_table(), '') + + f = FileForm(data={}, files={'file1': 'something that is not a file'}, auto_id=False) + self.assertEqual(f.as_table(), '') + + f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False) + self.assertEqual(f.as_table(), '') + self.assertTrue(f.is_valid()) + + f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False) + self.assertEqual(f.as_table(), '') + + def test_basic_processing_in_view(self): + class UserRegistration(Form): + username = CharField(max_length=10) + password1 = CharField(widget=PasswordInput) + password2 = CharField(widget=PasswordInput) + + def clean(self): + if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: + raise ValidationError(u'Please make sure your passwords match.') + + return self.cleaned_data + + def my_function(method, post_data): + if method == 'POST': + form = UserRegistration(post_data, auto_id=False) + else: + form = UserRegistration(auto_id=False) + + if form.is_valid(): + return 'VALID: %r' % form.cleaned_data + + t = Template('\n
        {{ form.username.label_tag }}` elements in `ChangeList` that cause improper rendering when `list_editable` is enabled; refactored `admin_changelist` tests. Thanks, skevy for bug report and patch. Backport of r13744 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13745 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../contrib/admin/media/css/changelists.css | 2 + .../templates/admin/change_list_results.html | 5 ++ .../contrib/admin/templatetags/admin_list.py | 9 +++- .../regressiontests/admin_changelist/tests.py | 46 ++++++++++++------- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/django/contrib/admin/media/css/changelists.css b/django/contrib/admin/media/css/changelists.css index 99ff8bc0cf42..3aa969a0493b 100644 --- a/django/contrib/admin/media/css/changelists.css +++ b/django/contrib/admin/media/css/changelists.css @@ -9,6 +9,8 @@ width: 100%; } +.change-list .hiddenfields { display:none; } + .change-list .filtered table { border-right: 1px solid #ddd; } diff --git a/django/contrib/admin/templates/admin/change_list_results.html b/django/contrib/admin/templates/admin/change_list_results.html index 0efcc9b24a03..dea027ce5ac0 100644 --- a/django/contrib/admin/templates/admin/change_list_results.html +++ b/django/contrib/admin/templates/admin/change_list_results.html @@ -1,3 +1,8 @@ +{% if result_hidden_fields %} +
        {# DIV for HTML validation #} +{% for item in result_hidden_fields %}{{ item }}{% endfor %} +
        +{% endif %} {% if results %} diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 565db32251ce..c05af0b7fe98 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -189,7 +189,7 @@ def items_for_result(cl, result, form): else: result_repr = conditional_escape(result_repr) yield mark_safe(u'%s' % (row_class, result_repr)) - if form: + if form and not form[cl.model._meta.pk.name].is_hidden: yield mark_safe(u'' % force_unicode(form[cl.model._meta.pk.name])) def results(cl): @@ -200,11 +200,18 @@ def results(cl): for res in cl.result_list: yield list(items_for_result(cl, res, None)) +def result_hidden_fields(cl): + if cl.formset: + for res, form in zip(cl.result_list, cl.formset.forms): + if form[cl.model._meta.pk.name].is_hidden: + yield mark_safe(force_unicode(form[cl.model._meta.pk.name])) + def result_list(cl): """ Displays the headers and data list together """ return {'cl': cl, + 'result_hidden_fields': list(result_hidden_fields(cl)), 'result_headers': list(result_headers(cl)), 'results': list(results(cl))} result_list = register.inclusion_tag("admin/change_list_results.html")(result_list) diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py index b70d7c51f411..c8ad1ce8f64e 100644 --- a/tests/regressiontests/admin_changelist/tests.py +++ b/tests/regressiontests/admin_changelist/tests.py @@ -1,10 +1,10 @@ -import unittest from django.contrib import admin from django.contrib.admin.views.main import ChangeList from django.template import Context, Template +from django.test import TransactionTestCase from regressiontests.admin_changelist.models import Child, Parent -class ChangeListTests(unittest.TestCase): +class ChangeListTests(TransactionTestCase): def test_select_related_preserved(self): """ Regression test for #10348: ChangeList.get_query_set() shouldn't @@ -18,9 +18,8 @@ def test_select_related_preserved(self): def test_result_list_html(self): """ - Regression test for #11791: Inclusion tag result_list generates a - table and this checks that the items are nested within the table - element tags. + Verifies that inclusion tag result_list generates a table when with + default ModelAdmin settings. """ new_parent = Parent.objects.create(name='parent') new_child = Child.objects.create(name='name', parent=new_parent) @@ -29,16 +28,27 @@ def test_result_list_html(self): cl = ChangeList(request, Child, m.list_display, m.list_display_links, m.list_filter, m.date_hierarchy, m.search_fields, m.list_select_related, m.list_per_page, m.list_editable, m) - FormSet = m.get_changelist_formset(request) - cl.formset = FormSet(queryset=cl.result_list) + cl.formset = None template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') context = Context({'cl': cl}) table_output = template.render(context) - hidden_input_elem = '' - self.failIf(table_output.find(hidden_input_elem) == -1, - 'Failed to find expected hidden input element in: %s' % table_output) - self.failIf(table_output.find('' % hidden_input_elem) == -1, - 'Hidden input element is not enclosed in ' + self.failIf(table_output.find(row_html) == -1, + 'Failed to find expected row element: %s' % table_output) + + def test_result_list_editable_html(self): + """ + Regression tests for #11791: Inclusion tag result_list generates a + table and this checks that the items are nested within the table + element tags. + Also a regression test for #13599, verifies that hidden fields + when list_editable is enabled are rendered in a div outside the + table. + """ + new_parent = Parent.objects.create(name='parent') + new_child = Child.objects.create(name='name', parent=new_parent) + request = MockRequest() + m = ChildAdmin(Child, admin.site) # Test with list_editable fields m.list_display = ['id', 'name', 'parent'] @@ -52,10 +62,14 @@ def test_result_list_html(self): template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') context = Context({'cl': cl}) table_output = template.render(context) - self.failIf(table_output.find(hidden_input_elem) == -1, - 'Failed to find expected hidden input element in: %s' % table_output) - self.failIf(table_output.find('' % hidden_input_elem) == -1, - 'Hidden input element is not enclosed in ' % editable_name_field == -1, + 'Failed to find "name" list_editable field in: %s' % table_output) class ChildAdmin(admin.ModelAdmin): list_display = ['name', 'parent'] From dc00873da09e49afb3a3d31248e61175f4b4fea4 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sat, 11 Sep 2010 03:25:39 +0000 Subject: [PATCH 135/902] [1.2.X] Fixed id attribute generation in the admin docs page. Patch from simeon. Fixed #3695. Thanks. Backport of r13728 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13747 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admindocs/templates/admin_doc/template_filter_index.html | 4 ++-- .../admindocs/templates/admin_doc/template_tag_index.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/django/contrib/admindocs/templates/admin_doc/template_filter_index.html b/django/contrib/admindocs/templates/admin_doc/template_filter_index.html index 53383edc23b0..74707627755c 100644 --- a/django/contrib/admindocs/templates/admin_doc/template_filter_index.html +++ b/django/contrib/admindocs/templates/admin_doc/template_filter_index.html @@ -15,7 +15,7 @@

        Template filter documentation

        {% firstof library.grouper "Built-in filters" %}

        {% if library.grouper %}

        To use these filters, put {% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %} in your template before using the filter.


        {% endif %} {% for filter in library.list|dictsort:"name" %} -

        {{ filter.name }}

        +

        {{ filter.name }}

        {{ filter.title }}

        {{ filter.body }}

        {% if not forloop.last %}
        {% endif %} @@ -36,7 +36,7 @@

        {{ filter.name }}

        {% firstof library.grouper "Built-in filters" %}

        diff --git a/django/contrib/admindocs/templates/admin_doc/template_tag_index.html b/django/contrib/admindocs/templates/admin_doc/template_tag_index.html index a4ad66fd96b1..774130bd703d 100644 --- a/django/contrib/admindocs/templates/admin_doc/template_tag_index.html +++ b/django/contrib/admindocs/templates/admin_doc/template_tag_index.html @@ -15,7 +15,7 @@

        Template tag documentation

        {% firstof library.grouper "Built-in tags" %}

        {% if library.grouper %}

        To use these tags, put {% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %} in your template before using the tag.


        {% endif %} {% for tag in library.list|dictsort:"name" %} -

        {{ tag.name }}

        +

        {{ tag.name }}

        {{ tag.title }}

        {{ tag.body }}

        {% if not forloop.last %}
        {% endif %} @@ -36,7 +36,7 @@

        {{ tag.title }}

        {% firstof library.grouper "Built-in tags" %}

        From 4eb78b89ce198dc27daea4bbe1ab71ba4bd5cb89 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sat, 11 Sep 2010 03:25:44 +0000 Subject: [PATCH 136/902] [1.2.X] Improved unicode-type, ASCII-convertible header handling in HttpResponse. Fixed #8765. Thanks to SmileyChris and semenov for working on this one. Backport of r13740 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13748 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/http/__init__.py | 12 ++++++------ tests/regressiontests/httpwrappers/tests.py | 11 ++++++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/django/http/__init__.py b/django/http/__init__.py index c3917a16b22d..e585a713dec6 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -303,13 +303,16 @@ class HttpResponse(object): def __init__(self, content='', mimetype=None, status=None, content_type=None): - from django.conf import settings + # _headers is a mapping of the lower-case name to the original case of + # the header (required for working with legacy systems) and the header + # value. Both the name of the header and its value are ASCII strings. + self._headers = {} self._charset = settings.DEFAULT_CHARSET if mimetype: content_type = mimetype # For backwards compatibility if not content_type: content_type = "%s; charset=%s" % (settings.DEFAULT_CONTENT_TYPE, - settings.DEFAULT_CHARSET) + self._charset) if not isinstance(content, basestring) and hasattr(content, '__iter__'): self._container = content self._is_string = False @@ -320,10 +323,7 @@ def __init__(self, content='', mimetype=None, status=None, if status: self.status_code = status - # _headers is a mapping of the lower-case name to the original case of - # the header (required for working with legacy systems) and the header - # value. - self._headers = {'content-type': ('Content-Type', content_type)} + self['Content-Type'] = content_type def __str__(self): """Full HTTP message, including headers.""" diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 23aa526a371d..d3991aad8685 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -204,9 +204,18 @@ def test_unicode_headers(self): r['value'] = u'test value' self.failUnless(isinstance(r['value'], str)) - # An error is raised When a unicode object with non-ascii is assigned. + # An error is raised ~hen a unicode object with non-ascii is assigned. self.assertRaises(UnicodeEncodeError, r.__setitem__, 'value', u't\xebst value') + # An error is raised when a unicode object with non-ASCII format is + # passed as initial mimetype or content_type. + self.assertRaises(UnicodeEncodeError, HttpResponse, + mimetype=u't\xebst value') + + # HttpResponse headers must be convertible to ASCII. + self.assertRaises(UnicodeEncodeError, HttpResponse, + content_type=u't\xebst value') + # The response also converts unicode keys to strings.) r[u'test'] = 'testing key' l = list(r.items()) From f140436e3a7295740f3d1a9e4adb9e1ae27278bb Mon Sep 17 00:00:00 2001 From: James Bennett Date: Sat, 11 Sep 2010 06:49:33 +0000 Subject: [PATCH 137/902] [1.2.X] Bump to 1.2.3. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13749 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django/__init__.py b/django/__init__.py index 48815232f82e..6a437e7000ea 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,4 +1,4 @@ -VERSION = (1, 2, 2, 'final', 0) +VERSION = (1, 2, 3, 'final', 0) def get_version(): version = '%s.%s' % (VERSION[0], VERSION[1]) diff --git a/setup.py b/setup.py index 938849aa6d61..f46d31a23123 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def fullsplit(path, result=None): author = 'Django Software Foundation', author_email = 'foundation@djangoproject.com', description = 'A high-level Python Web framework that encourages rapid development and clean, pragmatic design.', - download_url = 'http://media.djangoproject.com/releases/1.2/Django-1.2.2.tar.gz', + download_url = 'http://media.djangoproject.com/releases/1.2/Django-1.2.3.tar.gz', packages = packages, cmdclass = cmdclasses, data_files = data_files, From f4ebd701f239d4392ba8409125406eca514cbfb5 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sat, 11 Sep 2010 17:55:08 +0000 Subject: [PATCH 138/902] [1.2.X] Adjust AdminDocTests to run after r13728. Also match comments to tests and add test that was there in comment form only.Refs #3695. Backport of r13737 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13750 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/admin_views/tests.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index f76765236ef2..374c2aa6039c 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -2206,16 +2206,19 @@ def test_tags(self): self.assertContains(response, "

        Built-in tags

        ", count=2) # A builtin tag exists in both the index and detail - self.assertContains(response, '

        autoescape

        ') - self.assertContains(response, '
      • autoescape
      • ') + self.assertContains(response, '

        autoescape

        ') + self.assertContains(response, '
      • autoescape
      • ') # An app tag exists in both the index and detail - # The builtin tag group exists + self.assertContains(response, '

        get_flatpages

        ') + self.assertContains(response, '
      • get_flatpages
      • ') + + # The admin list tag group exists self.assertContains(response, "

        admin_list

        ", count=2) - # A builtin tag exists in both the index and detail - self.assertContains(response, '

        autoescape

        ') - self.assertContains(response, '
      • admin_actions
      • ') + # An admin list tag exists in both the index and detail + self.assertContains(response, '

        admin_actions

        ') + self.assertContains(response, '
      • admin_actions
      • ') def test_filters(self): response = self.client.get('/test_admin/admin/doc/filters/') @@ -2224,8 +2227,8 @@ def test_filters(self): self.assertContains(response, "

        Built-in filters

        ", count=2) # A builtin filter exists in both the index and detail - self.assertContains(response, '

        add

        ') - self.assertContains(response, '
      • add
      • ') + self.assertContains(response, '

        add

        ') + self.assertContains(response, '
      • add
      • ') except ImportError: pass From e4bd5e8a2d3442205cd292fe534d109777b55a7a Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 11 Sep 2010 18:38:24 +0000 Subject: [PATCH 139/902] [1.2.X] Fixed #13149 -- The admin `ForeignKeyRawIdWidget` now properly handles non-integer values. Thanks, Chris Adams. Backport of r13751 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13752 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/widgets.py | 6 +++--- django/forms/models.py | 2 +- tests/regressiontests/admin_widgets/tests.py | 12 ++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 1d321d0620a7..5832f8225336 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -154,9 +154,9 @@ def label_for_value(self, value): key = self.rel.get_related_field().name try: obj = self.rel.to._default_manager.using(self.db).get(**{key: value}) - except self.rel.to.DoesNotExist: + return ' %s' % escape(truncate_words(obj, 14)) + except (ValueError, self.rel.to.DoesNotExist): return '' - return ' %s' % escape(truncate_words(obj, 14)) class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): """ @@ -169,7 +169,7 @@ def __init__(self, rel, attrs=None, using=None): def render(self, name, value, attrs=None): attrs['class'] = 'vManyToManyRawIdAdminField' if value: - value = ','.join([str(v) for v in value]) + value = ','.join([force_unicode(v) for v in value]) else: value = '' return super(ManyToManyRawIdWidget, self).render(name, value, attrs) diff --git a/django/forms/models.py b/django/forms/models.py index 3a288203d39b..16a4f206b875 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -996,7 +996,7 @@ def to_python(self, value): try: key = self.to_field_name or 'pk' value = self.queryset.get(**{key: value}) - except self.queryset.model.DoesNotExist: + except (ValueError, self.queryset.model.DoesNotExist): raise ValidationError(self.error_messages['invalid_choice']) return value diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py index fd0c25ca1aac..c4456443351e 100644 --- a/tests/regressiontests/admin_widgets/tests.py +++ b/tests/regressiontests/admin_widgets/tests.py @@ -1,3 +1,5 @@ +# encoding: utf-8 + from django import forms from django.contrib import admin from django.contrib.admin import widgets @@ -151,3 +153,13 @@ def test_nonexistent_target_id(self): post_data) self.assertContains(response, 'Select a valid choice. That choice is not one of the available choices.') + + def test_invalid_target_id(self): + + for test_str in ('Iñtërnâtiônàlizætiøn', "1234'", -1234): + # This should result in an error message, not a server exception. + response = self.client.post('%s/admin_widgets/event/add/' % self.admin_root, + {"band": test_str}) + + self.assertContains(response, + 'Select a valid choice. That choice is not one of the available choices.') From 864361aabd02d1ba3b9c12813ec1c45f5f68b49c Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 11 Sep 2010 21:28:00 +0000 Subject: [PATCH 140/902] [1.2.X] Fixed #13390 -- `SplitDateTimeWidget` now recognizes when it's no longer required. Thanks vaxXxa for bug report and patch. Backport of r13753 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13754 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/forms/fields.py | 2 ++ tests/regressiontests/forms/widgets.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/django/forms/fields.py b/django/forms/fields.py index f6d9c4db54da..de14a5c8a8bc 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -399,6 +399,8 @@ def to_python(self, value): # components: date and time. if len(value) != 2: raise ValidationError(self.error_messages['invalid']) + if value[0] in validators.EMPTY_VALUES and value[1] in validators.EMPTY_VALUES: + return None value = '%s %s' % tuple(value) for format in self.input_formats or formats.get_format('DATETIME_INPUT_FORMATS'): try: diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py index 39d7d569a3b1..08d70c0030dc 100644 --- a/tests/regressiontests/forms/widgets.py +++ b/tests/regressiontests/forms/widgets.py @@ -1310,3 +1310,21 @@ def test_12048(self): # w2 ought to be independent of w1, since MultiWidget ought # to make a copy of its sub-widgets when it is copied. self.assertEqual(w1.choices, [1,2,3]) + + def test_13390(self): + # See ticket #13390 + class SplitDateForm(forms.Form): + field = forms.DateTimeField(widget=forms.SplitDateTimeWidget, required=False) + + form = SplitDateForm({'field': ''}) + self.assertTrue(form.is_valid()) + form = SplitDateForm({'field': ['', '']}) + self.assertTrue(form.is_valid()) + + class SplitDateRequiredForm(forms.Form): + field = forms.DateTimeField(widget=forms.SplitDateTimeWidget, required=True) + + form = SplitDateRequiredForm({'field': ''}) + self.assertFalse(form.is_valid()) + form = SplitDateRequiredForm({'field': ['', '']}) + self.assertFalse(form.is_valid()) From 24586d4d866b35f0b236aa4d375dcd6928162840 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sun, 12 Sep 2010 01:44:51 +0000 Subject: [PATCH 141/902] [1.2.X] Fixed regression in running the GeoDjango test suite after r13670 with addition of `GeoDjangoTestSuiteRunner` (replaces `run_gis_tests`, which is now a stub). Backport of r13755 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13756 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/tests/__init__.py | 211 +++++++++++++-------------- 1 file changed, 102 insertions(+), 109 deletions(-) diff --git a/django/contrib/gis/tests/__init__.py b/django/contrib/gis/tests/__init__.py index 0d862190c0eb..d9182f38778a 100644 --- a/django/contrib/gis/tests/__init__.py +++ b/django/contrib/gis/tests/__init__.py @@ -1,115 +1,108 @@ import sys +import unittest + +from django.conf import settings +from django.db.models import get_app +from django.test.simple import build_suite, DjangoTestSuiteRunner def run_tests(*args, **kwargs): from django.test.simple import run_tests as base_run_tests return base_run_tests(*args, **kwargs) -def geo_suite(): - """ - Builds a test suite for the GIS package. This is not named - `suite` so it will not interfere with the Django test suite (since - spatial database tables are required to execute these tests on - some backends). - """ - from django.conf import settings - from django.contrib.gis.geos import GEOS_PREPARE - from django.contrib.gis.gdal import HAS_GDAL - from django.contrib.gis.utils import HAS_GEOIP - from django.contrib.gis.tests.utils import postgis, mysql - from django.db import connection - from django.utils.importlib import import_module - - gis_tests = [] - - # Adding the GEOS tests. - from django.contrib.gis.geos import tests as geos_tests - gis_tests.append(geos_tests.suite()) - - # Tests that require use of a spatial database (e.g., creation of models) - test_apps = ['geoapp', 'relatedapp'] - if postgis and connection.ops.geography: - # Test geography support with PostGIS 1.5+. - test_apps.append('geogapp') - - # Tests that do not require setting up and tearing down a spatial database. - test_suite_names = [ - 'test_measure', - ] - - if HAS_GDAL: - # These tests require GDAL. - if not mysql: - test_apps.append('distapp') - - # Only PostGIS using GEOS 3.1+ can support 3D so far. - if postgis and GEOS_PREPARE: - test_apps.append('geo3d') - - test_suite_names.extend(['test_spatialrefsys', 'test_geoforms']) - test_apps.append('layermap') - - # Adding the GDAL tests. - from django.contrib.gis.gdal import tests as gdal_tests - gis_tests.append(gdal_tests.suite()) - else: - print >>sys.stderr, "GDAL not available - no tests requiring GDAL will be run." - - if HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'): - test_suite_names.append('test_geoip') - - # Adding the rest of the suites from the modules specified - # in the `test_suite_names`. - for suite_name in test_suite_names: - tsuite = import_module('django.contrib.gis.tests.' + suite_name) - gis_tests.append(tsuite.suite()) - - return gis_tests, test_apps - -def run_gis_tests(test_labels, **kwargs): - """ - Use this routine as the TEST_RUNNER in your settings in order to run the - GeoDjango test suite. This must be done as a database superuser for - PostGIS, so read the docstring in `run_test()` below for more details. - """ - from django.conf import settings - from django.db.models import loading - from django.contrib.gis.tests.utils import mysql - - # Getting initial values. - old_installed = settings.INSTALLED_APPS - old_root_urlconf = settings.ROOT_URLCONF - - # Overridding the INSTALLED_APPS with only what we need, - # to prevent unnecessary database table creation. - new_installed = ['django.contrib.sites', - 'django.contrib.sitemaps', - 'django.contrib.gis', - ] - - # Setting the URLs. - settings.ROOT_URLCONF = 'django.contrib.gis.tests.urls' - - # Creating the test suite, adding the test models to INSTALLED_APPS - # so they will be tested. - gis_tests, test_apps = geo_suite() - for test_model in test_apps: - module_name = 'django.contrib.gis.tests.%s' % test_model - new_installed.append(module_name) - - # Resetting the loaded flag to take into account what we appended to - # the INSTALLED_APPS (since this routine is invoked through - # django/core/management, it caches the apps; this ensures that syncdb - # will see our appended models) - settings.INSTALLED_APPS = new_installed - loading.cache.loaded = False - - kwargs['extra_tests'] = gis_tests - - # Running the tests using the GIS test runner. - result = run_tests(test_labels, **kwargs) - - # Restoring modified settings. - settings.INSTALLED_APPS = old_installed - settings.ROOT_URLCONF = old_root_urlconf - - return result +def run_gis_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=None): + import warnings + warnings.warn( + 'The run_gis_tests() test runner has been deprecated in favor of GeoDjangoTestSuiteRunner.', + PendingDeprecationWarning + ) + test_runner = GeoDjangoTestSuiteRunner(verbosity=verbosity, interactive=interactive, failfast=failfast) + return test_runner.run_tests(test_labels, extra_tests=extra_tests) + +class GeoDjangoTestSuiteRunner(DjangoTestSuiteRunner): + + def setup_test_environment(self, **kwargs): + super(GeoDjangoTestSuiteRunner, self).setup_test_environment(**kwargs) + + from django.db import connection + from django.contrib.gis.geos import GEOS_PREPARE + from django.contrib.gis.gdal import HAS_GDAL + + # Getting and storing the original values of INSTALLED_APPS and + # the ROOT_URLCONF. + self.old_installed = settings.INSTALLED_APPS + self.old_root_urlconf = settings.ROOT_URLCONF + + # Tests that require use of a spatial database (e.g., creation of models) + self.geo_apps = ['geoapp', 'relatedapp'] + if connection.ops.postgis and connection.ops.geography: + # Test geography support with PostGIS 1.5+. + self.geo_apps.append('geogapp') + + if HAS_GDAL: + # The following GeoDjango test apps depend on GDAL support. + if not connection.ops.mysql: + self.geo_apps.append('distapp') + + # 3D apps use LayerMapping, which uses GDAL. + if connection.ops.postgis and GEOS_PREPARE: + self.geo_apps.append('geo3d') + + self.geo_apps.append('layermap') + + # Constructing the new INSTALLED_APPS, and including applications + # within the GeoDjango test namespace (`self.geo_apps`). + new_installed = ['django.contrib.sites', + 'django.contrib.sitemaps', + 'django.contrib.gis', + ] + new_installed.extend(['django.contrib.gis.tests.%s' % app + for app in self.geo_apps]) + settings.INSTALLED_APPS = new_installed + + # Setting the URLs. + settings.ROOT_URLCONF = 'django.contrib.gis.tests.urls' + + def teardown_test_environment(self, **kwargs): + super(GeoDjangoTestSuiteRunner, self).teardown_test_environment(**kwargs) + settings.INSTALLED_APPS = self.old_installed + settings.ROOT_URLCONF = self.old_root_urlconf + + def build_suite(self, test_labels, extra_tests=None, **kwargs): + """ + This method is overridden to construct a suite consisting only of tests + for GeoDjango. + """ + suite = unittest.TestSuite() + + # Adding the GEOS tests. + from django.contrib.gis.geos import tests as geos_tests + suite.addTest(geos_tests.suite()) + + # Adding the measurment tests. + from django.contrib.gis.tests import test_measure + suite.addTest(test_measure.suite()) + + # Adding GDAL tests, and any test suite that depends on GDAL, to the + # suite if GDAL is available. + from django.contrib.gis.gdal import HAS_GDAL + if HAS_GDAL: + from django.contrib.gis.gdal import tests as gdal_tests + suite.addTest(gdal_tests.suite()) + + from django.contrib.gis.tests import test_spatialrefsys, test_geoforms + suite.addTest(test_spatialrefsys.suite()) + suite.addTest(test_geoforms.suite()) + else: + sys.stderr.write('GDAL not available - no tests requiring GDAL will be run.\n') + + # Add GeoIP tests to the suite, if the library and data is available. + from django.contrib.gis.utils import HAS_GEOIP + if HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'): + from django.contrib.gis.tests import test_geoip + suite.addTest(test_geoip.suite()) + + # Finally, adding the suites for each of the GeoDjango test apps. + for app_name in self.geo_apps: + suite.addTest(build_suite(get_app(app_name))) + + return suite From 31744a78cd8fe8b3c9878e8c2219fa63e0078e83 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sun, 12 Sep 2010 02:09:17 +0000 Subject: [PATCH 142/902] [1.2.X] Fixed #14060 -- PostGIS never implemented the `~=` operator for geography types, so removed support for it. Backport of r13757 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13758 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/db/backends/postgis/operations.py | 2 -- django/contrib/gis/tests/geogapp/tests.py | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index b1a9e315292e..5affcf9a189a 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -233,8 +233,6 @@ def get_dist_ops(operator): }) self.geography_operators = { 'bboverlaps' : PostGISOperator('&&'), - 'exact' : PostGISOperator('~='), - 'same_as' : PostGISOperator('~='), } # Creating a dictionary lookup of all GIS terms for PostGIS. diff --git a/django/contrib/gis/tests/geogapp/tests.py b/django/contrib/gis/tests/geogapp/tests.py index 7be519310317..3dea930afd01 100644 --- a/django/contrib/gis/tests/geogapp/tests.py +++ b/django/contrib/gis/tests/geogapp/tests.py @@ -44,6 +44,10 @@ def test04_invalid_operators_functions(self): # `@` operator not available. self.assertRaises(ValueError, City.objects.filter(point__contained=z.poly).count) + # Regression test for #14060, `~=` was never really implemented for PostGIS. + htown = City.objects.get(name='Houston') + self.assertRaises(ValueError, City.objects.get, point__exact=htown.point) + def test05_geography_layermapping(self): "Testing LayerMapping support on models with geography fields." # There is a similar test in `layermap` that uses the same data set, From ceedef1447a161cf551542084813ab5bb56e8e01 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 06:39:02 +0000 Subject: [PATCH 143/902] [1.2.X] More cleanups for the manifest file. Backport of r13760 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13761 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- MANIFEST.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index be5f30b796a7..d12b2523cd83 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,11 +5,10 @@ include LICENSE include MANIFEST.in include django/contrib/gis/gdal/LICENSE include django/contrib/gis/geos/LICENSE -include django/dispatch/LICENSE.txt +include django/dispatch/license.txt include django/utils/simplejson/LICENSE.txt recursive-include docs * recursive-include scripts * -recursive-include examples * recursive-include extras * recursive-include tests * recursive-include django/conf/locale * From 6bc74f06e284dd4be40833c4231449e6c5d23ed0 Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Sun, 12 Sep 2010 18:14:45 +0000 Subject: [PATCH 144/902] [1.2.X] Fix AdminDocsTest failure on 1.2.X branch by correcting the test to reference an app tag that actually exists in 1.2.X. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13764 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/admin_views/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 374c2aa6039c..6a4d3975b5b6 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -2210,8 +2210,8 @@ def test_tags(self): self.assertContains(response, '
      • autoescape
      • ') # An app tag exists in both the index and detail - self.assertContains(response, '

        get_flatpages

        ') - self.assertContains(response, '
      • get_flatpages
      • ') + self.assertContains(response, '

        get_comment_count

        ') + self.assertContains(response, '
      • get_comment_count
      • ') # The admin list tag group exists self.assertContains(response, "

        admin_list

        ", count=2) From 686d6a414ca1ed30ff529616e0364427ea3678bc Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sun, 12 Sep 2010 19:07:09 +0000 Subject: [PATCH 145/902] [1.2.X] Add warning when using cache keys that might not work with memcached. This means testing with local dev caches (not memcache) will warn developers if they are introducing inadvertent importabilities. There is also the ability to silence the warning if a dev is not planning to use memcache and knows what they are doing with their keys. Thanks to Carl Meyer for the patch. Fixed #6447. Backport of r13766 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13767 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/cache/__init__.py | 2 +- django/core/cache/backends/base.py | 28 ++++++++- django/core/cache/backends/db.py | 5 ++ django/core/cache/backends/dummy.py | 15 +++-- django/core/cache/backends/filebased.py | 5 ++ django/core/cache/backends/locmem.py | 5 ++ django/core/exceptions.py | 7 ++- docs/topics/cache.txt | 39 ++++++++++++ .../regressiontests/cache/liberal_backend.py | 9 +++ tests/regressiontests/cache/tests.py | 62 ++++++++++++++++++- 10 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 tests/regressiontests/cache/liberal_backend.py diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py index 1b602908cbd5..680f724f94c7 100644 --- a/django/core/cache/__init__.py +++ b/django/core/cache/__init__.py @@ -18,7 +18,7 @@ from cgi import parse_qsl from django.conf import settings from django.core import signals -from django.core.cache.backends.base import InvalidCacheBackendError +from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning from django.utils import importlib # Name for use in settings file --> name of module in "backends" directory. diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index e58267a2e931..83dd46180484 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -1,10 +1,18 @@ "Base Cache class." -from django.core.exceptions import ImproperlyConfigured +import warnings + +from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning class InvalidCacheBackendError(ImproperlyConfigured): pass +class CacheKeyWarning(DjangoRuntimeWarning): + pass + +# Memcached does not accept keys longer than this. +MEMCACHE_MAX_KEY_LENGTH = 250 + class BaseCache(object): def __init__(self, params): timeout = params.get('timeout', 300) @@ -116,3 +124,21 @@ def delete_many(self, keys): def clear(self): """Remove *all* values from the cache at once.""" raise NotImplementedError + + def validate_key(self, key): + """ + Warn about keys that would not be portable to the memcached + backend. This encourages (but does not force) writing backend-portable + cache code. + + """ + if len(key) > MEMCACHE_MAX_KEY_LENGTH: + warnings.warn('Cache key will cause errors if used with memcached: ' + '%s (longer than %s)' % (key, MEMCACHE_MAX_KEY_LENGTH), + CacheKeyWarning) + for char in key: + if ord(char) < 33 or ord(char) == 127: + warnings.warn('Cache key contains characters that will cause ' + 'errors if used with memcached: %r' % key, + CacheKeyWarning) + diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py index 833fa749aac9..c4429c80b3b4 100644 --- a/django/core/cache/backends/db.py +++ b/django/core/cache/backends/db.py @@ -46,6 +46,7 @@ class CacheEntry(object): self._cull_frequency = 3 def get(self, key, default=None): + self.validate_key(key) db = router.db_for_read(self.cache_model_class) table = connections[db].ops.quote_name(self._table) cursor = connections[db].cursor() @@ -65,9 +66,11 @@ def get(self, key, default=None): return pickle.loads(base64.decodestring(value)) def set(self, key, value, timeout=None): + self.validate_key(key) self._base_set('set', key, value, timeout) def add(self, key, value, timeout=None): + self.validate_key(key) return self._base_set('add', key, value, timeout) def _base_set(self, mode, key, value, timeout=None): @@ -103,6 +106,7 @@ def _base_set(self, mode, key, value, timeout=None): return True def delete(self, key): + self.validate_key(key) db = router.db_for_write(self.cache_model_class) table = connections[db].ops.quote_name(self._table) cursor = connections[db].cursor() @@ -111,6 +115,7 @@ def delete(self, key): transaction.commit_unless_managed(using=db) def has_key(self, key): + self.validate_key(key) db = router.db_for_read(self.cache_model_class) table = connections[db].ops.quote_name(self._table) cursor = connections[db].cursor() diff --git a/django/core/cache/backends/dummy.py b/django/core/cache/backends/dummy.py index 4337484cb11f..f73b7408bc16 100644 --- a/django/core/cache/backends/dummy.py +++ b/django/core/cache/backends/dummy.py @@ -6,22 +6,25 @@ class CacheClass(BaseCache): def __init__(self, *args, **kwargs): pass - def add(self, *args, **kwargs): + def add(self, key, *args, **kwargs): + self.validate_key(key) return True def get(self, key, default=None): + self.validate_key(key) return default - def set(self, *args, **kwargs): - pass + def set(self, key, *args, **kwargs): + self.validate_key(key) - def delete(self, *args, **kwargs): - pass + def delete(self, key, *args, **kwargs): + self.validate_key(key) def get_many(self, *args, **kwargs): return {} - def has_key(self, *args, **kwargs): + def has_key(self, key, *args, **kwargs): + self.validate_key(key) return False def set_many(self, *args, **kwargs): diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py index 6057f11ef7b0..46e69f309174 100644 --- a/django/core/cache/backends/filebased.py +++ b/django/core/cache/backends/filebased.py @@ -32,6 +32,7 @@ def __init__(self, dir, params): self._createdir() def add(self, key, value, timeout=None): + self.validate_key(key) if self.has_key(key): return False @@ -39,6 +40,7 @@ def add(self, key, value, timeout=None): return True def get(self, key, default=None): + self.validate_key(key) fname = self._key_to_file(key) try: f = open(fname, 'rb') @@ -56,6 +58,7 @@ def get(self, key, default=None): return default def set(self, key, value, timeout=None): + self.validate_key(key) fname = self._key_to_file(key) dirname = os.path.dirname(fname) @@ -79,6 +82,7 @@ def set(self, key, value, timeout=None): pass def delete(self, key): + self.validate_key(key) try: self._delete(self._key_to_file(key)) except (IOError, OSError): @@ -95,6 +99,7 @@ def _delete(self, fname): pass def has_key(self, key): + self.validate_key(key) fname = self._key_to_file(key) try: f = open(fname, 'rb') diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py index eff1201b9777..fe33d333070e 100644 --- a/django/core/cache/backends/locmem.py +++ b/django/core/cache/backends/locmem.py @@ -30,6 +30,7 @@ def __init__(self, _, params): self._lock = RWLock() def add(self, key, value, timeout=None): + self.validate_key(key) self._lock.writer_enters() try: exp = self._expire_info.get(key) @@ -44,6 +45,7 @@ def add(self, key, value, timeout=None): self._lock.writer_leaves() def get(self, key, default=None): + self.validate_key(key) self._lock.reader_enters() try: exp = self._expire_info.get(key) @@ -76,6 +78,7 @@ def _set(self, key, value, timeout=None): self._expire_info[key] = time.time() + timeout def set(self, key, value, timeout=None): + self.validate_key(key) self._lock.writer_enters() # Python 2.4 doesn't allow combined try-except-finally blocks. try: @@ -87,6 +90,7 @@ def set(self, key, value, timeout=None): self._lock.writer_leaves() def has_key(self, key): + self.validate_key(key) self._lock.reader_enters() try: exp = self._expire_info.get(key) @@ -127,6 +131,7 @@ def _delete(self, key): pass def delete(self, key): + self.validate_key(key) self._lock.writer_enters() try: self._delete(key) diff --git a/django/core/exceptions.py b/django/core/exceptions.py index ee6d5fe37b11..21be8702fa49 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -1,4 +1,9 @@ -"Global Django exceptions" +""" +Global Django exception and warning classes. +""" + +class DjangoRuntimeWarning(RuntimeWarning): + pass class ObjectDoesNotExist(Exception): "The requested object does not exist" diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index e5ca52649476..579719941135 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -641,6 +641,45 @@ 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. +Cache key warnings +------------------ + +.. versionadded:: 1.3 + +Memcached, the most commonly-used production cache backend, does not allow +cache keys longer than 250 characters or containing whitespace or control +characters, and using such keys will cause an exception. To encourage +cache-portable code and minimize unpleasant surprises, the other built-in cache +backends issue a warning (``django.core.cache.backends.base.CacheKeyWarning``) +if a key is used that would cause an error on memcached. + +If you are using a production backend that can accept a wider range of keys (a +custom backend, or one of the non-memcached built-in backends), and want to use +this wider range without warnings, you can silence ``CacheKeyWarning`` with +this code in the ``management`` module of one of your +:setting:`INSTALLED_APPS`:: + + import warnings + + from django.core.cache import CacheKeyWarning + + warnings.simplefilter("ignore", CacheKeyWarning) + +If you want to instead provide custom key validation logic for one of the +built-in backends, you can subclass it, override just the ``validate_key`` +method, and follow the instructions for `using a custom cache backend`_. For +instance, to do this for the ``locmem`` backend, put this code in a module:: + + from django.core.cache.backends.locmem import CacheClass as LocMemCacheClass + + class CacheClass(LocMemCacheClass): + def validate_key(self, key): + """Custom validation, raising exceptions or warnings as needed.""" + # ... + +...and use the dotted Python path to this module as the scheme portion of your +:setting:`CACHE_BACKEND`. + Upstream caches =============== diff --git a/tests/regressiontests/cache/liberal_backend.py b/tests/regressiontests/cache/liberal_backend.py new file mode 100644 index 000000000000..5c7e3126906a --- /dev/null +++ b/tests/regressiontests/cache/liberal_backend.py @@ -0,0 +1,9 @@ +from django.core.cache.backends.locmem import CacheClass as LocMemCacheClass + +class LiberalKeyValidationMixin(object): + def validate_key(self, key): + pass + +class CacheClass(LiberalKeyValidationMixin, LocMemCacheClass): + pass + diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 0f9f5c9fffd5..1e0a4046bb57 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -8,11 +8,12 @@ import tempfile import time import unittest +import warnings from django.conf import settings from django.core import management from django.core.cache import get_cache -from django.core.cache.backends.base import InvalidCacheBackendError +from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning from django.http import HttpResponse, HttpRequest from django.middleware.cache import FetchFromCacheMiddleware, UpdateCacheMiddleware from django.utils import translation @@ -366,6 +367,33 @@ def perform_cull_test(self, initial_count, final_count): count = count + 1 self.assertEqual(count, final_count) + def test_invalid_keys(self): + """ + All the builtin backends (except memcached, see below) should warn on + keys that would be refused by memcached. This encourages portable + caching code without making it too difficult to use production backends + with more liberal key rules. Refs #6447. + + """ + # On Python 2.6+ we could use the catch_warnings context + # manager to test this warning nicely. Since we can't do that + # yet, the cleanest option is to temporarily ask for + # CacheKeyWarning to be raised as an exception. + warnings.simplefilter("error", CacheKeyWarning) + + # memcached does not allow whitespace or control characters in keys + self.assertRaises(CacheKeyWarning, self.cache.set, 'key with spaces', 'value') + # memcached limits key length to 250 + self.assertRaises(CacheKeyWarning, self.cache.set, 'a' * 251, 'value') + + # The warnings module has no public API for getting the + # current list of warning filters, so we can't save that off + # and reset to the previous value, we have to globally reset + # it. The effect will be the same, as long as the Django test + # runner doesn't add any global warning filters (it currently + # does not). + warnings.resetwarnings() + class DBCacheTests(unittest.TestCase, BaseCacheTests): def setUp(self): # Spaces are used in the table name to ensure quoting/escaping is working @@ -397,6 +425,22 @@ class MemcachedCacheTests(unittest.TestCase, BaseCacheTests): def setUp(self): self.cache = get_cache(settings.CACHE_BACKEND) + def test_invalid_keys(self): + """ + On memcached, we don't introduce a duplicate key validation + step (for speed reasons), we just let the memcached API + library raise its own exception on bad keys. Refs #6447. + + In order to be memcached-API-library agnostic, we only assert + that a generic exception of some kind is raised. + + """ + # memcached does not allow whitespace or control characters in keys + self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value') + # memcached limits key length to 250 + self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value') + + class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): """ Specific test cases for the file-based cache. @@ -429,6 +473,22 @@ def test_subdirectory_removal(self): def test_cull(self): self.perform_cull_test(50, 28) +class CustomCacheKeyValidationTests(unittest.TestCase): + """ + Tests for the ability to mixin a custom ``validate_key`` method to + a custom cache backend that otherwise inherits from a builtin + backend, and override the default key validation. Refs #6447. + + """ + def test_custom_key_validation(self): + cache = get_cache('regressiontests.cache.liberal_backend://') + + # this key is both longer than 250 characters, and has spaces + key = 'some key with spaces' * 15 + val = 'a value' + cache.set(key, val) + self.assertEqual(cache.get(key), val) + class CacheUtils(unittest.TestCase): """TestCase for django.utils.cache functions.""" From 3a6afc7e0b93ebc7dbe4060974dfc2d6e2973903 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 12 Sep 2010 19:41:48 +0000 Subject: [PATCH 146/902] [1.2.X] Fixed #13702 -- Made sure to actually fall back to the l10n format strings provided in the settings, when disabled. Backport from trunk (r13770). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13771 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/global_settings.py | 4 ++-- django/utils/formats.py | 20 ++++++++++---------- tests/regressiontests/i18n/tests.py | 12 ++++++------ tests/regressiontests/templates/filters.py | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 6033297e5c17..2714bfb9ab4b 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -370,8 +370,8 @@ # Boolean that sets whether to add thousand separator when formatting numbers USE_THOUSAND_SEPARATOR = False -# Number of digits that will be togheter, when spliting them by THOUSAND_SEPARATOR -# 0 means no grouping, 3 means splitting by thousands... +# Number of digits that will be together, when spliting them by +# THOUSAND_SEPARATOR. 0 means no grouping, 3 means splitting by thousands... NUMBER_GROUPING = 0 # Thousand separator symbol diff --git a/django/utils/formats.py b/django/utils/formats.py index 31027abd23f5..372998f22157 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -79,16 +79,16 @@ def localize(value): Checks if value is a localizable type (date, number...) and returns it formatted as a string using current locale format """ - if settings.USE_L10N: - if isinstance(value, (decimal.Decimal, float, int)): - return number_format(value) - elif isinstance(value, datetime.datetime): - return date_format(value, 'DATETIME_FORMAT') - elif isinstance(value, datetime.date): - return date_format(value) - elif isinstance(value, datetime.time): - return time_format(value, 'TIME_FORMAT') - return value + if isinstance(value, (decimal.Decimal, float, int)): + return number_format(value) + elif isinstance(value, datetime.datetime): + return date_format(value, 'DATETIME_FORMAT') + elif isinstance(value, datetime.date): + return date_format(value) + elif isinstance(value, datetime.time): + return time_format(value, 'TIME_FORMAT') + else: + return value def localize_input(value, default=None): """ diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 32c991a436b1..8c1c7f3d8bc3 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -170,14 +170,14 @@ def test_l10n_disabled(self): self.assertEqual(u'desembre 2009', date_format(self.d, 'YEAR_MONTH_FORMAT')) self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(self.dt, 'SHORT_DATETIME_FORMAT')) self.assertEqual('No localizable', localize('No localizable')) - self.assertEqual(decimal.Decimal('66666.666'), localize(self.n)) - self.assertEqual(99999.999, localize(self.f)) - self.assertEqual(datetime.date(2009, 12, 31), localize(self.d)) - self.assertEqual(datetime.datetime(2009, 12, 31, 20, 50), localize(self.dt)) + self.assertEqual('66666.666', localize(self.n)) + self.assertEqual('99999.999', localize(self.f)) + self.assertEqual(u'des. 31, 2009', localize(self.d)) + self.assertEqual(u'des. 31, 2009, 8:50 p.m.', localize(self.dt)) self.assertEqual(u'66666.666', Template('{{ n }}').render(self.ctxt)) self.assertEqual(u'99999.999', Template('{{ f }}').render(self.ctxt)) - self.assertEqual(u'2009-12-31', Template('{{ d }}').render(self.ctxt)) - self.assertEqual(u'2009-12-31 20:50:00', Template('{{ dt }}').render(self.ctxt)) + self.assertEqual(u'des. 31, 2009', Template('{{ d }}').render(self.ctxt)) + self.assertEqual(u'des. 31, 2009, 8:50 p.m.', Template('{{ dt }}').render(self.ctxt)) self.assertEqual(u'66666.67', Template('{{ n|floatformat:2 }}').render(self.ctxt)) self.assertEqual(u'100000.0', Template('{{ f|floatformat }}').render(self.ctxt)) self.assertEqual(u'10:15 a.m.', Template('{{ t|time:"TIME_FORMAT" }}').render(self.ctxt)) diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py index d351c550b7d3..af34c58a9f86 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/regressiontests/templates/filters.py @@ -346,5 +346,5 @@ def get_filter_tests(): 'add04': (r'{{ i|add:"16" }}', {'i': 'not_an_int'}, 'not_an_int16'), 'add05': (r'{{ l1|add:l2 }}', {'l1': [1, 2], 'l2': [3, 4]}, '[1, 2, 3, 4]'), 'add06': (r'{{ t1|add:t2 }}', {'t1': (3, 4), 't2': (1, 2)}, '(3, 4, 1, 2)'), - 'add07': (r'{{ d|add:t }}', {'d': date(2000, 1, 1), 't': timedelta(10)}, '2000-01-11'), + 'add07': (r'{{ d|add:t }}', {'d': date(2000, 1, 1), 't': timedelta(10)}, 'Jan. 11, 2000'), } From e41d1b761449a4167474fe83e6a7862b145db84c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:09:23 +0000 Subject: [PATCH 147/902] [1.2.X] Migrated the custom_managers tests. Thanks to Alex Gaynor. Backport of r13774 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13791 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/custom_managers/models.py | 48 --------------- tests/modeltests/custom_managers/tests.py | 71 ++++++++++++++++++++++ 2 files changed, 71 insertions(+), 48 deletions(-) create mode 100644 tests/modeltests/custom_managers/tests.py diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py index 40bf77e27333..1052552bb32d 100644 --- a/tests/modeltests/custom_managers/models.py +++ b/tests/modeltests/custom_managers/models.py @@ -57,51 +57,3 @@ class Car(models.Model): def __unicode__(self): return self.name - -__test__ = {'API_TESTS':""" ->>> p1 = Person(first_name='Bugs', last_name='Bunny', fun=True) ->>> p1.save() ->>> p2 = Person(first_name='Droopy', last_name='Dog', fun=False) ->>> p2.save() ->>> Person.objects.get_fun_people() -[] - -# The RelatedManager used on the 'books' descriptor extends the default manager ->>> from modeltests.custom_managers.models import PublishedBookManager ->>> isinstance(p2.books, PublishedBookManager) -True - ->>> b1 = Book(title='How to program', author='Rodney Dangerfield', is_published=True) ->>> b1.save() ->>> b2 = Book(title='How to be smart', author='Albert Einstein', is_published=False) ->>> b2.save() - -# The default manager, "objects", doesn't exist, -# because a custom one was provided. ->>> Book.objects -Traceback (most recent call last): - ... -AttributeError: type object 'Book' has no attribute 'objects' - -# The RelatedManager used on the 'authors' descriptor extends the default manager ->>> from modeltests.custom_managers.models import PersonManager ->>> isinstance(b2.authors, PersonManager) -True - ->>> Book.published_objects.all() -[] - ->>> c1 = Car(name='Corvette', mileage=21, top_speed=180) ->>> c1.save() ->>> c2 = Car(name='Neon', mileage=31, top_speed=100) ->>> c2.save() ->>> Car.cars.order_by('name') -[, ] ->>> Car.fast_cars.all() -[] - -# Each model class gets a "_default_manager" attribute, which is a reference -# to the first manager defined in the class. In this case, it's "cars". ->>> Car._default_manager.order_by('name') -[, ] -"""} diff --git a/tests/modeltests/custom_managers/tests.py b/tests/modeltests/custom_managers/tests.py new file mode 100644 index 000000000000..8721e9ac5210 --- /dev/null +++ b/tests/modeltests/custom_managers/tests.py @@ -0,0 +1,71 @@ +from django.test import TestCase + +from models import Person, Book, Car, PersonManager, PublishedBookManager + + +class CustomManagerTests(TestCase): + def test_manager(self): + p1 = Person.objects.create(first_name="Bugs", last_name="Bunny", fun=True) + p2 = Person.objects.create(first_name="Droopy", last_name="Dog", fun=False) + + self.assertQuerysetEqual( + Person.objects.get_fun_people(), [ + "Bugs Bunny" + ], + unicode + ) + # The RelatedManager used on the 'books' descriptor extends the default + # manager + self.assertTrue(isinstance(p2.books, PublishedBookManager)) + + b1 = Book.published_objects.create( + title="How to program", author="Rodney Dangerfield", is_published=True + ) + b2 = Book.published_objects.create( + title="How to be smart", author="Albert Einstein", is_published=False + ) + + # The default manager, "objects", doesn't exist, because a custom one + # was provided. + self.assertRaises(AttributeError, lambda: Book.objects) + + # The RelatedManager used on the 'authors' descriptor extends the + # default manager + self.assertTrue(isinstance(b2.authors, PersonManager)) + + self.assertQuerysetEqual( + Book.published_objects.all(), [ + "How to program", + ], + lambda b: b.title + ) + + c1 = Car.cars.create(name="Corvette", mileage=21, top_speed=180) + c2 = Car.cars.create(name="Neon", mileage=31, top_speed=100) + + self.assertQuerysetEqual( + Car.cars.order_by("name"), [ + "Corvette", + "Neon", + ], + lambda c: c.name + ) + + self.assertQuerysetEqual( + Car.fast_cars.all(), [ + "Corvette", + ], + lambda c: c.name + ) + + # Each model class gets a "_default_manager" attribute, which is a + # reference to the first manager defined in the class. In this case, + # it's "cars". + + self.assertQuerysetEqual( + Car._default_manager.order_by("name"), [ + "Corvette", + "Neon", + ], + lambda c: c.name + ) From 574ee8279219ac12b5e66cd57f4a21d69694bae0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:09:37 +0000 Subject: [PATCH 148/902] [1.2.X] Migrated custom_methods doctests. Thanks to Alex Gaynor. Backport of r13775 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13792 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/custom_methods/models.py | 23 ------------- tests/modeltests/custom_methods/tests.py | 42 +++++++++++++++++++++++ 2 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 tests/modeltests/custom_methods/tests.py diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py index d420871373c5..15150a6c3f5d 100644 --- a/tests/modeltests/custom_methods/models.py +++ b/tests/modeltests/custom_methods/models.py @@ -33,27 +33,4 @@ def articles_from_same_day_2(self): WHERE pub_date = %s AND id != %s""", [connection.ops.value_to_db_date(self.pub_date), self.id]) - # The asterisk in "(*row)" tells Python to expand the list into - # positional arguments to Article(). return [self.__class__(*row) for row in cursor.fetchall()] - -__test__ = {'API_TESTS':""" -# Create a couple of Articles. ->>> from datetime import date ->>> a = Article(id=None, headline='Area man programs in Python', pub_date=date(2005, 7, 27)) ->>> a.save() ->>> b = Article(id=None, headline='Beatles reunite', pub_date=date(2005, 7, 27)) ->>> b.save() - -# Test the custom methods. ->>> a.was_published_today() -False ->>> a.articles_from_same_day_1() -[] ->>> a.articles_from_same_day_2() -[] ->>> b.articles_from_same_day_1() -[] ->>> b.articles_from_same_day_2() -[] -"""} diff --git a/tests/modeltests/custom_methods/tests.py b/tests/modeltests/custom_methods/tests.py new file mode 100644 index 000000000000..90a7f0da293f --- /dev/null +++ b/tests/modeltests/custom_methods/tests.py @@ -0,0 +1,42 @@ +from datetime import date + +from django.test import TestCase + +from models import Article + + +class MethodsTests(TestCase): + def test_custom_methods(self): + a = Article.objects.create( + headline="Area man programs in Python", pub_date=date(2005, 7, 27) + ) + b = Article.objects.create( + headline="Beatles reunite", pub_date=date(2005, 7, 27) + ) + + self.assertFalse(a.was_published_today()) + self.assertQuerysetEqual( + a.articles_from_same_day_1(), [ + "Beatles reunite", + ], + lambda a: a.headline, + ) + self.assertQuerysetEqual( + a.articles_from_same_day_2(), [ + "Beatles reunite", + ], + lambda a: a.headline + ) + + self.assertQuerysetEqual( + b.articles_from_same_day_1(), [ + "Area man programs in Python", + ], + lambda a: a.headline, + ) + self.assertQuerysetEqual( + b.articles_from_same_day_2(), [ + "Area man programs in Python", + ], + lambda a: a.headline + ) From 592288fb95f12edb170dec6e6eecbdeacfde7263 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:09:49 +0000 Subject: [PATCH 149/902] [1.2.X] Migrated custom_pk doctests. Thanks to Alex Gaynor. Backport of r13776 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13793 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/custom_pk/fields.py | 1 + tests/modeltests/custom_pk/models.py | 135 -------------------- tests/modeltests/custom_pk/tests.py | 183 +++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 135 deletions(-) create mode 100644 tests/modeltests/custom_pk/tests.py diff --git a/tests/modeltests/custom_pk/fields.py b/tests/modeltests/custom_pk/fields.py index 319e42f974bf..2eeb80e6ace8 100644 --- a/tests/modeltests/custom_pk/fields.py +++ b/tests/modeltests/custom_pk/fields.py @@ -3,6 +3,7 @@ from django.db import models + class MyWrapper(object): def __init__(self, value): self.value = value diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py index 84e6480af9de..ff2f2bac1ba8 100644 --- a/tests/modeltests/custom_pk/models.py +++ b/tests/modeltests/custom_pk/models.py @@ -40,138 +40,3 @@ def __unicode__(self): class Foo(models.Model): bar = models.ForeignKey(Bar) -__test__ = {'API_TESTS':""" ->>> dan = Employee(employee_code=123, first_name='Dan', last_name='Jones') ->>> dan.save() ->>> Employee.objects.all() -[] - ->>> fran = Employee(employee_code=456, first_name='Fran', last_name='Bones') ->>> fran.save() ->>> Employee.objects.all() -[, ] - ->>> Employee.objects.get(pk=123) - ->>> Employee.objects.get(pk=456) - ->>> Employee.objects.get(pk=42) -Traceback (most recent call last): - ... -DoesNotExist: Employee matching query does not exist. - -# Use the name of the primary key, rather than pk. ->>> Employee.objects.get(employee_code__exact=123) - - -# pk can be used as a substitute for the primary key. ->>> Employee.objects.filter(pk__in=[123, 456]) -[, ] - -# The primary key can be accessed via the pk property on the model. ->>> e = Employee.objects.get(pk=123) ->>> e.pk -123 - -# Or we can use the real attribute name for the primary key: ->>> e.employee_code -123 - -# Fran got married and changed her last name. ->>> fran = Employee.objects.get(pk=456) ->>> fran.last_name = 'Jones' ->>> fran.save() ->>> Employee.objects.filter(last_name__exact='Jones') -[, ] ->>> emps = Employee.objects.in_bulk([123, 456]) ->>> emps[123] - - ->>> b = Business(name='Sears') ->>> b.save() ->>> b.employees.add(dan, fran) ->>> b.employees.all() -[, ] ->>> fran.business_set.all() -[] ->>> Business.objects.in_bulk(['Sears']) -{u'Sears': } - ->>> Business.objects.filter(name__exact='Sears') -[] ->>> Business.objects.filter(pk='Sears') -[] - -# Queries across tables, involving primary key ->>> Employee.objects.filter(business__name__exact='Sears') -[, ] ->>> Employee.objects.filter(business__pk='Sears') -[, ] - ->>> Business.objects.filter(employees__employee_code__exact=123) -[] ->>> Business.objects.filter(employees__pk=123) -[] ->>> Business.objects.filter(employees__first_name__startswith='Fran') -[] - -# Primary key may be unicode string ->>> bus = Business(name=u'jaźń') ->>> bus.save() - -# The primary key must also obviously be unique, so trying to create a new -# object with the same primary key will fail. ->>> try: -... sid = transaction.savepoint() -... Employee.objects.create(employee_code=123, first_name='Fred', last_name='Jones') -... transaction.savepoint_commit(sid) -... except Exception, e: -... if isinstance(e, IntegrityError): -... transaction.savepoint_rollback(sid) -... print "Pass" -... else: -... print "Fail with %s" % type(e) -Pass - -# Regression for #10785 -- Custom fields can be used for primary keys. ->>> new_bar = Bar.objects.create() ->>> new_foo = Foo.objects.create(bar=new_bar) - -# FIXME: This still doesn't work, but will require some changes in -# get_db_prep_lookup to fix it. -# >>> f = Foo.objects.get(bar=new_bar.pk) -# >>> f == new_foo -# True -# >>> f.bar == new_bar -# True - ->>> f = Foo.objects.get(bar=new_bar) ->>> f == new_foo -True ->>> f.bar == new_bar -True - -"""} - -# SQLite lets objects be saved with an empty primary key, even though an -# integer is expected. So we can't check for an error being raised in that case -# for SQLite. Remove it from the suite for this next bit. -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.sqlite3': - __test__["API_TESTS"] += """ -# The primary key must be specified, so an error is raised if you try to create -# an object without it. ->>> try: -... sid = transaction.savepoint() -... Employee.objects.create(first_name='Tom', last_name='Smith') -... print 'hello' -... transaction.savepoint_commit(sid) -... print 'hello2' -... except Exception, e: -... if isinstance(e, IntegrityError): -... transaction.savepoint_rollback(sid) -... print "Pass" -... else: -... print "Fail with %s" % type(e) -Pass - -""" diff --git a/tests/modeltests/custom_pk/tests.py b/tests/modeltests/custom_pk/tests.py new file mode 100644 index 000000000000..6ef4bdd4331d --- /dev/null +++ b/tests/modeltests/custom_pk/tests.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +from django.conf import settings +from django.db import DEFAULT_DB_ALIAS, transaction, IntegrityError +from django.test import TestCase + +from models import Employee, Business, Bar, Foo + + +class CustomPKTests(TestCase): + def test_custom_pk(self): + dan = Employee.objects.create( + employee_code=123, first_name="Dan", last_name="Jones" + ) + self.assertQuerysetEqual( + Employee.objects.all(), [ + "Dan Jones", + ], + unicode + ) + + fran = Employee.objects.create( + employee_code=456, first_name="Fran", last_name="Bones" + ) + self.assertQuerysetEqual( + Employee.objects.all(), [ + "Fran Bones", + "Dan Jones", + ], + unicode + ) + + self.assertEqual(Employee.objects.get(pk=123), dan) + self.assertEqual(Employee.objects.get(pk=456), fran) + + self.assertRaises(Employee.DoesNotExist, + lambda: Employee.objects.get(pk=42) + ) + + # Use the name of the primary key, rather than pk. + self.assertEqual(Employee.objects.get(employee_code=123), dan) + # pk can be used as a substitute for the primary key. + self.assertQuerysetEqual( + Employee.objects.filter(pk__in=[123, 456]), [ + "Fran Bones", + "Dan Jones", + ], + unicode + ) + # The primary key can be accessed via the pk property on the model. + e = Employee.objects.get(pk=123) + self.assertEqual(e.pk, 123) + # Or we can use the real attribute name for the primary key: + self.assertEqual(e.employee_code, 123) + + # Fran got married and changed her last name. + fran = Employee.objects.get(pk=456) + fran.last_name = "Jones" + fran.save() + + self.assertQuerysetEqual( + Employee.objects.filter(last_name="Jones"), [ + "Dan Jones", + "Fran Jones", + ], + unicode + ) + + emps = Employee.objects.in_bulk([123, 456]) + self.assertEqual(emps[123], dan) + + b = Business.objects.create(name="Sears") + b.employees.add(dan, fran) + self.assertQuerysetEqual( + b.employees.all(), [ + "Dan Jones", + "Fran Jones", + ], + unicode + ) + self.assertQuerysetEqual( + fran.business_set.all(), [ + "Sears", + ], + lambda b: b.name + ) + + self.assertEqual(Business.objects.in_bulk(["Sears"]), { + "Sears": b, + }) + + self.assertQuerysetEqual( + Business.objects.filter(name="Sears"), [ + "Sears" + ], + lambda b: b.name + ) + self.assertQuerysetEqual( + Business.objects.filter(pk="Sears"), [ + "Sears", + ], + lambda b: b.name + ) + + # Queries across tables, involving primary key + self.assertQuerysetEqual( + Employee.objects.filter(business__name="Sears"), [ + "Dan Jones", + "Fran Jones", + ], + unicode, + ) + self.assertQuerysetEqual( + Employee.objects.filter(business__pk="Sears"), [ + "Dan Jones", + "Fran Jones", + ], + unicode, + ) + + self.assertQuerysetEqual( + Business.objects.filter(employees__employee_code=123), [ + "Sears", + ], + lambda b: b.name + ) + self.assertQuerysetEqual( + Business.objects.filter(employees__pk=123), [ + "Sears", + ], + lambda b: b.name, + ) + + self.assertQuerysetEqual( + Business.objects.filter(employees__first_name__startswith="Fran"), [ + "Sears", + ], + lambda b: b.name + ) + + def test_unicode_pk(self): + # Primary key may be unicode string + bus = Business.objects.create(name=u'jaźń') + + def test_unique_pk(self): + # The primary key must also obviously be unique, so trying to create a + # new object with the same primary key will fail. + e = Employee.objects.create( + employee_code=123, first_name="Frank", last_name="Jones" + ) + sid = transaction.savepoint() + self.assertRaises(IntegrityError, + Employee.objects.create, employee_code=123, first_name="Fred", last_name="Jones" + ) + transaction.savepoint_rollback(sid) + + def test_custom_field_pk(self): + # Regression for #10785 -- Custom fields can be used for primary keys. + new_bar = Bar.objects.create() + new_foo = Foo.objects.create(bar=new_bar) + + # FIXME: This still doesn't work, but will require some changes in + # get_db_prep_lookup to fix it. + # f = Foo.objects.get(bar=new_bar.pk) + # self.assertEqual(f, new_foo) + # self.assertEqual(f.bar, new_bar) + + f = Foo.objects.get(bar=new_bar) + self.assertEqual(f, new_foo), + self.assertEqual(f.bar, new_bar) + + + # SQLite lets objects be saved with an empty primary key, even though an + # integer is expected. So we can't check for an error being raised in that + # case for SQLite. Remove it from the suite for this next bit. + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.sqlite3': + def test_required_pk(self): + # The primary key must be specified, so an error is raised if you + # try to create an object without it. + sid = transaction.savepoint() + self.assertRaises(IntegrityError, + Employee.objects.create, first_name="Tom", last_name="Smith" + ) + transaction.savepoint_rollback(sid) From 7fbdeb49f90d596a2bd2374ce71419e20e2c5f8c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:10:03 +0000 Subject: [PATCH 150/902] [1.2.X] Migrated defer doctests. Thanks to Alex Gaynor. Backport of r13777 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13794 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/defer/models.py | 164 +------------------------------ tests/modeltests/defer/tests.py | 137 ++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 163 deletions(-) create mode 100644 tests/modeltests/defer/tests.py diff --git a/tests/modeltests/defer/models.py b/tests/modeltests/defer/models.py index ac3c876a57d6..4fddd39d2635 100644 --- a/tests/modeltests/defer/models.py +++ b/tests/modeltests/defer/models.py @@ -3,7 +3,7 @@ """ from django.db import models -from django.db.models.query_utils import DeferredAttribute + class Secondary(models.Model): first = models.CharField(max_length=50) @@ -22,165 +22,3 @@ class Child(Primary): class BigChild(Primary): other = models.CharField(max_length=50) - -def count_delayed_fields(obj, debug=False): - """ - Returns the number of delayed attributes on the given model instance. - """ - count = 0 - for field in obj._meta.fields: - if isinstance(obj.__class__.__dict__.get(field.attname), - DeferredAttribute): - if debug: - print field.name, field.attname - count += 1 - return count - - -__test__ = {"API_TEST": """ -To all outward appearances, instances with deferred fields look the same as -normal instances when we examine attribute values. Therefore we test for the -number of deferred fields on returned instances (by poking at the internals), -as a way to observe what is going on. - ->>> s1 = Secondary.objects.create(first="x1", second="y1") ->>> p1 = Primary.objects.create(name="p1", value="xx", related=s1) - ->>> qs = Primary.objects.all() - ->>> count_delayed_fields(qs.defer('name')[0]) -1 ->>> count_delayed_fields(qs.only('name')[0]) -2 ->>> count_delayed_fields(qs.defer('related__first')[0]) -0 ->>> obj = qs.select_related().only('related__first')[0] ->>> count_delayed_fields(obj) -2 ->>> obj.related_id == s1.pk -True ->>> count_delayed_fields(qs.defer('name').extra(select={'a': 1})[0]) -1 ->>> count_delayed_fields(qs.extra(select={'a': 1}).defer('name')[0]) -1 ->>> count_delayed_fields(qs.defer('name').defer('value')[0]) -2 ->>> count_delayed_fields(qs.only('name').only('value')[0]) -2 ->>> count_delayed_fields(qs.only('name').defer('value')[0]) -2 ->>> count_delayed_fields(qs.only('name', 'value').defer('value')[0]) -2 ->>> count_delayed_fields(qs.defer('name').only('value')[0]) -2 ->>> obj = qs.only()[0] ->>> count_delayed_fields(qs.defer(None)[0]) -0 ->>> count_delayed_fields(qs.only('name').defer(None)[0]) -0 - -User values() won't defer anything (you get the full list of dictionaries -back), but it still works. ->>> qs.defer('name').values()[0] == {'id': p1.id, 'name': u'p1', 'value': 'xx', 'related_id': s1.id} -True ->>> qs.only('name').values()[0] == {'id': p1.id, 'name': u'p1', 'value': 'xx', 'related_id': s1.id} -True - -Using defer() and only() with get() is also valid. ->>> count_delayed_fields(qs.defer('name').get(pk=p1.pk)) -1 ->>> count_delayed_fields(qs.only('name').get(pk=p1.pk)) -2 - -# KNOWN NOT TO WORK: >>> count_delayed_fields(qs.only('name').select_related('related')[0]) -# KNOWN NOT TO WORK >>> count_delayed_fields(qs.defer('related').select_related('related')[0]) - -# Saving models with deferred fields is possible (but inefficient, since every -# field has to be retrieved first). - ->>> obj = Primary.objects.defer("value").get(name="p1") ->>> obj.name = "a new name" ->>> obj.save() ->>> Primary.objects.all() -[] - -# Regression for #10572 - A subclass with no extra fields can defer fields from the base class ->>> _ = Child.objects.create(name="c1", value="foo", related=s1) - -# You can defer a field on a baseclass when the subclass has no fields ->>> obj = Child.objects.defer("value").get(name="c1") ->>> count_delayed_fields(obj) -1 ->>> obj.name -u"c1" ->>> obj.value -u"foo" ->>> obj.name = "c2" ->>> obj.save() - -# You can retrive a single column on a base class with no fields ->>> obj = Child.objects.only("name").get(name="c2") ->>> count_delayed_fields(obj) -3 ->>> obj.name -u"c2" ->>> obj.value -u"foo" ->>> obj.name = "cc" ->>> obj.save() - ->>> _ = BigChild.objects.create(name="b1", value="foo", related=s1, other="bar") - -# You can defer a field on a baseclass ->>> obj = BigChild.objects.defer("value").get(name="b1") ->>> count_delayed_fields(obj) -1 ->>> obj.name -u"b1" ->>> obj.value -u"foo" ->>> obj.other -u"bar" ->>> obj.name = "b2" ->>> obj.save() - -# You can defer a field on a subclass ->>> obj = BigChild.objects.defer("other").get(name="b2") ->>> count_delayed_fields(obj) -1 ->>> obj.name -u"b2" ->>> obj.value -u"foo" ->>> obj.other -u"bar" ->>> obj.name = "b3" ->>> obj.save() - -# You can retrieve a single field on a baseclass ->>> obj = BigChild.objects.only("name").get(name="b3") ->>> count_delayed_fields(obj) -4 ->>> obj.name -u"b3" ->>> obj.value -u"foo" ->>> obj.other -u"bar" ->>> obj.name = "b4" ->>> obj.save() - -# You can retrieve a single field on a baseclass ->>> obj = BigChild.objects.only("other").get(name="b4") ->>> count_delayed_fields(obj) -4 ->>> obj.name -u"b4" ->>> obj.value -u"foo" ->>> obj.other -u"bar" ->>> obj.name = "bb" ->>> obj.save() - -"""} diff --git a/tests/modeltests/defer/tests.py b/tests/modeltests/defer/tests.py new file mode 100644 index 000000000000..5f6c53dee2ed --- /dev/null +++ b/tests/modeltests/defer/tests.py @@ -0,0 +1,137 @@ +from django.db.models.query_utils import DeferredAttribute +from django.test import TestCase + +from models import Secondary, Primary, Child, BigChild + + +class DeferTests(TestCase): + def assert_delayed(self, obj, num): + count = 0 + for field in obj._meta.fields: + if isinstance(obj.__class__.__dict__.get(field.attname), + DeferredAttribute): + count += 1 + self.assertEqual(count, num) + + def test_defer(self): + # To all outward appearances, instances with deferred fields look the + # same as normal instances when we examine attribute values. Therefore + # we test for the number of deferred fields on returned instances (by + # poking at the internals), as a way to observe what is going on. + + s1 = Secondary.objects.create(first="x1", second="y1") + p1 = Primary.objects.create(name="p1", value="xx", related=s1) + + qs = Primary.objects.all() + + self.assert_delayed(qs.defer("name")[0], 1) + self.assert_delayed(qs.only("name")[0], 2) + self.assert_delayed(qs.defer("related__first")[0], 0) + + obj = qs.select_related().only("related__first")[0] + self.assert_delayed(obj, 2) + + self.assertEqual(obj.related_id, s1.pk) + + self.assert_delayed(qs.defer("name").extra(select={"a": 1})[0], 1) + self.assert_delayed(qs.extra(select={"a": 1}).defer("name")[0], 1) + self.assert_delayed(qs.defer("name").defer("value")[0], 2) + self.assert_delayed(qs.only("name").only("value")[0], 2) + self.assert_delayed(qs.only("name").defer("value")[0], 2) + self.assert_delayed(qs.only("name", "value").defer("value")[0], 2) + self.assert_delayed(qs.defer("name").only("value")[0], 2) + + obj = qs.only()[0] + self.assert_delayed(qs.defer(None)[0], 0) + self.assert_delayed(qs.only("name").defer(None)[0], 0) + + # User values() won't defer anything (you get the full list of + # dictionaries back), but it still works. + self.assertEqual(qs.defer("name").values()[0], { + "id": p1.id, + "name": "p1", + "value": "xx", + "related_id": s1.id, + }) + self.assertEqual(qs.only("name").values()[0], { + "id": p1.id, + "name": "p1", + "value": "xx", + "related_id": s1.id, + }) + + # Using defer() and only() with get() is also valid. + self.assert_delayed(qs.defer("name").get(pk=p1.pk), 1) + self.assert_delayed(qs.only("name").get(pk=p1.pk), 2) + + # DOES THIS WORK? + self.assert_delayed(qs.only("name").select_related("related")[0], 1) + self.assert_delayed(qs.defer("related").select_related("related")[0], 0) + + # Saving models with deferred fields is possible (but inefficient, + # since every field has to be retrieved first). + obj = Primary.objects.defer("value").get(name="p1") + obj.name = "a new name" + obj.save() + self.assertQuerysetEqual( + Primary.objects.all(), [ + "a new name", + ], + lambda p: p.name + ) + + # Regression for #10572 - A subclass with no extra fields can defer + # fields from the base class + Child.objects.create(name="c1", value="foo", related=s1) + # You can defer a field on a baseclass when the subclass has no fields + obj = Child.objects.defer("value").get(name="c1") + self.assert_delayed(obj, 1) + self.assertEqual(obj.name, "c1") + self.assertEqual(obj.value, "foo") + obj.name = "c2" + obj.save() + + # You can retrive a single column on a base class with no fields + obj = Child.objects.only("name").get(name="c2") + self.assert_delayed(obj, 3) + self.assertEqual(obj.name, "c2") + self.assertEqual(obj.value, "foo") + obj.name = "cc" + obj.save() + + BigChild.objects.create(name="b1", value="foo", related=s1, other="bar") + # You can defer a field on a baseclass + obj = BigChild.objects.defer("value").get(name="b1") + self.assert_delayed(obj, 1) + self.assertEqual(obj.name, "b1") + self.assertEqual(obj.value, "foo") + self.assertEqual(obj.other, "bar") + obj.name = "b2" + obj.save() + + # You can defer a field on a subclass + obj = BigChild.objects.defer("other").get(name="b2") + self.assert_delayed(obj, 1) + self.assertEqual(obj.name, "b2") + self.assertEqual(obj.value, "foo") + self.assertEqual(obj.other, "bar") + obj.name = "b3" + obj.save() + + # You can retrieve a single field on a baseclass + obj = BigChild.objects.only("name").get(name="b3") + self.assert_delayed(obj, 4) + self.assertEqual(obj.name, "b3") + self.assertEqual(obj.value, "foo") + self.assertEqual(obj.other, "bar") + obj.name = "b4" + obj.save() + + # You can retrieve a single field on a baseclass + obj = BigChild.objects.only("other").get(name="b4") + self.assert_delayed(obj, 4) + self.assertEqual(obj.name, "b4") + self.assertEqual(obj.value, "foo") + self.assertEqual(obj.other, "bar") + obj.name = "bb" + obj.save() From 24f4a4b26f1fe2be130b4f1117e5189b9f438e78 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:10:17 +0000 Subject: [PATCH 151/902] [1.2.X] Migrated delete doctests. Thanks to Alex Gaynor. Backport of r13778 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13795 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/delete/models.py | 165 ------------------------------ tests/modeltests/delete/tests.py | 135 ++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 165 deletions(-) create mode 100644 tests/modeltests/delete/tests.py diff --git a/tests/modeltests/delete/models.py b/tests/modeltests/delete/models.py index 0e063fd8f010..9c81f6b8f867 100644 --- a/tests/modeltests/delete/models.py +++ b/tests/modeltests/delete/models.py @@ -40,168 +40,3 @@ class E(DefaultRepr, models.Model): class F(DefaultRepr, models.Model): e = models.ForeignKey(E, related_name='f_rel') - -__test__ = {'API_TESTS': """ -### Tests for models A,B,C,D ### - -## First, test the CollectedObjects data structure directly - ->>> from django.db.models.query import CollectedObjects - ->>> g = CollectedObjects() ->>> g.add("key1", 1, "item1", None) -False ->>> g["key1"] -{1: 'item1'} ->>> g.add("key2", 1, "item1", "key1") -False ->>> g.add("key2", 2, "item2", "key1") -False ->>> g["key2"] -{1: 'item1', 2: 'item2'} ->>> g.add("key3", 1, "item1", "key1") -False ->>> g.add("key3", 1, "item1", "key2") -True ->>> g.ordered_keys() -['key3', 'key2', 'key1'] - ->>> g.add("key2", 1, "item1", "key3") -True ->>> g.ordered_keys() -Traceback (most recent call last): - ... -CyclicDependency: There is a cyclic dependency of items to be processed. - - -## Second, test the usage of CollectedObjects by Model.delete() - -# Due to the way that transactions work in the test harness, -# doing m.delete() here can work but fail in a real situation, -# since it may delete all objects, but not in the right order. -# So we manually check that the order of deletion is correct. - -# Also, it is possible that the order is correct 'accidentally', due -# solely to order of imports etc. To check this, we set the order -# that 'get_models()' will retrieve to a known 'nice' order, and -# then try again with a known 'tricky' order. Slightly naughty -# access to internals here :-) - -# If implementation changes, then the tests may need to be simplified: -# - remove the lines that set the .keyOrder and clear the related -# object caches -# - remove the second set of tests (with a2, b2 etc) - ->>> from django.db.models.loading import cache - ->>> def clear_rel_obj_caches(models): -... for m in models: -... if hasattr(m._meta, '_related_objects_cache'): -... del m._meta._related_objects_cache - -# Nice order ->>> cache.app_models['delete'].keyOrder = ['a', 'b', 'c', 'd'] ->>> clear_rel_obj_caches([A, B, C, D]) - ->>> a1 = A() ->>> a1.save() ->>> b1 = B(a=a1) ->>> b1.save() ->>> c1 = C(b=b1) ->>> c1.save() ->>> d1 = D(c=c1, a=a1) ->>> d1.save() - ->>> o = CollectedObjects() ->>> a1._collect_sub_objects(o) ->>> o.keys() -[, , , ] ->>> a1.delete() - -# Same again with a known bad order ->>> cache.app_models['delete'].keyOrder = ['d', 'c', 'b', 'a'] ->>> clear_rel_obj_caches([A, B, C, D]) - ->>> a2 = A() ->>> a2.save() ->>> b2 = B(a=a2) ->>> b2.save() ->>> c2 = C(b=b2) ->>> c2.save() ->>> d2 = D(c=c2, a=a2) ->>> d2.save() - ->>> o = CollectedObjects() ->>> a2._collect_sub_objects(o) ->>> o.keys() -[, , , ] ->>> a2.delete() - -### Tests for models E,F - nullable related fields ### - -## First, test the CollectedObjects data structure directly - ->>> g = CollectedObjects() ->>> g.add("key1", 1, "item1", None) -False ->>> g.add("key2", 1, "item1", "key1", nullable=True) -False ->>> g.add("key1", 1, "item1", "key2") -True ->>> g.ordered_keys() -['key1', 'key2'] - -## Second, test the usage of CollectedObjects by Model.delete() - ->>> e1 = E() ->>> e1.save() ->>> f1 = F(e=e1) ->>> f1.save() ->>> e1.f = f1 ->>> e1.save() - -# Since E.f is nullable, we should delete F first (after nulling out -# the E.f field), then E. - ->>> o = CollectedObjects() ->>> e1._collect_sub_objects(o) ->>> o.keys() -[, ] - -# temporarily replace the UpdateQuery class to verify that E.f is actually nulled out first ->>> import django.db.models.sql ->>> class LoggingUpdateQuery(django.db.models.sql.UpdateQuery): -... def clear_related(self, related_field, pk_list, using): -... print "CLEARING FIELD",related_field.name -... return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list, using) ->>> original_class = django.db.models.sql.UpdateQuery ->>> django.db.models.sql.UpdateQuery = LoggingUpdateQuery ->>> e1.delete() -CLEARING FIELD f - ->>> e2 = E() ->>> e2.save() ->>> f2 = F(e=e2) ->>> f2.save() ->>> e2.f = f2 ->>> e2.save() - -# Same deal as before, though we are starting from the other object. - ->>> o = CollectedObjects() ->>> f2._collect_sub_objects(o) ->>> o.keys() -[, ] - ->>> f2.delete() -CLEARING FIELD f - -# Put this back to normal ->>> django.db.models.sql.UpdateQuery = original_class - -# Restore the app cache to previous condition so that all models are accounted for. ->>> cache.app_models['delete'].keyOrder = ['a', 'b', 'c', 'd', 'e', 'f'] ->>> clear_rel_obj_caches([A, B, C, D, E, F]) - -""" -} diff --git a/tests/modeltests/delete/tests.py b/tests/modeltests/delete/tests.py new file mode 100644 index 000000000000..7927cce1c1dc --- /dev/null +++ b/tests/modeltests/delete/tests.py @@ -0,0 +1,135 @@ +from django.db.models import sql +from django.db.models.loading import cache +from django.db.models.query import CollectedObjects +from django.db.models.query_utils import CyclicDependency +from django.test import TestCase + +from models import A, B, C, D, E, F + + +class DeleteTests(TestCase): + def clear_rel_obj_caches(self, *models): + for m in models: + if hasattr(m._meta, '_related_objects_cache'): + del m._meta._related_objects_cache + + def order_models(self, *models): + cache.app_models["delete"].keyOrder = models + + def setUp(self): + self.order_models("a", "b", "c", "d", "e", "f") + self.clear_rel_obj_caches(A, B, C, D, E, F) + + def tearDown(self): + self.order_models("a", "b", "c", "d", "e", "f") + self.clear_rel_obj_caches(A, B, C, D, E, F) + + def test_collected_objects(self): + g = CollectedObjects() + self.assertFalse(g.add("key1", 1, "item1", None)) + self.assertEqual(g["key1"], {1: "item1"}) + + self.assertFalse(g.add("key2", 1, "item1", "key1")) + self.assertFalse(g.add("key2", 2, "item2", "key1")) + + self.assertEqual(g["key2"], {1: "item1", 2: "item2"}) + + self.assertFalse(g.add("key3", 1, "item1", "key1")) + self.assertTrue(g.add("key3", 1, "item1", "key2")) + self.assertEqual(g.ordered_keys(), ["key3", "key2", "key1"]) + + self.assertTrue(g.add("key2", 1, "item1", "key3")) + self.assertRaises(CyclicDependency, g.ordered_keys) + + def test_delete(self): + ## Second, test the usage of CollectedObjects by Model.delete() + + # Due to the way that transactions work in the test harness, doing + # m.delete() here can work but fail in a real situation, since it may + # delete all objects, but not in the right order. So we manually check + # that the order of deletion is correct. + + # Also, it is possible that the order is correct 'accidentally', due + # solely to order of imports etc. To check this, we set the order that + # 'get_models()' will retrieve to a known 'nice' order, and then try + # again with a known 'tricky' order. Slightly naughty access to + # internals here :-) + + # If implementation changes, then the tests may need to be simplified: + # - remove the lines that set the .keyOrder and clear the related + # object caches + # - remove the second set of tests (with a2, b2 etc) + + a1 = A.objects.create() + b1 = B.objects.create(a=a1) + c1 = C.objects.create(b=b1) + d1 = D.objects.create(c=c1, a=a1) + + o = CollectedObjects() + a1._collect_sub_objects(o) + self.assertEqual(o.keys(), [D, C, B, A]) + a1.delete() + + # Same again with a known bad order + self.order_models("d", "c", "b", "a") + self.clear_rel_obj_caches(A, B, C, D) + + a2 = A.objects.create() + b2 = B.objects.create(a=a2) + c2 = C.objects.create(b=b2) + d2 = D.objects.create(c=c2, a=a2) + + o = CollectedObjects() + a2._collect_sub_objects(o) + self.assertEqual(o.keys(), [D, C, B, A]) + a2.delete() + + def test_collected_objects_null(self): + g = CollectedObjects() + self.assertFalse(g.add("key1", 1, "item1", None)) + self.assertFalse(g.add("key2", 1, "item1", "key1", nullable=True)) + self.assertTrue(g.add("key1", 1, "item1", "key2")) + self.assertEqual(g.ordered_keys(), ["key1", "key2"]) + + def test_delete_nullable(self): + e1 = E.objects.create() + f1 = F.objects.create(e=e1) + e1.f = f1 + e1.save() + + # Since E.f is nullable, we should delete F first (after nulling out + # the E.f field), then E. + + o = CollectedObjects() + e1._collect_sub_objects(o) + self.assertEqual(o.keys(), [F, E]) + + # temporarily replace the UpdateQuery class to verify that E.f is + # actually nulled out first + + logged = [] + class LoggingUpdateQuery(sql.UpdateQuery): + def clear_related(self, related_field, pk_list, using): + logged.append(related_field.name) + return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list, using) + original = sql.UpdateQuery + sql.UpdateQuery = LoggingUpdateQuery + + e1.delete() + self.assertEqual(logged, ["f"]) + logged = [] + + e2 = E.objects.create() + f2 = F.objects.create(e=e2) + e2.f = f2 + e2.save() + + # Same deal as before, though we are starting from the other object. + o = CollectedObjects() + f2._collect_sub_objects(o) + self.assertEqual(o.keys(), [F, E]) + f2.delete() + self.assertEqual(logged, ["f"]) + logged = [] + + sql.UpdateQuery = original From f7f2c7251a91499e901a241e0bfd120485d025a2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:10:30 +0000 Subject: [PATCH 152/902] [1.2.X] Migrated i18n and field_defaults doctests. Thanks to Alex Gaynor. Backport of r13779 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13796 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/field_defaults/models.py | 38 ----------------------- tests/modeltests/field_defaults/tests.py | 16 ++++++++++ tests/regressiontests/i18n/models.py | 6 ---- tests/regressiontests/i18n/tests.py | 21 +++++++++++-- 4 files changed, 34 insertions(+), 47 deletions(-) create mode 100644 tests/modeltests/field_defaults/tests.py diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py index f25813414729..0dd1f729344c 100644 --- a/tests/modeltests/field_defaults/models.py +++ b/tests/modeltests/field_defaults/models.py @@ -19,41 +19,3 @@ class Article(models.Model): def __unicode__(self): return self.headline - -__test__ = {'API_TESTS': u""" ->>> from datetime import datetime - -# No articles are in the system yet. ->>> Article.objects.all() -[] - -# Create an Article. ->>> a = Article(id=None) - -# Grab the current datetime it should be very close to the default that just -# got saved as a.pub_date ->>> now = datetime.now() - -# Save it into the database. You have to call save() explicitly. ->>> a.save() - -# Now it has an ID. Note it's a long integer, as designated by the trailing "L". ->>> a.id -1L - -# Access database columns via Python attributes. ->>> a.headline -u'Default headline' - -# make sure the two dates are sufficiently close ->>> d = now - a.pub_date ->>> d.seconds < 5 -True - -# make sure that SafeString/SafeUnicode fields work ->>> from django.utils.safestring import SafeUnicode, SafeString ->>> a.headline = SafeUnicode(u'Iñtërnâtiônàlizætiøn1') ->>> a.save() ->>> a.headline = SafeString(u'Iñtërnâtiônàlizætiøn1'.encode('utf-8')) ->>> a.save() -"""} diff --git a/tests/modeltests/field_defaults/tests.py b/tests/modeltests/field_defaults/tests.py new file mode 100644 index 000000000000..a23f64404a05 --- /dev/null +++ b/tests/modeltests/field_defaults/tests.py @@ -0,0 +1,16 @@ +from datetime import datetime + +from django.test import TestCase + +from models import Article + + +class DefaultTests(TestCase): + def test_field_defaults(self): + a = Article() + now = datetime.now() + a.save() + + self.assertTrue(isinstance(a.id, (int, long))) + self.assertEqual(a.headline, "Default headline") + self.assertTrue((now - a.pub_date).seconds < 5) diff --git a/tests/regressiontests/i18n/models.py b/tests/regressiontests/i18n/models.py index 8b142b36bc53..75cd996f8354 100644 --- a/tests/regressiontests/i18n/models.py +++ b/tests/regressiontests/i18n/models.py @@ -10,9 +10,3 @@ class Company(models.Model): date_added = models.DateTimeField(default=datetime(1799,1,31,23,59,59,0)) cents_payed = models.DecimalField(max_digits=4, decimal_places=2) products_delivered = models.IntegerField() - -__test__ = {'API_TESTS': ''' ->>> tm = TestModel() ->>> tm.save() -''' -} diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 8c1c7f3d8bc3..7e2e9f8228c7 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -5,14 +5,17 @@ import sys import pickle -from django.template import Template, Context from django.conf import settings +from django.template import Template, Context +from django.test import TestCase from django.utils.formats import get_format, date_format, time_format, localize, localize_input from django.utils.numberformat import format as nformat -from django.test import TestCase +from django.utils.safestring import mark_safe, SafeString, SafeUnicode from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, to_locale + from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm +from models import Company, TestModel class TranslationTests(TestCase): @@ -59,7 +62,6 @@ def test_safe_status(self): """ Translating a string requiring no auto-escaping shouldn't change the "safe" status. """ - from django.utils.safestring import mark_safe, SafeString, SafeUnicode s = mark_safe('Password') self.assertEqual(SafeString, type(s)) activate('de') @@ -620,3 +622,16 @@ class DjangoFallbackResolutionOrderI18NTests(ResolutionOrderI18NTests): def test_django_fallback(self): self.assertUgettext('Date/time', 'Datum/Zeit') + + +class TestModels(TestCase): + def test_lazy(self): + tm = TestModel() + tm.save() + + def test_safestr(self): + c = Company(cents_payed=12, products_delivered=1) + c.name = SafeUnicode(u'Iñtërnâtiônàlizætiøn1') + c.save() + c.name = SafeString(u'Iñtërnâtiônàlizætiøn1'.encode('utf-8')) + c.save() From 106600edb63cdebf5764a25a6fa5cb1ac7117c4c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:10:43 +0000 Subject: [PATCH 153/902] [1.2.X] Migrated the field_subclsasing doctests. Thanks to Alex Gaynor. Backport of r13780 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13797 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/field_subclassing/fields.py | 8 +-- tests/modeltests/field_subclassing/models.py | 54 ----------------- tests/modeltests/field_subclassing/tests.py | 64 ++++++++++++++++++-- 3 files changed, 63 insertions(+), 63 deletions(-) diff --git a/tests/modeltests/field_subclassing/fields.py b/tests/modeltests/field_subclassing/fields.py index a43714dcf514..1f9bdf5e0a08 100644 --- a/tests/modeltests/field_subclassing/fields.py +++ b/tests/modeltests/field_subclassing/fields.py @@ -53,18 +53,18 @@ def get_prep_lookup(self, lookup_type, value): class JSONField(models.TextField): __metaclass__ = models.SubfieldBase - + description = ("JSONField automatically serializes and desializes values to " "and from JSON.") - + def to_python(self, value): if not value: return None - + if isinstance(value, basestring): value = json.loads(value) return value - + def get_db_prep_save(self, value): if value is None: return None diff --git a/tests/modeltests/field_subclassing/models.py b/tests/modeltests/field_subclassing/models.py index a9fe88fe7774..4a55b72961d0 100644 --- a/tests/modeltests/field_subclassing/models.py +++ b/tests/modeltests/field_subclassing/models.py @@ -2,7 +2,6 @@ Tests for field subclassing. """ -from django.core import serializers from django.db import models from django.utils.encoding import force_unicode @@ -18,56 +17,3 @@ def __unicode__(self): class DataModel(models.Model): data = JSONField() - -__test__ = {'API_TESTS': ur""" -# Creating a model with custom fields is done as per normal. ->>> s = Small(1, 2) ->>> print s -12 ->>> m = MyModel(name='m', data=s) ->>> m.save() - -# Custom fields still have normal field's attributes. ->>> m._meta.get_field('data').verbose_name -'small field' - -# The m.data attribute has been initialised correctly. It's a Small object. ->>> m.data.first, m.data.second -(1, 2) - -# The data loads back from the database correctly and 'data' has the right type. ->>> m1 = MyModel.objects.get(pk=m.pk) ->>> isinstance(m1.data, Small) -True ->>> print m1.data -12 - -# We can do normal filtering on the custom field (and will get an error when we -# use a lookup type that does not make sense). ->>> s1 = Small(1, 3) ->>> s2 = Small('a', 'b') ->>> MyModel.objects.filter(data__in=[s, s1, s2]) -[] ->>> MyModel.objects.filter(data__lt=s) -Traceback (most recent call last): -... -TypeError: Invalid lookup type: 'lt' - -# Serialization works, too. ->>> stream = serializers.serialize("json", MyModel.objects.all()) ->>> stream -'[{"pk": 1, "model": "field_subclassing.mymodel", "fields": {"data": "12", "name": "m"}}]' ->>> obj = list(serializers.deserialize("json", stream))[0] ->>> obj.object == m -True - -# Test retrieving custom field data ->>> m.delete() ->>> m1 = MyModel(name="1", data=Small(1, 2)) ->>> m1.save() ->>> m2 = MyModel(name="2", data=Small(2, 3)) ->>> m2.save() ->>> for m in MyModel.objects.all(): print unicode(m.data) -12 -23 -"""} diff --git a/tests/modeltests/field_subclassing/tests.py b/tests/modeltests/field_subclassing/tests.py index 731ab51d242d..ba7148a6547a 100644 --- a/tests/modeltests/field_subclassing/tests.py +++ b/tests/modeltests/field_subclassing/tests.py @@ -1,21 +1,75 @@ +from django.core import serializers from django.test import TestCase -from models import DataModel +from fields import Small +from models import DataModel, MyModel class CustomField(TestCase): def test_defer(self): d = DataModel.objects.create(data=[1, 2, 3]) - + self.assertTrue(isinstance(d.data, list)) - + d = DataModel.objects.get(pk=d.pk) self.assertTrue(isinstance(d.data, list)) self.assertEqual(d.data, [1, 2, 3]) - + d = DataModel.objects.defer("data").get(pk=d.pk) d.save() - + d = DataModel.objects.get(pk=d.pk) self.assertTrue(isinstance(d.data, list)) self.assertEqual(d.data, [1, 2, 3]) + + def test_custom_field(self): + # Creating a model with custom fields is done as per normal. + s = Small(1, 2) + self.assertEqual(str(s), "12") + + m = MyModel.objects.create(name="m", data=s) + # Custom fields still have normal field's attributes. + self.assertEqual(m._meta.get_field("data").verbose_name, "small field") + + # The m.data attribute has been initialised correctly. It's a Small + # object. + self.assertEqual((m.data.first, m.data.second), (1, 2)) + + # The data loads back from the database correctly and 'data' has the + # right type. + m1 = MyModel.objects.get(pk=m.pk) + self.assertTrue(isinstance(m1.data, Small)) + self.assertEqual(str(m1.data), "12") + + # We can do normal filtering on the custom field (and will get an error + # when we use a lookup type that does not make sense). + s1 = Small(1, 3) + s2 = Small("a", "b") + self.assertQuerysetEqual( + MyModel.objects.filter(data__in=[s, s1, s2]), [ + "m", + ], + lambda m: m.name, + ) + self.assertRaises(TypeError, lambda: MyModel.objects.filter(data__lt=s)) + + # Serialization works, too. + stream = serializers.serialize("json", MyModel.objects.all()) + self.assertEqual(stream, '[{"pk": 1, "model": "field_subclassing.mymodel", "fields": {"data": "12", "name": "m"}}]') + + obj = list(serializers.deserialize("json", stream))[0] + self.assertEqual(obj.object, m) + + # Test retrieving custom field data + m.delete() + + m1 = MyModel.objects.create(name="1", data=Small(1, 2)) + m2 = MyModel.objects.create(name="2", data=Small(2, 3)) + + self.assertQuerysetEqual( + MyModel.objects.all(), [ + "12", + "23", + ], + lambda m: str(m.data) + ) From ae26534567ac638e08fb7e69ff3661db3288e02a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:10:54 +0000 Subject: [PATCH 154/902] [1.2.X] Migrate the files doctests. Thanks to Alex Gaynor. Backport of r13781 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13798 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/files/models.py | 124 +------------------------------ tests/modeltests/files/tests.py | 100 +++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 122 deletions(-) create mode 100644 tests/modeltests/files/tests.py diff --git a/tests/modeltests/files/models.py b/tests/modeltests/files/models.py index 67c27b54b574..f798f74df9e3 100644 --- a/tests/modeltests/files/models.py +++ b/tests/modeltests/files/models.py @@ -7,10 +7,12 @@ import random import tempfile + from django.db import models from django.core.files.base import ContentFile from django.core.files.storage import FileSystemStorage + temp_storage_location = tempfile.mkdtemp() temp_storage = FileSystemStorage(location=temp_storage_location) @@ -30,125 +32,3 @@ def random_upload_to(self, filename): custom = models.FileField(storage=temp_storage, upload_to=custom_upload_to) random = models.FileField(storage=temp_storage, upload_to=random_upload_to) default = models.FileField(storage=temp_storage, upload_to='tests', default='tests/default.txt') - -__test__ = {'API_TESTS':""" -# Attempting to access a FileField from the class raises a descriptive error ->>> Storage.normal -Traceback (most recent call last): -... -AttributeError: The 'normal' attribute can only be accessed from Storage instances. - -# An object without a file has limited functionality. - ->>> obj1 = Storage() ->>> obj1.normal - ->>> obj1.normal.size -Traceback (most recent call last): -... -ValueError: The 'normal' attribute has no file associated with it. - -# Saving a file enables full functionality. - ->>> obj1.normal.save('django_test.txt', ContentFile('content')) ->>> obj1.normal - ->>> obj1.normal.size -7 ->>> obj1.normal.read() -'content' - -# File objects can be assigned to FileField attributes, but shouldn't get -# committed until the model it's attached to is saved. - ->>> from django.core.files.uploadedfile import SimpleUploadedFile ->>> obj1.normal = SimpleUploadedFile('assignment.txt', 'content') ->>> dirs, files = temp_storage.listdir('tests') ->>> dirs -[] ->>> files.sort() ->>> files == ['default.txt', 'django_test.txt'] -True - ->>> obj1.save() ->>> dirs, files = temp_storage.listdir('tests') ->>> files.sort() ->>> files == ['assignment.txt', 'default.txt', 'django_test.txt'] -True - -# Files can be read in a little at a time, if necessary. - ->>> obj1.normal.open() ->>> obj1.normal.read(3) -'con' ->>> obj1.normal.read() -'tent' ->>> '-'.join(obj1.normal.chunks(chunk_size=2)) -'co-nt-en-t' - -# Save another file with the same name. - ->>> obj2 = Storage() ->>> obj2.normal.save('django_test.txt', ContentFile('more content')) ->>> obj2.normal - ->>> obj2.normal.size -12 - -# Push the objects into the cache to make sure they pickle properly - ->>> from django.core.cache import cache ->>> cache.set('obj1', obj1) ->>> cache.set('obj2', obj2) ->>> cache.get('obj2').normal - - -# Deleting an object deletes the file it uses, if there are no other objects -# still using that file. - ->>> obj2.delete() ->>> obj2.normal.save('django_test.txt', ContentFile('more content')) ->>> obj2.normal - - -# Multiple files with the same name get _N appended to them. - ->>> objs = [Storage() for i in range(3)] ->>> for o in objs: -... o.normal.save('multiple_files.txt', ContentFile('Same Content')) ->>> [o.normal for o in objs] -[, , ] ->>> for o in objs: -... o.delete() - -# Default values allow an object to access a single file. - ->>> obj3 = Storage.objects.create() ->>> obj3.default - ->>> obj3.default.read() -'default content' - -# But it shouldn't be deleted, even if there are no more objects using it. - ->>> obj3.delete() ->>> obj3 = Storage() ->>> obj3.default.read() -'default content' - -# Verify the fix for #5655, making sure the directory is only determined once. - ->>> obj4 = Storage() ->>> obj4.random.save('random_file', ContentFile('random content')) ->>> obj4.random - - -# Clean up the temporary files and dir. ->>> obj1.normal.delete() ->>> obj2.normal.delete() ->>> obj3.default.delete() ->>> obj4.random.delete() - ->>> import shutil ->>> shutil.rmtree(temp_storage_location) -"""} diff --git a/tests/modeltests/files/tests.py b/tests/modeltests/files/tests.py new file mode 100644 index 000000000000..025fcc574a5c --- /dev/null +++ b/tests/modeltests/files/tests.py @@ -0,0 +1,100 @@ +import shutil + +from django.core.cache import cache +from django.core.files.base import ContentFile +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase + +from models import Storage, temp_storage, temp_storage_location + + +class FileTests(TestCase): + def tearDown(self): + shutil.rmtree(temp_storage_location) + + def test_files(self): + # Attempting to access a FileField from the class raises a descriptive + # error + self.assertRaises(AttributeError, lambda: Storage.normal) + + # An object without a file has limited functionality. + obj1 = Storage() + self.assertEqual(obj1.normal.name, "") + self.assertRaises(ValueError, lambda: obj1.normal.size) + + # Saving a file enables full functionality. + obj1.normal.save("django_test.txt", ContentFile("content")) + self.assertEqual(obj1.normal.name, "tests/django_test.txt") + self.assertEqual(obj1.normal.size, 7) + self.assertEqual(obj1.normal.read(), "content") + + # File objects can be assigned to FileField attributes, but shouldn't + # get committed until the model it's attached to is saved. + obj1.normal = SimpleUploadedFile("assignment.txt", "content") + dirs, files = temp_storage.listdir("tests") + self.assertEqual(dirs, []) + self.assertEqual(sorted(files), ["default.txt", "django_test.txt"]) + + obj1.save() + dirs, files = temp_storage.listdir("tests") + self.assertEqual( + sorted(files), ["assignment.txt", "default.txt", "django_test.txt"] + ) + + # Files can be read in a little at a time, if necessary. + obj1.normal.open() + self.assertEqual(obj1.normal.read(3), "con") + self.assertEqual(obj1.normal.read(), "tent") + self.assertEqual(list(obj1.normal.chunks(chunk_size=2)), ["co", "nt", "en", "t"]) + + # Save another file with the same name. + obj2 = Storage() + obj2.normal.save("django_test.txt", ContentFile("more content")) + self.assertEqual(obj2.normal.name, "tests/django_test_1.txt") + self.assertEqual(obj2.normal.size, 12) + + # Push the objects into the cache to make sure they pickle properly + cache.set("obj1", obj1) + cache.set("obj2", obj2) + self.assertEqual(cache.get("obj2").normal.name, "tests/django_test_1.txt") + + # Deleting an object deletes the file it uses, if there are no other + # objects still using that file. + obj2.delete() + obj2.normal.save("django_test.txt", ContentFile("more content")) + self.assertEqual(obj2.normal.name, "tests/django_test_1.txt") + + # Multiple files with the same name get _N appended to them. + objs = [Storage() for i in range(3)] + for o in objs: + o.normal.save("multiple_files.txt", ContentFile("Same Content")) + self.assertEqual( + [o.normal.name for o in objs], + ["tests/multiple_files.txt", "tests/multiple_files_1.txt", "tests/multiple_files_2.txt"] + ) + for o in objs: + o.delete() + + # Default values allow an object to access a single file. + obj3 = Storage.objects.create() + self.assertEqual(obj3.default.name, "tests/default.txt") + self.assertEqual(obj3.default.read(), "default content") + + # But it shouldn't be deleted, even if there are no more objects using + # it. + obj3.delete() + obj3 = Storage() + self.assertEqual(obj3.default.read(), "default content") + + # Verify the fix for #5655, making sure the directory is only + # determined once. + obj4 = Storage() + obj4.random.save("random_file", ContentFile("random content")) + self.assertTrue(obj4.random.name.endswith("/random_file")) + + # Clean up the temporary files and dir. + obj1.normal.delete() + obj2.normal.delete() + obj3.default.delete() + obj4.random.delete() + From 5b2ec255e6558d0c6d27e1a566b28e7bb4ce6368 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:11:06 +0000 Subject: [PATCH 155/902] [1.2.X] Migrated the force_insert_update tests. Thanks to Alex Gaynor. Backport of r13782 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13799 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../modeltests/force_insert_update/models.py | 51 ------------------- tests/modeltests/force_insert_update/tests.py | 38 ++++++++++++++ 2 files changed, 38 insertions(+), 51 deletions(-) create mode 100644 tests/modeltests/force_insert_update/tests.py diff --git a/tests/modeltests/force_insert_update/models.py b/tests/modeltests/force_insert_update/models.py index 2489740e9875..9516be77186e 100644 --- a/tests/modeltests/force_insert_update/models.py +++ b/tests/modeltests/force_insert_update/models.py @@ -11,54 +11,3 @@ class Counter(models.Model): class WithCustomPK(models.Model): name = models.IntegerField(primary_key=True) value = models.IntegerField() - -__test__ = {"API_TESTS": """ ->>> c = Counter.objects.create(name="one", value=1) - -# The normal case ->>> c.value = 2 ->>> c.save() - -# Same thing, via an update ->>> c.value = 3 ->>> c.save(force_update=True) - -# Won't work because force_update and force_insert are mutually exclusive ->>> c.value = 4 ->>> c.save(force_insert=True, force_update=True) -Traceback (most recent call last): -... -ValueError: Cannot force both insert and updating in model saving. - -# Try to update something that doesn't have a primary key in the first place. ->>> c1 = Counter(name="two", value=2) ->>> c1.save(force_update=True) -Traceback (most recent call last): -... -ValueError: Cannot force an update in save() with no primary key. - ->>> c1.save(force_insert=True) - -# Won't work because we can't insert a pk of the same value. ->>> sid = transaction.savepoint() ->>> c.value = 5 ->>> try: -... c.save(force_insert=True) -... except Exception, e: -... if isinstance(e, IntegrityError): -... print "Pass" -... else: -... print "Fail with %s" % type(e) -Pass ->>> transaction.savepoint_rollback(sid) - -# Trying to update should still fail, even with manual primary keys, if the -# data isn't in the database already. ->>> obj = WithCustomPK(name=1, value=1) ->>> obj.save(force_update=True) -Traceback (most recent call last): -... -DatabaseError: ... - -""" -} diff --git a/tests/modeltests/force_insert_update/tests.py b/tests/modeltests/force_insert_update/tests.py new file mode 100644 index 000000000000..bd3eb7dcf63a --- /dev/null +++ b/tests/modeltests/force_insert_update/tests.py @@ -0,0 +1,38 @@ +from django.db import transaction, IntegrityError, DatabaseError +from django.test import TestCase + +from models import Counter, WithCustomPK + + +class ForceTests(TestCase): + def test_force_update(self): + c = Counter.objects.create(name="one", value=1) + # The normal case + + c.value = 2 + c.save() + # Same thing, via an update + c.value = 3 + c.save(force_update=True) + + # Won't work because force_update and force_insert are mutually + # exclusive + c.value = 4 + self.assertRaises(ValueError, c.save, force_insert=True, force_update=True) + + # Try to update something that doesn't have a primary key in the first + # place. + c1 = Counter(name="two", value=2) + self.assertRaises(ValueError, c1.save, force_update=True) + c1.save(force_insert=True) + + # Won't work because we can't insert a pk of the same value. + sid = transaction.savepoint() + c.value = 5 + self.assertRaises(IntegrityError, c.save, force_insert=True) + transaction.savepoint_rollback(sid) + + # Trying to update should still fail, even with manual primary keys, if + # the data isn't in the database already. + obj = WithCustomPK(name=1, value=1) + self.assertRaises(DatabaseError, obj.save, force_update=True) From 43951647659ce0f908f7c02c6d3f4f32c5b2520a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:11:17 +0000 Subject: [PATCH 156/902] [1.2.X] Migrate get_latest doctests. Thanks to Alex Gaynor. Backport of r13783 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13800 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/get_latest/models.py | 49 ------------------------- tests/modeltests/get_latest/tests.py | 53 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 49 deletions(-) create mode 100644 tests/modeltests/get_latest/tests.py diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py index 624f3a879ae0..1eeb29926754 100644 --- a/tests/modeltests/get_latest/models.py +++ b/tests/modeltests/get_latest/models.py @@ -28,52 +28,3 @@ class Person(models.Model): def __unicode__(self): return self.name - -__test__ = {'API_TESTS':""" -# Because no Articles exist yet, latest() raises ArticleDoesNotExist. ->>> Article.objects.latest() -Traceback (most recent call last): - ... -DoesNotExist: Article matching query does not exist. - -# Create a couple of Articles. ->>> from datetime import datetime ->>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26), expire_date=datetime(2005, 9, 1)) ->>> a1.save() ->>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27), expire_date=datetime(2005, 7, 28)) ->>> a2.save() ->>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27), expire_date=datetime(2005, 8, 27)) ->>> a3.save() ->>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28), expire_date=datetime(2005, 7, 30)) ->>> a4.save() - -# Get the latest Article. ->>> Article.objects.latest() - - -# Get the latest Article that matches certain filters. ->>> Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest() - - -# Pass a custom field name to latest() to change the field that's used to -# determine the latest object. ->>> Article.objects.latest('expire_date') - - ->>> Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date') - - -# You can still use latest() with a model that doesn't have "get_latest_by" -# set -- just pass in the field name manually. ->>> p1 = Person(name='Ralph', birthday=datetime(1950, 1, 1)) ->>> p1.save() ->>> p2 = Person(name='Stephanie', birthday=datetime(1960, 2, 3)) ->>> p2.save() ->>> Person.objects.latest() -Traceback (most recent call last): - ... -AssertionError: latest() requires either a field_name parameter or 'get_latest_by' in the model - ->>> Person.objects.latest('birthday') - -"""} diff --git a/tests/modeltests/get_latest/tests.py b/tests/modeltests/get_latest/tests.py new file mode 100644 index 000000000000..3c3588bba04e --- /dev/null +++ b/tests/modeltests/get_latest/tests.py @@ -0,0 +1,53 @@ +from datetime import datetime + +from django.test import TestCase + +from models import Article, Person + + +class LatestTests(TestCase): + def test_latest(self): + # Because no Articles exist yet, latest() raises ArticleDoesNotExist. + self.assertRaises(Article.DoesNotExist, Article.objects.latest) + + a1 = Article.objects.create( + headline="Article 1", pub_date=datetime(2005, 7, 26), + expire_date=datetime(2005, 9, 1) + ) + a2 = Article.objects.create( + headline="Article 2", pub_date=datetime(2005, 7, 27), + expire_date=datetime(2005, 7, 28) + ) + a3 = Article.objects.create( + headline="Article 3", pub_date=datetime(2005, 7, 27), + expire_date=datetime(2005, 8, 27) + ) + a4 = Article.objects.create( + headline="Article 4", pub_date=datetime(2005, 7, 28), + expire_date=datetime(2005, 7, 30) + ) + + # Get the latest Article. + self.assertEqual(Article.objects.latest(), a4) + # Get the latest Article that matches certain filters. + self.assertEqual( + Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest(), + a1 + ) + + # Pass a custom field name to latest() to change the field that's used + # to determine the latest object. + self.assertEqual(Article.objects.latest('expire_date'), a1) + self.assertEqual( + Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date'), + a3, + ) + + def test_latest_manual(self): + # You can still use latest() with a model that doesn't have + # "get_latest_by" set -- just pass in the field name manually. + p1 = Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1)) + p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3)) + self.assertRaises(AssertionError, Person.objects.latest) + + self.assertEqual(Person.objects.latest("birthday"), p2) From f0835cecf8aa623be0ce4c660d1b201fbaee7360 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:11:30 +0000 Subject: [PATCH 157/902] [1.2.X] Migrated get_object_or_404 doctests. Thanks to Alex Gaynor. Backport of r13784 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13801 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/get_object_or_404/models.py | 73 ------------------ tests/modeltests/get_object_or_404/tests.py | 80 ++++++++++++++++++++ 2 files changed, 80 insertions(+), 73 deletions(-) create mode 100644 tests/modeltests/get_object_or_404/tests.py diff --git a/tests/modeltests/get_object_or_404/models.py b/tests/modeltests/get_object_or_404/models.py index b2812e61e77c..eb3cd8254dea 100644 --- a/tests/modeltests/get_object_or_404/models.py +++ b/tests/modeltests/get_object_or_404/models.py @@ -32,76 +32,3 @@ class Article(models.Model): def __unicode__(self): return self.title - -__test__ = {'API_TESTS':""" -# Create some Authors. ->>> a = Author.objects.create(name="Brave Sir Robin") ->>> a.save() ->>> a2 = Author.objects.create(name="Patsy") ->>> a2.save() - -# No Articles yet, so we should get a Http404 error. ->>> get_object_or_404(Article, title="Foo") -Traceback (most recent call last): -... -Http404: No Article matches the given query. - -# Create an Article. ->>> article = Article.objects.create(title="Run away!") ->>> article.authors = [a, a2] ->>> article.save() - -# get_object_or_404 can be passed a Model to query. ->>> get_object_or_404(Article, title__contains="Run") - - -# We can also use the Article manager through an Author object. ->>> get_object_or_404(a.article_set, title__contains="Run") - - -# No articles containing "Camelot". This should raise a Http404 error. ->>> get_object_or_404(a.article_set, title__contains="Camelot") -Traceback (most recent call last): -... -Http404: No Article matches the given query. - -# Custom managers can be used too. ->>> get_object_or_404(Article.by_a_sir, title="Run away!") - - -# QuerySets can be used too. ->>> get_object_or_404(Article.objects.all(), title__contains="Run") - - -# Just as when using a get() lookup, you will get an error if more than one -# object is returned. ->>> get_object_or_404(Author.objects.all()) -Traceback (most recent call last): -... -MultipleObjectsReturned: get() returned more than one Author -- it returned ...! Lookup parameters were {} - -# Using an EmptyQuerySet raises a Http404 error. ->>> get_object_or_404(Article.objects.none(), title__contains="Run") -Traceback (most recent call last): -... -Http404: No Article matches the given query. - -# get_list_or_404 can be used to get lists of objects ->>> get_list_or_404(a.article_set, title__icontains='Run') -[] - -# Http404 is returned if the list is empty. ->>> get_list_or_404(a.article_set, title__icontains='Shrubbery') -Traceback (most recent call last): -... -Http404: No Article matches the given query. - -# Custom managers can be used too. ->>> get_list_or_404(Article.by_a_sir, title__icontains="Run") -[] - -# QuerySets can be used too. ->>> get_list_or_404(Article.objects.all(), title__icontains="Run") -[] - -"""} diff --git a/tests/modeltests/get_object_or_404/tests.py b/tests/modeltests/get_object_or_404/tests.py new file mode 100644 index 000000000000..b8c4f7510b25 --- /dev/null +++ b/tests/modeltests/get_object_or_404/tests.py @@ -0,0 +1,80 @@ +from django.http import Http404 +from django.shortcuts import get_object_or_404, get_list_or_404 +from django.test import TestCase + +from models import Author, Article + + +class GetObjectOr404Tests(TestCase): + def test_get_object_or_404(self): + a1 = Author.objects.create(name="Brave Sir Robin") + a2 = Author.objects.create(name="Patsy") + + # No Articles yet, so we should get a Http404 error. + self.assertRaises(Http404, get_object_or_404, Article, title="Foo") + + article = Article.objects.create(title="Run away!") + article.authors = [a1, a2] + # get_object_or_404 can be passed a Model to query. + self.assertEqual( + get_object_or_404(Article, title__contains="Run"), + article + ) + + # We can also use the Article manager through an Author object. + self.assertEqual( + get_object_or_404(a1.article_set, title__contains="Run"), + article + ) + + # No articles containing "Camelot". This should raise a Http404 error. + self.assertRaises(Http404, + get_object_or_404, a1.article_set, title__contains="Camelot" + ) + + # Custom managers can be used too. + self.assertEqual( + get_object_or_404(Article.by_a_sir, title="Run away!"), + article + ) + + # QuerySets can be used too. + self.assertEqual( + get_object_or_404(Article.objects.all(), title__contains="Run"), + article + ) + + # Just as when using a get() lookup, you will get an error if more than + # one object is returned. + + self.assertRaises(Author.MultipleObjectsReturned, + get_object_or_404, Author.objects.all() + ) + + # Using an EmptyQuerySet raises a Http404 error. + self.assertRaises(Http404, + get_object_or_404, Article.objects.none(), title__contains="Run" + ) + + # get_list_or_404 can be used to get lists of objects + self.assertEqual( + get_list_or_404(a1.article_set, title__icontains="Run"), + [article] + ) + + # Http404 is returned if the list is empty. + self.assertRaises(Http404, + get_list_or_404, a1.article_set, title__icontains="Shrubbery" + ) + + # Custom managers can be used too. + self.assertEqual( + get_list_or_404(Article.by_a_sir, title__icontains="Run"), + [article] + ) + + # QuerySets can be used too. + self.assertEqual( + get_list_or_404(Article.objects.all(), title__icontains="Run"), + [article] + ) From 0023357eb0979fa75aceb131fa6dc03c007b4dbf Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:11:41 +0000 Subject: [PATCH 158/902] [1.2.X] Migrate get_or_create doctests. Thanks to Alex Gaynor. Backport of r13785 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13802 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/get_or_create/models.py | 62 ------------------------ tests/modeltests/get_or_create/tests.py | 52 ++++++++++++++++++++ 2 files changed, 52 insertions(+), 62 deletions(-) create mode 100644 tests/modeltests/get_or_create/tests.py diff --git a/tests/modeltests/get_or_create/models.py b/tests/modeltests/get_or_create/models.py index 56baa5c1ed1b..db5719b79e6c 100644 --- a/tests/modeltests/get_or_create/models.py +++ b/tests/modeltests/get_or_create/models.py @@ -19,65 +19,3 @@ def __unicode__(self): class ManualPrimaryKeyTest(models.Model): id = models.IntegerField(primary_key=True) data = models.CharField(max_length=100) - -__test__ = {'API_TESTS':""" -# Acting as a divine being, create an Person. ->>> from datetime import date ->>> p = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)) ->>> p.save() - -# Only one Person is in the database at this point. ->>> Person.objects.count() -1 - -# get_or_create() a person with similar first names. ->>> p, created = Person.objects.get_or_create(first_name='John', last_name='Lennon', defaults={'birthday': date(1940, 10, 9)}) - -# get_or_create() didn't have to create an object. ->>> created -False - -# There's still only one Person in the database. ->>> Person.objects.count() -1 - -# get_or_create() a Person with a different name. ->>> p, created = Person.objects.get_or_create(first_name='George', last_name='Harrison', defaults={'birthday': date(1943, 2, 25)}) ->>> created -True ->>> Person.objects.count() -2 - -# If we execute the exact same statement, it won't create a Person. ->>> p, created = Person.objects.get_or_create(first_name='George', last_name='Harrison', defaults={'birthday': date(1943, 2, 25)}) ->>> created -False ->>> Person.objects.count() -2 - -# If you don't specify a value or default value for all required fields, you -# will get an error. ->>> try: -... p, created = Person.objects.get_or_create(first_name='Tom', last_name='Smith') -... except Exception, e: -... if isinstance(e, IntegrityError): -... print "Pass" -... else: -... print "Fail with %s" % type(e) -Pass - -# If you specify an existing primary key, but different other fields, then you -# will get an error and data will not be updated. ->>> m = ManualPrimaryKeyTest(id=1, data='Original') ->>> m.save() ->>> try: -... m, created = ManualPrimaryKeyTest.objects.get_or_create(id=1, data='Different') -... except Exception, e: -... if isinstance(e, IntegrityError): -... print "Pass" -... else: -... print "Fail with %s" % type(e) -Pass ->>> ManualPrimaryKeyTest.objects.get(id=1).data == 'Original' -True -"""} diff --git a/tests/modeltests/get_or_create/tests.py b/tests/modeltests/get_or_create/tests.py new file mode 100644 index 000000000000..3323c88a8205 --- /dev/null +++ b/tests/modeltests/get_or_create/tests.py @@ -0,0 +1,52 @@ +from datetime import date + +from django.db import IntegrityError +from django.test import TestCase + +from models import Person, ManualPrimaryKeyTest + + +class GetOrCreateTests(TestCase): + def test_get_or_create(self): + p = Person.objects.create( + first_name='John', last_name='Lennon', birthday=date(1940, 10, 9) + ) + + p, created = Person.objects.get_or_create( + first_name="John", last_name="Lennon", defaults={ + "birthday": date(1940, 10, 9) + } + ) + self.assertFalse(created) + self.assertEqual(Person.objects.count(), 1) + + p, created = Person.objects.get_or_create( + first_name='George', last_name='Harrison', defaults={ + 'birthday': date(1943, 2, 25) + } + ) + self.assertTrue(created) + self.assertEqual(Person.objects.count(), 2) + + # If we execute the exact same statement, it won't create a Person. + p, created = Person.objects.get_or_create( + first_name='George', last_name='Harrison', defaults={ + 'birthday': date(1943, 2, 25) + } + ) + self.assertFalse(created) + self.assertEqual(Person.objects.count(), 2) + + # If you don't specify a value or default value for all required + # fields, you will get an error. + self.assertRaises(IntegrityError, + Person.objects.get_or_create, first_name="Tom", last_name="Smith" + ) + + # If you specify an existing primary key, but different other fields, + # then you will get an error and data will not be updated. + m = ManualPrimaryKeyTest.objects.create(id=1, data="Original") + self.assertRaises(IntegrityError, + ManualPrimaryKeyTest.objects.get_or_create, id=1, data="Different" + ) + self.assertEqual(ManualPrimaryKeyTest.objects.get(id=1).data, "Original") From 42f7bbda1abf6c79dd28f207040a94be8d45364e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:11:52 +0000 Subject: [PATCH 159/902] [1.2.X] Migrate m2m_and_m2o doctests. Thanks to Alex Gaynor. Backport of r13786 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13803 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/m2m_and_m2o/models.py | 44 --------------- tests/modeltests/m2m_and_m2o/tests.py | 75 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 tests/modeltests/m2m_and_m2o/tests.py diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py index 0ab7a72d5739..0fea1a2e7b9e 100644 --- a/tests/modeltests/m2m_and_m2o/models.py +++ b/tests/modeltests/m2m_and_m2o/models.py @@ -19,47 +19,3 @@ def __unicode__(self): class Meta: ordering = ('num',) - - -__test__ = {'API_TESTS':""" ->>> Issue.objects.all() -[] ->>> r = User(username='russell') ->>> r.save() ->>> g = User(username='gustav') ->>> g.save() - ->>> i = Issue(num=1) ->>> i.client = r ->>> i.save() - ->>> i2 = Issue(num=2) ->>> i2.client = r ->>> i2.save() ->>> i2.cc.add(r) - ->>> i3 = Issue(num=3) ->>> i3.client = g ->>> i3.save() ->>> i3.cc.add(r) - ->>> from django.db.models.query import Q - ->>> Issue.objects.filter(client=r.id) -[, ] ->>> Issue.objects.filter(client=g.id) -[] ->>> Issue.objects.filter(cc__id__exact=g.id) -[] ->>> Issue.objects.filter(cc__id__exact=r.id) -[, ] - -# These queries combine results from the m2m and the m2o relationships. -# They're three ways of saying the same thing. ->>> Issue.objects.filter(Q(cc__id__exact=r.id) | Q(client=r.id)) -[, , ] ->>> Issue.objects.filter(cc__id__exact=r.id) | Issue.objects.filter(client=r.id) -[, , ] ->>> Issue.objects.filter(Q(client=r.id) | Q(cc__id__exact=r.id)) -[, , ] -"""} diff --git a/tests/modeltests/m2m_and_m2o/tests.py b/tests/modeltests/m2m_and_m2o/tests.py new file mode 100644 index 000000000000..113f2a2a48d5 --- /dev/null +++ b/tests/modeltests/m2m_and_m2o/tests.py @@ -0,0 +1,75 @@ +from django.db.models import Q +from django.test import TestCase + +from models import Issue, User + + +class RelatedObjectTests(TestCase): + def test_m2m_and_m2o(self): + r = User.objects.create(username="russell") + g = User.objects.create(username="gustav") + + i1 = Issue(num=1) + i1.client = r + i1.save() + + i2 = Issue(num=2) + i2.client = r + i2.save() + i2.cc.add(r) + + i3 = Issue(num=3) + i3.client = g + i3.save() + i3.cc.add(r) + + self.assertQuerysetEqual( + Issue.objects.filter(client=r.id), [ + 1, + 2, + ], + lambda i: i.num + ) + self.assertQuerysetEqual( + Issue.objects.filter(client=g.id), [ + 3, + ], + lambda i: i.num + ) + self.assertQuerysetEqual( + Issue.objects.filter(cc__id__exact=g.id), [] + ) + self.assertQuerysetEqual( + Issue.objects.filter(cc__id__exact=r.id), [ + 2, + 3, + ], + lambda i: i.num + ) + + # These queries combine results from the m2m and the m2o relationships. + # They're three ways of saying the same thing. + self.assertQuerysetEqual( + Issue.objects.filter(Q(cc__id__exact = r.id) | Q(client=r.id)), [ + 1, + 2, + 3, + ], + lambda i: i.num + ) + self.assertQuerysetEqual( + Issue.objects.filter(cc__id__exact=r.id) | Issue.objects.filter(client=r.id), [ + 1, + 2, + 3, + ], + lambda i: i.num + ) + self.assertQuerysetEqual( + Issue.objects.filter(Q(client=r.id) | Q(cc__id__exact=r.id)), [ + 1, + 2, + 3, + ], + lambda i: i.num + ) From a4a19b81c394ddaa986bee12bad0fc359aa7aaf6 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:12:02 +0000 Subject: [PATCH 160/902] [1.2.X] Migrated m2m_intermediary doctests. Thanks to Alex Gaynor. Backport of r13787 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13804 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/m2m_and_m2o/tests.py | 10 +++--- tests/modeltests/m2m_intermediary/models.py | 32 ----------------- tests/modeltests/m2m_intermediary/tests.py | 38 +++++++++++++++++++++ 3 files changed, 43 insertions(+), 37 deletions(-) create mode 100644 tests/modeltests/m2m_intermediary/tests.py diff --git a/tests/modeltests/m2m_and_m2o/tests.py b/tests/modeltests/m2m_and_m2o/tests.py index 113f2a2a48d5..dedf9cdf2645 100644 --- a/tests/modeltests/m2m_and_m2o/tests.py +++ b/tests/modeltests/m2m_and_m2o/tests.py @@ -8,21 +8,21 @@ class RelatedObjectTests(TestCase): def test_m2m_and_m2o(self): r = User.objects.create(username="russell") g = User.objects.create(username="gustav") - + i1 = Issue(num=1) i1.client = r i1.save() - + i2 = Issue(num=2) i2.client = r i2.save() i2.cc.add(r) - + i3 = Issue(num=3) i3.client = g i3.save() i3.cc.add(r) - + self.assertQuerysetEqual( Issue.objects.filter(client=r.id), [ 1, @@ -46,7 +46,7 @@ def test_m2m_and_m2o(self): ], lambda i: i.num ) - + # These queries combine results from the m2m and the m2o relationships. # They're three ways of saying the same thing. self.assertQuerysetEqual( diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py index e9f964aa4eb2..8042a52b3847 100644 --- a/tests/modeltests/m2m_intermediary/models.py +++ b/tests/modeltests/m2m_intermediary/models.py @@ -34,35 +34,3 @@ class Writer(models.Model): def __unicode__(self): return u'%s (%s)' % (self.reporter, self.position) -__test__ = {'API_TESTS':""" -# Create a few Reporters. ->>> r1 = Reporter(first_name='John', last_name='Smith') ->>> r1.save() ->>> r2 = Reporter(first_name='Jane', last_name='Doe') ->>> r2.save() - -# Create an Article. ->>> from datetime import datetime ->>> a = Article(headline='This is a test', pub_date=datetime(2005, 7, 27)) ->>> a.save() - -# Create a few Writers. ->>> w1 = Writer(reporter=r1, article=a, position='Main writer') ->>> w1.save() ->>> w2 = Writer(reporter=r2, article=a, position='Contributor') ->>> w2.save() - -# Play around with the API. ->>> a.writer_set.select_related().order_by('-position') -[, ] ->>> w1.reporter - ->>> w2.reporter - ->>> w1.article - ->>> w2.article - ->>> r1.writer_set.all() -[] -"""} diff --git a/tests/modeltests/m2m_intermediary/tests.py b/tests/modeltests/m2m_intermediary/tests.py new file mode 100644 index 000000000000..5f357412a5e5 --- /dev/null +++ b/tests/modeltests/m2m_intermediary/tests.py @@ -0,0 +1,38 @@ +from datetime import datetime + +from django.test import TestCase + +from models import Reporter, Article, Writer + + +class M2MIntermediaryTests(TestCase): + def test_intermeiary(self): + r1 = Reporter.objects.create(first_name="John", last_name="Smith") + r2 = Reporter.objects.create(first_name="Jane", last_name="Doe") + + a = Article.objects.create( + headline="This is a test", pub_date=datetime(2005, 7, 27) + ) + + w1 = Writer.objects.create(reporter=r1, article=a, position="Main writer") + w2 = Writer.objects.create(reporter=r2, article=a, position="Contributor") + + self.assertQuerysetEqual( + a.writer_set.select_related().order_by("-position"), [ + ("John Smith", "Main writer"), + ("Jane Doe", "Contributor"), + ], + lambda w: (unicode(w.reporter), w.position) + ) + self.assertEqual(w1.reporter, r1) + self.assertEqual(w2.reporter, r2) + + self.assertEqual(w1.article, a) + self.assertEqual(w2.article, a) + + self.assertQuerysetEqual( + r1.writer_set.all(), [ + ("John Smith", "Main writer") + ], + lambda w: (unicode(w.reporter), w.position) + ) From cbcdd408c0310ec72655e86c31481f0b27e5e23a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:12:14 +0000 Subject: [PATCH 161/902] [1.2.X] Migrated m2m_multiple doctests. Thanks to Alex Gaynor. Backport of r13789 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13805 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/m2m_multiple/models.py | 49 --------------- tests/modeltests/m2m_multiple/tests.py | 84 +++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 49 deletions(-) create mode 100644 tests/modeltests/m2m_multiple/tests.py diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/modeltests/m2m_multiple/models.py index 42e74553d96d..e53f840653f4 100644 --- a/tests/modeltests/m2m_multiple/models.py +++ b/tests/modeltests/m2m_multiple/models.py @@ -28,52 +28,3 @@ class Meta: def __unicode__(self): return self.headline -__test__ = {'API_TESTS':""" ->>> from datetime import datetime - ->>> c1 = Category(name='Sports') ->>> c1.save() ->>> c2 = Category(name='News') ->>> c2.save() ->>> c3 = Category(name='Crime') ->>> c3.save() ->>> c4 = Category(name='Life') ->>> c4.save() - ->>> a1 = Article(headline='Area man steals', pub_date=datetime(2005, 11, 27)) ->>> a1.save() ->>> a1.primary_categories.add(c2, c3) ->>> a1.secondary_categories.add(c4) - ->>> a2 = Article(headline='Area man runs', pub_date=datetime(2005, 11, 28)) ->>> a2.save() ->>> a2.primary_categories.add(c1, c2) ->>> a2.secondary_categories.add(c4) - ->>> a1.primary_categories.all() -[, ] - ->>> a2.primary_categories.all() -[, ] - ->>> a1.secondary_categories.all() -[] - - ->>> c1.primary_article_set.all() -[] ->>> c1.secondary_article_set.all() -[] ->>> c2.primary_article_set.all() -[, ] ->>> c2.secondary_article_set.all() -[] ->>> c3.primary_article_set.all() -[] ->>> c3.secondary_article_set.all() -[] ->>> c4.primary_article_set.all() -[] ->>> c4.secondary_article_set.all() -[, ] -"""} diff --git a/tests/modeltests/m2m_multiple/tests.py b/tests/modeltests/m2m_multiple/tests.py new file mode 100644 index 000000000000..1f4503a483c6 --- /dev/null +++ b/tests/modeltests/m2m_multiple/tests.py @@ -0,0 +1,84 @@ +from datetime import datetime + +from django.test import TestCase + +from models import Article, Category + + +class M2MMultipleTests(TestCase): + def test_multiple(self): + c1, c2, c3, c4 = [ + Category.objects.create(name=name) + for name in ["Sports", "News", "Crime", "Life"] + ] + + a1 = Article.objects.create( + headline="Area man steals", pub_date=datetime(2005, 11, 27) + ) + a1.primary_categories.add(c2, c3) + a1.secondary_categories.add(c4) + + a2 = Article.objects.create( + headline="Area man runs", pub_date=datetime(2005, 11, 28) + ) + a2.primary_categories.add(c1, c2) + a2.secondary_categories.add(c4) + + self.assertQuerysetEqual( + a1.primary_categories.all(), [ + "Crime", + "News", + ], + lambda c: c.name + ) + self.assertQuerysetEqual( + a2.primary_categories.all(), [ + "News", + "Sports", + ], + lambda c: c.name + ) + self.assertQuerysetEqual( + a1.secondary_categories.all(), [ + "Life", + ], + lambda c: c.name + ) + self.assertQuerysetEqual( + c1.primary_article_set.all(), [ + "Area man runs", + ], + lambda a: a.headline + ) + self.assertQuerysetEqual( + c1.secondary_article_set.all(), [] + ) + self.assertQuerysetEqual( + c2.primary_article_set.all(), [ + "Area man steals", + "Area man runs", + ], + lambda a: a.headline + ) + self.assertQuerysetEqual( + c2.secondary_article_set.all(), [] + ) + self.assertQuerysetEqual( + c3.primary_article_set.all(), [ + "Area man steals", + ], + lambda a: a.headline + ) + self.assertQuerysetEqual( + c3.secondary_article_set.all(), [] + ) + self.assertQuerysetEqual( + c4.primary_article_set.all(), [] + ) + self.assertQuerysetEqual( + c4.secondary_article_set.all(), [ + "Area man steals", + "Area man runs", + ], + lambda a: a.headline + ) From 411f8dc0d62eb3e3311a504b9262ed3d0c68b482 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:12:23 +0000 Subject: [PATCH 162/902] [1.2.X] Migrated empty doctests. Thanks to Alex Gaynor. Backport of r13789 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13806 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/empty/models.py | 16 +--------------- tests/modeltests/empty/tests.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 tests/modeltests/empty/tests.py diff --git a/tests/modeltests/empty/models.py b/tests/modeltests/empty/models.py index d57087134e17..a6cdb0aa227f 100644 --- a/tests/modeltests/empty/models.py +++ b/tests/modeltests/empty/models.py @@ -7,20 +7,6 @@ from django.db import models + class Empty(models.Model): pass - -__test__ = {'API_TESTS':""" ->>> m = Empty() ->>> m.id ->>> m.save() ->>> m2 = Empty() ->>> m2.save() ->>> len(Empty.objects.all()) -2 ->>> m.id is not None -True ->>> existing = Empty(m.id) ->>> existing.save() - -"""} diff --git a/tests/modeltests/empty/tests.py b/tests/modeltests/empty/tests.py new file mode 100644 index 000000000000..01fa1c58cdc7 --- /dev/null +++ b/tests/modeltests/empty/tests.py @@ -0,0 +1,15 @@ +from django.test import TestCase + +from models import Empty + + +class EmptyModelTests(TestCase): + def test_empty(self): + m = Empty() + self.assertEqual(m.id, None) + m.save() + m2 = Empty.objects.create() + self.assertEqual(len(Empty.objects.all()), 2) + self.assertTrue(m.id is not None) + existing = Empty(m.id) + existing.save() From 6752258289a8de885688605146e1ecd983e3df56 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 20:12:33 +0000 Subject: [PATCH 163/902] [1.2.X] Migrated expressions doctests. Thanks to Alex Gaynor. Backport of r13790 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13807 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/expressions/models.py | 105 ------------ tests/modeltests/expressions/tests.py | 218 +++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 105 deletions(-) create mode 100644 tests/modeltests/expressions/tests.py diff --git a/tests/modeltests/expressions/models.py b/tests/modeltests/expressions/models.py index f6292f5d9b10..b00440853641 100644 --- a/tests/modeltests/expressions/models.py +++ b/tests/modeltests/expressions/models.py @@ -25,108 +25,3 @@ class Company(models.Model): def __unicode__(self): return self.name - - -__test__ = {'API_TESTS': """ ->>> from django.db.models import F - ->>> Company(name='Example Inc.', num_employees=2300, num_chairs=5, -... ceo=Employee.objects.create(firstname='Joe', lastname='Smith')).save() ->>> Company(name='Foobar Ltd.', num_employees=3, num_chairs=3, -... ceo=Employee.objects.create(firstname='Frank', lastname='Meyer')).save() ->>> Company(name='Test GmbH', num_employees=32, num_chairs=1, -... ceo=Employee.objects.create(firstname='Max', lastname='Mustermann')).save() - ->>> company_query = Company.objects.values('name','num_employees','num_chairs').order_by('name','num_employees','num_chairs') - -# We can filter for companies where the number of employees is greater than the -# number of chairs. ->>> company_query.filter(num_employees__gt=F('num_chairs')) -[{'num_chairs': 5, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 1, 'name': u'Test GmbH', 'num_employees': 32}] - -# We can set one field to have the value of another field -# Make sure we have enough chairs ->>> _ = company_query.update(num_chairs=F('num_employees')) ->>> company_query -[{'num_chairs': 2300, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 3, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 32, 'name': u'Test GmbH', 'num_employees': 32}] - -# We can perform arithmetic operations in expressions -# Make sure we have 2 spare chairs ->>> _ =company_query.update(num_chairs=F('num_employees')+2) ->>> company_query -[{'num_chairs': 2302, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 5, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 34, 'name': u'Test GmbH', 'num_employees': 32}] - -# Law of order of operations is followed ->>> _ =company_query.update(num_chairs=F('num_employees') + 2 * F('num_employees')) ->>> company_query -[{'num_chairs': 6900, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 9, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 96, 'name': u'Test GmbH', 'num_employees': 32}] - -# Law of order of operations can be overridden by parentheses ->>> _ =company_query.update(num_chairs=((F('num_employees') + 2) * F('num_employees'))) ->>> company_query -[{'num_chairs': 5294600, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 15, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 1088, 'name': u'Test GmbH', 'num_employees': 32}] - -# The relation of a foreign key can become copied over to an other foreign key. ->>> Company.objects.update(point_of_contact=F('ceo')) -3 - ->>> [c.point_of_contact for c in Company.objects.all()] -[, , ] - ->>> c = Company.objects.all()[0] ->>> c.point_of_contact = Employee.objects.create(firstname="Guido", lastname="van Rossum") ->>> c.save() - -# F Expressions can also span joins ->>> Company.objects.filter(ceo__firstname=F('point_of_contact__firstname')).distinct().order_by('name') -[, ] - ->>> _ = Company.objects.exclude(ceo__firstname=F('point_of_contact__firstname')).update(name='foo') ->>> Company.objects.exclude(ceo__firstname=F('point_of_contact__firstname')).get().name -u'foo' - ->>> _ = Company.objects.exclude(ceo__firstname=F('point_of_contact__firstname')).update(name=F('point_of_contact__lastname')) -Traceback (most recent call last): -... -FieldError: Joined field references are not permitted in this query - -# F expressions can be used to update attributes on single objects ->>> test_gmbh = Company.objects.get(name='Test GmbH') ->>> test_gmbh.num_employees -32 ->>> test_gmbh.num_employees = F('num_employees') + 4 ->>> test_gmbh.save() ->>> test_gmbh = Company.objects.get(pk=test_gmbh.pk) ->>> test_gmbh.num_employees -36 - -# F expressions cannot be used to update attributes which are foreign keys, or -# attributes which involve joins. ->>> test_gmbh.point_of_contact = None ->>> test_gmbh.save() ->>> test_gmbh.point_of_contact is None -True ->>> test_gmbh.point_of_contact = F('ceo') -Traceback (most recent call last): -... -ValueError: Cannot assign "": "Company.point_of_contact" must be a "Employee" instance. - ->>> test_gmbh.point_of_contact = test_gmbh.ceo ->>> test_gmbh.save() ->>> test_gmbh.name = F('ceo__last_name') ->>> test_gmbh.save() -Traceback (most recent call last): -... -FieldError: Joined field references are not permitted in this query - -# F expressions cannot be used to update attributes on objects which do not yet -# exist in the database ->>> acme = Company(name='The Acme Widget Co.', num_employees=12, num_chairs=5, -... ceo=test_gmbh.ceo) ->>> acme.num_employees = F('num_employees') + 16 ->>> acme.save() -Traceback (most recent call last): -... -TypeError: ... - -"""} diff --git a/tests/modeltests/expressions/tests.py b/tests/modeltests/expressions/tests.py new file mode 100644 index 000000000000..0a136ae5d852 --- /dev/null +++ b/tests/modeltests/expressions/tests.py @@ -0,0 +1,218 @@ +from django.core.exceptions import FieldError +from django.db.models import F +from django.test import TestCase + +from models import Company, Employee + + +class ExpressionsTests(TestCase): + def test_filter(self): + Company.objects.create( + name="Example Inc.", num_employees=2300, num_chairs=5, + ceo=Employee.objects.create(firstname="Joe", lastname="Smith") + ) + Company.objects.create( + name="Foobar Ltd.", num_employees=3, num_chairs=4, + ceo=Employee.objects.create(firstname="Frank", lastname="Meyer") + ) + Company.objects.create( + name="Test GmbH", num_employees=32, num_chairs=1, + ceo=Employee.objects.create(firstname="Max", lastname="Mustermann") + ) + + company_query = Company.objects.values( + "name", "num_employees", "num_chairs" + ).order_by( + "name", "num_employees", "num_chairs" + ) + + # We can filter for companies where the number of employees is greater + # than the number of chairs. + self.assertQuerysetEqual( + company_query.filter(num_employees__gt=F("num_chairs")), [ + { + "num_chairs": 5, + "name": "Example Inc.", + "num_employees": 2300, + }, + { + "num_chairs": 1, + "name": "Test GmbH", + "num_employees": 32 + }, + ], + lambda o: o + ) + + # We can set one field to have the value of another field + # Make sure we have enough chairs + company_query.update(num_chairs=F("num_employees")) + self.assertQuerysetEqual( + company_query, [ + { + "num_chairs": 2300, + "name": "Example Inc.", + "num_employees": 2300 + }, + { + "num_chairs": 3, + "name": "Foobar Ltd.", + "num_employees": 3 + }, + { + "num_chairs": 32, + "name": "Test GmbH", + "num_employees": 32 + } + ], + lambda o: o + ) + + # We can perform arithmetic operations in expressions + # Make sure we have 2 spare chairs + company_query.update(num_chairs=F("num_employees")+2) + self.assertQuerysetEqual( + company_query, [ + { + 'num_chairs': 2302, + 'name': u'Example Inc.', + 'num_employees': 2300 + }, + { + 'num_chairs': 5, + 'name': u'Foobar Ltd.', + 'num_employees': 3 + }, + { + 'num_chairs': 34, + 'name': u'Test GmbH', + 'num_employees': 32 + } + ], + lambda o: o, + ) + + # Law of order of operations is followed + company_query.update( + num_chairs=F('num_employees') + 2 * F('num_employees') + ) + self.assertQuerysetEqual( + company_query, [ + { + 'num_chairs': 6900, + 'name': u'Example Inc.', + 'num_employees': 2300 + }, + { + 'num_chairs': 9, + 'name': u'Foobar Ltd.', + 'num_employees': 3 + }, + { + 'num_chairs': 96, + 'name': u'Test GmbH', + 'num_employees': 32 + } + ], + lambda o: o, + ) + + # Law of order of operations can be overridden by parentheses + company_query.update( + num_chairs=((F('num_employees') + 2) * F('num_employees')) + ) + self.assertQuerysetEqual( + company_query, [ + { + 'num_chairs': 5294600, + 'name': u'Example Inc.', + 'num_employees': 2300 + }, + { + 'num_chairs': 15, + 'name': u'Foobar Ltd.', + 'num_employees': 3 + }, + { + 'num_chairs': 1088, + 'name': u'Test GmbH', + 'num_employees': 32 + } + ], + lambda o: o, + ) + + # The relation of a foreign key can become copied over to an other + # foreign key. + self.assertEqual( + Company.objects.update(point_of_contact=F('ceo')), + 3 + ) + self.assertQuerysetEqual( + Company.objects.all(), [ + "Joe Smith", + "Frank Meyer", + "Max Mustermann", + ], + lambda c: unicode(c.point_of_contact), + ) + + c = Company.objects.all()[0] + c.point_of_contact = Employee.objects.create(firstname="Guido", lastname="van Rossum") + c.save() + + # F Expressions can also span joins + self.assertQuerysetEqual( + Company.objects.filter(ceo__firstname=F("point_of_contact__firstname")), [ + "Foobar Ltd.", + "Test GmbH", + ], + lambda c: c.name + ) + + Company.objects.exclude( + ceo__firstname=F("point_of_contact__firstname") + ).update(name="foo") + self.assertEqual( + Company.objects.exclude( + ceo__firstname=F('point_of_contact__firstname') + ).get().name, + "foo", + ) + + self.assertRaises(FieldError, + lambda: Company.objects.exclude( + ceo__firstname=F('point_of_contact__firstname') + ).update(name=F('point_of_contact__lastname')) + ) + + # F expressions can be used to update attributes on single objects + test_gmbh = Company.objects.get(name="Test GmbH") + self.assertEqual(test_gmbh.num_employees, 32) + test_gmbh.num_employees = F("num_employees") + 4 + test_gmbh.save() + test_gmbh = Company.objects.get(pk=test_gmbh.pk) + self.assertEqual(test_gmbh.num_employees, 36) + + # F expressions cannot be used to update attributes which are foreign + # keys, or attributes which involve joins. + test_gmbh.point_of_contact = None + test_gmbh.save() + self.assertTrue(test_gmbh.point_of_contact is None) + def test(): + test_gmbh.point_of_contact = F("ceo") + self.assertRaises(ValueError, test) + + test_gmbh.point_of_contact = test_gmbh.ceo + test_gmbh.save() + test_gmbh.name = F("ceo__last_name") + self.assertRaises(FieldError, test_gmbh.save) + + # F expressions cannot be used to update attributes on objects which do + # not yet exist in the database + acme = Company( + name="The Acme Widget Co.", num_employees=12, num_chairs=5, + ceo=test_gmbh.ceo + ) + acme.num_employees = F("num_employees") + 16 + self.assertRaises(TypeError, acme.save) From 0c65a6f7daaffb99e8269c4884b341ee1266a102 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 12 Sep 2010 21:56:25 +0000 Subject: [PATCH 164/902] [1.2.X] Modified the egg template loader tests; firstly to ensure that we are testing the new, non-deprecated interface; secondly to ensure that the old deprecated interface is also tested, but deprecation warnings are silenced. Backport of r13812 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13813 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/templates/loaders.py | 42 +++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/tests/regressiontests/templates/loaders.py b/tests/regressiontests/templates/loaders.py index 64a0dc650520..caa3faa6bcd2 100644 --- a/tests/regressiontests/templates/loaders.py +++ b/tests/regressiontests/templates/loaders.py @@ -15,9 +15,11 @@ import imp import StringIO import os.path +import warnings from django.template import TemplateDoesNotExist, Context from django.template.loaders.eggs import load_template_source as lts_egg +from django.template.loaders.eggs import Loader as EggLoader from django.template import loader # Mock classes and objects for pkg_resources functions. @@ -53,7 +55,33 @@ def create_egg(name, resources): egg._resources = resources sys.modules[name] = egg -class EggLoader(unittest.TestCase): +class DeprecatedEggLoaderTest(unittest.TestCase): + "Test the deprecated load_template_source interface to the egg loader" + def setUp(self): + pkg_resources._provider_factories[MockLoader] = MockProvider + + self.empty_egg = create_egg("egg_empty", {}) + self.egg_1 = create_egg("egg_1", { + os.path.normcase('templates/y.html') : StringIO.StringIO("y"), + os.path.normcase('templates/x.txt') : StringIO.StringIO("x"), + }) + self._old_installed_apps = settings.INSTALLED_APPS + settings.INSTALLED_APPS = [] + warnings.simplefilter("ignore", PendingDeprecationWarning) + + def tearDown(self): + settings.INSTALLED_APPS = self._old_installed_apps + warnings.resetwarnings() + + def test_existing(self): + "A template can be loaded from an egg" + settings.INSTALLED_APPS = ['egg_1'] + contents, template_name = lts_egg("y.html") + self.assertEqual(contents, "y") + self.assertEqual(template_name, "egg:egg_1:templates/y.html") + + +class EggLoaderTest(unittest.TestCase): def setUp(self): pkg_resources._provider_factories[MockLoader] = MockProvider @@ -71,24 +99,28 @@ def tearDown(self): def test_empty(self): "Loading any template on an empty egg should fail" settings.INSTALLED_APPS = ['egg_empty'] - self.assertRaises(TemplateDoesNotExist, lts_egg, "not-existing.html") + egg_loader = EggLoader() + self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html") def test_non_existing(self): "Template loading fails if the template is not in the egg" settings.INSTALLED_APPS = ['egg_1'] - self.assertRaises(TemplateDoesNotExist, lts_egg, "not-existing.html") + egg_loader = EggLoader() + self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "not-existing.html") def test_existing(self): "A template can be loaded from an egg" settings.INSTALLED_APPS = ['egg_1'] - contents, template_name = lts_egg("y.html") + egg_loader = EggLoader() + contents, template_name = egg_loader.load_template_source("y.html") self.assertEqual(contents, "y") self.assertEqual(template_name, "egg:egg_1:templates/y.html") def test_not_installed(self): "Loading an existent template from an egg not included in INSTALLED_APPS should fail" settings.INSTALLED_APPS = [] - self.assertRaises(TemplateDoesNotExist, lts_egg, "y.html") + egg_loader = EggLoader() + self.assertRaises(TemplateDoesNotExist, egg_loader.load_template_source, "y.html") class CachedLoader(unittest.TestCase): def setUp(self): From fa35d2bd4d0c941fda8d0e54af59700589cd725f Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Sun, 12 Sep 2010 22:16:36 +0000 Subject: [PATCH 165/902] [1.2.X] Fixed the get_or_create tests for postgreSQL, by using TransactionTestCase. Backport of r13814 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13815 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/get_or_create/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/modeltests/get_or_create/tests.py b/tests/modeltests/get_or_create/tests.py index 3323c88a8205..1999b20c769e 100644 --- a/tests/modeltests/get_or_create/tests.py +++ b/tests/modeltests/get_or_create/tests.py @@ -1,12 +1,12 @@ from datetime import date from django.db import IntegrityError -from django.test import TestCase +from django.test import TransactionTestCase from models import Person, ManualPrimaryKeyTest -class GetOrCreateTests(TestCase): +class GetOrCreateTests(TransactionTestCase): def test_get_or_create(self): p = Person.objects.create( first_name='John', last_name='Lennon', birthday=date(1940, 10, 9) From 62585b7570034a8c67ab327c17a987e6c41eddb9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 13 Sep 2010 05:46:34 +0000 Subject: [PATCH 166/902] [1.2.X] Migrated user_commands doctests. Thanks to Eric Florenzano. Backport of r13823 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13832 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../management/commands/dance.py | 2 +- tests/modeltests/user_commands/models.py | 19 ----------------- tests/modeltests/user_commands/tests.py | 21 +++++++++++++++++++ 3 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 tests/modeltests/user_commands/tests.py diff --git a/tests/modeltests/user_commands/management/commands/dance.py b/tests/modeltests/user_commands/management/commands/dance.py index a504d486d68a..acefe0927158 100644 --- a/tests/modeltests/user_commands/management/commands/dance.py +++ b/tests/modeltests/user_commands/management/commands/dance.py @@ -11,4 +11,4 @@ class Command(BaseCommand): ] def handle(self, *args, **options): - print "I don't feel like dancing %s." % options["style"] + self.stdout.write("I don't feel like dancing %s." % options["style"]) diff --git a/tests/modeltests/user_commands/models.py b/tests/modeltests/user_commands/models.py index 10ccdb8e2ce5..f2aa549bb972 100644 --- a/tests/modeltests/user_commands/models.py +++ b/tests/modeltests/user_commands/models.py @@ -12,22 +12,3 @@ ``django.core.management.commands`` directory. This directory contains the definitions for the base Django ``manage.py`` commands. """ - -__test__ = {'API_TESTS': """ ->>> from django.core import management - -# Invoke a simple user-defined command ->>> management.call_command('dance', style="Jive") -I don't feel like dancing Jive. - -# Invoke a command that doesn't exist ->>> management.call_command('explode') -Traceback (most recent call last): -... -CommandError: Unknown command: 'explode' - -# Invoke a command with default option `style` ->>> management.call_command('dance') -I don't feel like dancing Rock'n'Roll. - -"""} diff --git a/tests/modeltests/user_commands/tests.py b/tests/modeltests/user_commands/tests.py new file mode 100644 index 000000000000..84aa7a53d576 --- /dev/null +++ b/tests/modeltests/user_commands/tests.py @@ -0,0 +1,21 @@ +from StringIO import StringIO + +from django.test import TestCase +from django.core import management +from django.core.management.base import CommandError + +class CommandTests(TestCase): + def test_command(self): + out = StringIO() + management.call_command('dance', stdout=out) + self.assertEquals(out.getvalue(), + "I don't feel like dancing Rock'n'Roll.") + + def test_command_style(self): + out = StringIO() + management.call_command('dance', style='Jive', stdout=out) + self.assertEquals(out.getvalue(), + "I don't feel like dancing Jive.") + + def test_explode(self): + self.assertRaises(CommandError, management.call_command, ('explode',)) \ No newline at end of file From f7bdebf2fec57a58e374161cd9522333e806c8db Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 13 Sep 2010 05:46:46 +0000 Subject: [PATCH 167/902] [1.2.X] Migrated the update doctests. Thanks to Eric Florenzano. Backport of r13824 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13833 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/update/models.py | 56 ------------------------- tests/modeltests/update/tests.py | 68 ++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/tests/modeltests/update/models.py b/tests/modeltests/update/models.py index 9ce672f2b3fc..7b633e28dce8 100644 --- a/tests/modeltests/update/models.py +++ b/tests/modeltests/update/models.py @@ -33,59 +33,3 @@ class C(models.Model): class D(C): a = models.ForeignKey(A) - -__test__ = {'API_TESTS': """ ->>> DataPoint(name="d0", value="apple").save() ->>> DataPoint(name="d2", value="banana").save() ->>> d3 = DataPoint.objects.create(name="d3", value="banana") ->>> RelatedPoint(name="r1", data=d3).save() - -Objects are updated by first filtering the candidates into a queryset and then -calling the update() method. It executes immediately and returns nothing. - ->>> DataPoint.objects.filter(value="apple").update(name="d1") -1 ->>> DataPoint.objects.filter(value="apple") -[] - -We can update multiple objects at once. - ->>> DataPoint.objects.filter(value="banana").update(value="pineapple") -2 ->>> DataPoint.objects.get(name="d2").value -u'pineapple' - -Foreign key fields can also be updated, although you can only update the object -referred to, not anything inside the related object. - ->>> d = DataPoint.objects.get(name="d1") ->>> RelatedPoint.objects.filter(name="r1").update(data=d) -1 ->>> RelatedPoint.objects.filter(data__name="d1") -[] - -Multiple fields can be updated at once - ->>> DataPoint.objects.filter(value="pineapple").update(value="fruit", another_value="peaches") -2 ->>> d = DataPoint.objects.get(name="d2") ->>> d.value, d.another_value -(u'fruit', u'peaches') - -In the rare case you want to update every instance of a model, update() is also -a manager method. - ->>> DataPoint.objects.update(value='thing') -3 ->>> DataPoint.objects.values('value').distinct() -[{'value': u'thing'}] - -We do not support update on already sliced query sets. - ->>> DataPoint.objects.all()[:2].update(another_value='another thing') -Traceback (most recent call last): - ... -AssertionError: Cannot update a query once a slice has been taken. - -""" -} diff --git a/tests/modeltests/update/tests.py b/tests/modeltests/update/tests.py index 05397f8306a5..005690d0fc5e 100644 --- a/tests/modeltests/update/tests.py +++ b/tests/modeltests/update/tests.py @@ -1,6 +1,6 @@ from django.test import TestCase -from models import A, B, D +from models import A, B, C, D, DataPoint, RelatedPoint class SimpleTest(TestCase): def setUp(self): @@ -47,3 +47,69 @@ def test_empty_update_with_inheritance(self): self.failUnlessEqual(num_updated, 0) cnt = D.objects.filter(y=100).count() self.failUnlessEqual(cnt, 0) + +class AdvancedTests(TestCase): + + def setUp(self): + self.d0 = DataPoint.objects.create(name="d0", value="apple") + self.d2 = DataPoint.objects.create(name="d2", value="banana") + self.d3 = DataPoint.objects.create(name="d3", value="banana") + self.r1 = RelatedPoint.objects.create(name="r1", data=self.d3) + + def test_update(self): + """ + Objects are updated by first filtering the candidates into a queryset + and then calling the update() method. It executes immediately and + returns nothing. + """ + resp = DataPoint.objects.filter(value="apple").update(name="d1") + self.assertEqual(resp, 1) + resp = DataPoint.objects.filter(value="apple") + self.assertEqual(list(resp), [self.d0]) + + def test_update_multiple_objects(self): + """ + We can update multiple objects at once. + """ + resp = DataPoint.objects.filter(value="banana").update( + value="pineapple") + self.assertEqual(resp, 2) + self.assertEqual(DataPoint.objects.get(name="d2").value, u'pineapple') + + def test_update_fk(self): + """ + Foreign key fields can also be updated, although you can only update + the object referred to, not anything inside the related object. + """ + resp = RelatedPoint.objects.filter(name="r1").update(data=self.d0) + self.assertEqual(resp, 1) + resp = RelatedPoint.objects.filter(data__name="d0") + self.assertEqual(list(resp), [self.r1]) + + def test_update_multiple_fields(self): + """ + Multiple fields can be updated at once + """ + resp = DataPoint.objects.filter(value="apple").update( + value="fruit", another_value="peach") + self.assertEqual(resp, 1) + d = DataPoint.objects.get(name="d0") + self.assertEqual(d.value, u'fruit') + self.assertEqual(d.another_value, u'peach') + + def test_update_all(self): + """ + In the rare case you want to update every instance of a model, update() + is also a manager method. + """ + self.assertEqual(DataPoint.objects.update(value='thing'), 3) + resp = DataPoint.objects.values('value').distinct() + self.assertEqual(list(resp), [{'value': u'thing'}]) + + def test_update_slice_fail(self): + """ + We do not support update on already sliced query sets. + """ + method = DataPoint.objects.all()[:2].update + self.assertRaises(AssertionError, method, + another_value='another thing') \ No newline at end of file From 06211b05cb344090a59bafae22a6a216a964d925 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 13 Sep 2010 05:46:59 +0000 Subject: [PATCH 168/902] [1.2.X] Migrated unmanaged_models doctests. Thanks to Eric Florenzano. Backport of r13825 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13834 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/unmanaged_models/models.py | 27 ------------- tests/modeltests/unmanaged_models/tests.py | 42 +++++++++++++++++++-- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/tests/modeltests/unmanaged_models/models.py b/tests/modeltests/unmanaged_models/models.py index ca9b05aca40d..0c2cf500dae9 100644 --- a/tests/modeltests/unmanaged_models/models.py +++ b/tests/modeltests/unmanaged_models/models.py @@ -123,30 +123,3 @@ class Meta: # table *will* be created (unless given a custom `through` as for C02 above). class Managed1(models.Model): mm = models.ManyToManyField(Unmanaged1) - -__test__ = {'API_TESTS':""" -The main test here is that the all the models can be created without any -database errors. We can also do some more simple insertion and lookup tests -whilst we're here to show that the second of models do refer to the tables from -the first set. - -# Insert some data into one set of models. ->>> a = A01.objects.create(f_a="foo", f_b=42) ->>> _ = B01.objects.create(fk_a=a, f_a="fred", f_b=1729) ->>> c = C01.objects.create(f_a="barney", f_b=1) ->>> c.mm_a = [a] - -# ... and pull it out via the other set. ->>> A02.objects.all() -[] ->>> b = B02.objects.all()[0] ->>> b - ->>> b.fk_a - ->>> C02.objects.filter(f_a=None) -[] ->>> C02.objects.filter(mm_a=a.id) -[] - -"""} diff --git a/tests/modeltests/unmanaged_models/tests.py b/tests/modeltests/unmanaged_models/tests.py index c5f14bd3df05..dbbe848cce91 100644 --- a/tests/modeltests/unmanaged_models/tests.py +++ b/tests/modeltests/unmanaged_models/tests.py @@ -1,9 +1,46 @@ from django.test import TestCase from django.db import connection from models import Unmanaged1, Unmanaged2, Managed1 +from models import A01, A02, B01, B02, C01, C02 + +class SimpleTests(TestCase): + + def test_simple(self): + """ + The main test here is that the all the models can be created without + any database errors. We can also do some more simple insertion and + lookup tests whilst we're here to show that the second of models do + refer to the tables from the first set. + """ + # Insert some data into one set of models. + a = A01.objects.create(f_a="foo", f_b=42) + B01.objects.create(fk_a=a, f_a="fred", f_b=1729) + c = C01.objects.create(f_a="barney", f_b=1) + c.mm_a = [a] + + # ... and pull it out via the other set. + a2 = A02.objects.all()[0] + self.assertTrue(isinstance(a2, A02)) + self.assertEqual(a2.f_a, "foo") + + b2 = B02.objects.all()[0] + self.assertTrue(isinstance(b2, B02)) + self.assertEqual(b2.f_a, "fred") + + self.assertTrue(isinstance(b2.fk_a, A02)) + self.assertEqual(b2.fk_a.f_a, "foo") + + self.assertEqual(list(C02.objects.filter(f_a=None)), []) + + resp = list(C02.objects.filter(mm_a=a.id)) + self.assertEqual(len(resp), 1) + + self.assertTrue(isinstance(resp[0], C02)) + self.assertEqual(resp[0].f_a, 'barney') + class ManyToManyUnmanagedTests(TestCase): - + def test_many_to_many_between_unmanaged(self): """ The intermediary table between two unmanaged models should not be created. @@ -11,7 +48,7 @@ def test_many_to_many_between_unmanaged(self): table = Unmanaged2._meta.get_field('mm').m2m_db_table() tables = connection.introspection.table_names() self.assert_(table not in tables, "Table '%s' should not exist, but it does." % table) - + def test_many_to_many_between_unmanaged_and_managed(self): """ An intermediary table between a managed and an unmanaged model should be created. @@ -19,4 +56,3 @@ def test_many_to_many_between_unmanaged_and_managed(self): table = Managed1._meta.get_field('mm').m2m_db_table() tables = connection.introspection.table_names() self.assert_(table in tables, "Table '%s' does not exist." % table) - \ No newline at end of file From 685ddbbe24303eb33b14e16e6a915e46e7c5cf5d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 13 Sep 2010 05:47:13 +0000 Subject: [PATCH 169/902] [1.2.X] Migrated transactions doctests. Thanks to Eric Florenzano. Backport of r13826 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13835 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/transactions/models.py | 136 +-------------------- tests/modeltests/transactions/tests.py | 155 ++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 135 deletions(-) create mode 100644 tests/modeltests/transactions/tests.py diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py index df0dd805a000..d957fe174ce0 100644 --- a/tests/modeltests/transactions/models.py +++ b/tests/modeltests/transactions/models.py @@ -18,138 +18,4 @@ class Meta: ordering = ('first_name', 'last_name') def __unicode__(self): - return u"%s %s" % (self.first_name, self.last_name) - -__test__ = {'API_TESTS':""" ->>> from django.db import connection, transaction -"""} - -from django.conf import settings - -building_docs = getattr(settings, 'BUILDING_DOCS', False) - -if building_docs or settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql': - __test__['API_TESTS'] += """ -# the default behavior is to autocommit after each save() action ->>> def create_a_reporter_then_fail(first, last): -... a = Reporter(first_name=first, last_name=last) -... a.save() -... raise Exception("I meant to do that") -... ->>> create_a_reporter_then_fail("Alice", "Smith") -Traceback (most recent call last): - ... -Exception: I meant to do that - -# The object created before the exception still exists ->>> Reporter.objects.all() -[] - -# the autocommit decorator works exactly the same as the default behavior ->>> autocomitted_create_then_fail = transaction.autocommit(create_a_reporter_then_fail) ->>> autocomitted_create_then_fail("Ben", "Jones") -Traceback (most recent call last): - ... -Exception: I meant to do that - -# Same behavior as before ->>> Reporter.objects.all() -[, ] - -# the autocommit decorator also works with a using argument ->>> using_autocomitted_create_then_fail = transaction.autocommit(using='default')(create_a_reporter_then_fail) ->>> using_autocomitted_create_then_fail("Carol", "Doe") -Traceback (most recent call last): - ... -Exception: I meant to do that - -# Same behavior as before ->>> Reporter.objects.all() -[, , ] - -# With the commit_on_success decorator, the transaction is only committed if the -# function doesn't throw an exception ->>> committed_on_success = transaction.commit_on_success(create_a_reporter_then_fail) ->>> committed_on_success("Dirk", "Gently") -Traceback (most recent call last): - ... -Exception: I meant to do that - -# This time the object never got saved ->>> Reporter.objects.all() -[, , ] - -# commit_on_success decorator also works with a using argument ->>> using_committed_on_success = transaction.commit_on_success(using='default')(create_a_reporter_then_fail) ->>> using_committed_on_success("Dirk", "Gently") -Traceback (most recent call last): - ... -Exception: I meant to do that - -# This time the object never got saved ->>> Reporter.objects.all() -[, , ] - -# If there aren't any exceptions, the data will get saved ->>> def remove_a_reporter(): -... r = Reporter.objects.get(first_name="Alice") -... r.delete() -... ->>> remove_comitted_on_success = transaction.commit_on_success(remove_a_reporter) ->>> remove_comitted_on_success() ->>> Reporter.objects.all() -[, ] - -# You can manually manage transactions if you really want to, but you -# have to remember to commit/rollback ->>> def manually_managed(): -... r = Reporter(first_name="Dirk", last_name="Gently") -... r.save() -... transaction.commit() ->>> manually_managed = transaction.commit_manually(manually_managed) ->>> manually_managed() ->>> Reporter.objects.all() -[, , ] - -# If you forget, you'll get bad errors ->>> def manually_managed_mistake(): -... r = Reporter(first_name="Edward", last_name="Woodward") -... r.save() -... # oops, I forgot to commit/rollback! ->>> manually_managed_mistake = transaction.commit_manually(manually_managed_mistake) ->>> manually_managed_mistake() -Traceback (most recent call last): - ... -TransactionManagementError: Transaction managed block ended with pending COMMIT/ROLLBACK - -# commit_manually also works with a using argument ->>> using_manually_managed_mistake = transaction.commit_manually(using='default')(manually_managed_mistake) ->>> using_manually_managed_mistake() -Traceback (most recent call last): - ... -TransactionManagementError: Transaction managed block ended with pending COMMIT/ROLLBACK - -""" - -# Regression for #11900: If a function wrapped by commit_on_success writes a -# transaction that can't be committed, that transaction should be rolled back. -# The bug is only visible using the psycopg2 backend, though -# the fix is generally a good idea. -pgsql_backends = ('django.db.backends.postgresql_psycopg2', 'postgresql_psycopg2',) -if building_docs or settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in pgsql_backends: - __test__['API_TESTS'] += """ ->>> def execute_bad_sql(): -... cursor = connection.cursor() -... cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');") -... transaction.set_dirty() -... ->>> execute_bad_sql = transaction.commit_on_success(execute_bad_sql) ->>> execute_bad_sql() -Traceback (most recent call last): - ... -IntegrityError: null value in column "email" violates not-null constraint - - ->>> transaction.rollback() - -""" + return u"%s %s" % (self.first_name, self.last_name) \ No newline at end of file diff --git a/tests/modeltests/transactions/tests.py b/tests/modeltests/transactions/tests.py new file mode 100644 index 000000000000..9964f5d7aba4 --- /dev/null +++ b/tests/modeltests/transactions/tests.py @@ -0,0 +1,155 @@ +from django.test import TransactionTestCase +from django.db import connection, transaction, IntegrityError, DEFAULT_DB_ALIAS +from django.conf import settings + +from models import Reporter + +PGSQL = 'psycopg2' in settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] +MYSQL = 'mysql' in settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] + +class TransactionTests(TransactionTestCase): + + if not MYSQL: + + def create_a_reporter_then_fail(self, first, last): + a = Reporter(first_name=first, last_name=last) + a.save() + raise Exception("I meant to do that") + + def remove_a_reporter(self, first_name): + r = Reporter.objects.get(first_name="Alice") + r.delete() + + def manually_managed(self): + r = Reporter(first_name="Dirk", last_name="Gently") + r.save() + transaction.commit() + + def manually_managed_mistake(self): + r = Reporter(first_name="Edward", last_name="Woodward") + r.save() + # Oops, I forgot to commit/rollback! + + def execute_bad_sql(self): + cursor = connection.cursor() + cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');") + transaction.set_dirty() + + def test_autocommit(self): + """ + The default behavior is to autocommit after each save() action. + """ + self.assertRaises(Exception, + self.create_a_reporter_then_fail, + "Alice", "Smith" + ) + + # The object created before the exception still exists + self.assertEqual(Reporter.objects.count(), 1) + + def test_autocommit_decorator(self): + """ + The autocommit decorator works exactly the same as the default behavior. + """ + autocomitted_create_then_fail = transaction.autocommit( + self.create_a_reporter_then_fail + ) + self.assertRaises(Exception, + autocomitted_create_then_fail, + "Alice", "Smith" + ) + # Again, the object created before the exception still exists + self.assertEqual(Reporter.objects.count(), 1) + + def test_autocommit_decorator_with_using(self): + """ + The autocommit decorator also works with a using argument. + """ + autocomitted_create_then_fail = transaction.autocommit(using='default')( + self.create_a_reporter_then_fail + ) + self.assertRaises(Exception, + autocomitted_create_then_fail, + "Alice", "Smith" + ) + # Again, the object created before the exception still exists + self.assertEqual(Reporter.objects.count(), 1) + + def test_commit_on_success(self): + """ + With the commit_on_success decorator, the transaction is only committed + if the function doesn't throw an exception. + """ + committed_on_success = transaction.commit_on_success( + self.create_a_reporter_then_fail) + self.assertRaises(Exception, committed_on_success, "Dirk", "Gently") + # This time the object never got saved + self.assertEqual(Reporter.objects.count(), 0) + + def test_commit_on_success_with_using(self): + """ + The commit_on_success decorator also works with a using argument. + """ + using_committed_on_success = transaction.commit_on_success(using='default')( + self.create_a_reporter_then_fail + ) + self.assertRaises(Exception, + using_committed_on_success, + "Dirk", "Gently" + ) + # This time the object never got saved + self.assertEqual(Reporter.objects.count(), 0) + + def test_commit_on_success_succeed(self): + """ + If there aren't any exceptions, the data will get saved. + """ + Reporter.objects.create(first_name="Alice", last_name="Smith") + remove_comitted_on_success = transaction.commit_on_success( + self.remove_a_reporter + ) + remove_comitted_on_success("Alice") + self.assertEqual(list(Reporter.objects.all()), []) + + def test_manually_managed(self): + """ + You can manually manage transactions if you really want to, but you + have to remember to commit/rollback. + """ + manually_managed = transaction.commit_manually(self.manually_managed) + manually_managed() + self.assertEqual(Reporter.objects.count(), 1) + + def test_manually_managed_mistake(self): + """ + If you forget, you'll get bad errors. + """ + manually_managed_mistake = transaction.commit_manually( + self.manually_managed_mistake + ) + self.assertRaises(transaction.TransactionManagementError, + manually_managed_mistake) + + def test_manually_managed_with_using(self): + """ + The commit_manually function also works with a using argument. + """ + using_manually_managed_mistake = transaction.commit_manually(using='default')( + self.manually_managed_mistake + ) + self.assertRaises(transaction.TransactionManagementError, + using_manually_managed_mistake + ) + + if PGSQL: + + def test_bad_sql(self): + """ + Regression for #11900: If a function wrapped by commit_on_success + writes a transaction that can't be committed, that transaction should + be rolled back. The bug is only visible using the psycopg2 backend, + though the fix is generally a good idea. + """ + execute_bad_sql = transaction.commit_on_success(self.execute_bad_sql) + self.assertRaises(IntegrityError, execute_bad_sql) + transaction.rollback() From 902c1114429b3b215cc3efc98c9a639a5018da2f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 13 Sep 2010 05:47:26 +0000 Subject: [PATCH 170/902] [1.2.X] Migrated str doctests. Thanks to Eric Florenzano. Backport of r13827 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13836 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/str/models.py | 21 +-------------------- tests/modeltests/str/tests.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 20 deletions(-) create mode 100644 tests/modeltests/str/tests.py diff --git a/tests/modeltests/str/models.py b/tests/modeltests/str/models.py index 644c6025ab19..84b8d67d12cd 100644 --- a/tests/modeltests/str/models.py +++ b/tests/modeltests/str/models.py @@ -30,23 +30,4 @@ class InternationalArticle(models.Model): pub_date = models.DateTimeField() def __unicode__(self): - return self.headline - -__test__ = {'API_TESTS':ur""" -# Create an Article. ->>> from datetime import datetime ->>> a = Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28)) ->>> a.save() - ->>> str(a) -'Area man programs in Python' - ->>> a - - ->>> a1 = InternationalArticle(headline=u'Girl wins €12.500 in lottery', pub_date=datetime(2005, 7, 28)) - -# The default str() output will be the UTF-8 encoded output of __unicode__(). ->>> str(a1) -'Girl wins \xe2\x82\xac12.500 in lottery' -"""} + return self.headline \ No newline at end of file diff --git a/tests/modeltests/str/tests.py b/tests/modeltests/str/tests.py new file mode 100644 index 000000000000..4e4c76501fed --- /dev/null +++ b/tests/modeltests/str/tests.py @@ -0,0 +1,23 @@ + # -*- coding: utf-8 -*- +import datetime + +from django.test import TestCase + +from models import Article, InternationalArticle + +class SimpleTests(TestCase): + def test_basic(self): + a = Article.objects.create( + headline='Area man programs in Python', + pub_date=datetime.datetime(2005, 7, 28) + ) + self.assertEqual(str(a), 'Area man programs in Python') + self.assertEqual(repr(a), '') + + def test_international(self): + a = InternationalArticle.objects.create( + headline=u'Girl wins €12.500 in lottery', + pub_date=datetime.datetime(2005, 7, 28) + ) + # The default str() output will be the UTF-8 encoded output of __unicode__(). + self.assertEqual(str(a), 'Girl wins \xe2\x82\xac12.500 in lottery') \ No newline at end of file From 21fccbc13e28bf63c7998c79f15fa85537caa2db Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 13 Sep 2010 05:47:39 +0000 Subject: [PATCH 171/902] [1.2.X] Migrated select_related doctests. Thanks to Eric Florenzano. Backport of r13828 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13837 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/select_related/models.py | 132 +---------------- tests/modeltests/select_related/tests.py | 166 ++++++++++++++++++++++ 2 files changed, 167 insertions(+), 131 deletions(-) create mode 100644 tests/modeltests/select_related/tests.py diff --git a/tests/modeltests/select_related/models.py b/tests/modeltests/select_related/models.py index 9d64cf24c6c7..3c2e7721fd63 100644 --- a/tests/modeltests/select_related/models.py +++ b/tests/modeltests/select_related/models.py @@ -56,134 +56,4 @@ class Species(models.Model): name = models.CharField(max_length=50) genus = models.ForeignKey(Genus) def __unicode__(self): - return self.name - -def create_tree(stringtree): - """Helper to create a complete tree""" - names = stringtree.split() - models = [Domain, Kingdom, Phylum, Klass, Order, Family, Genus, Species] - assert len(names) == len(models), (names, models) - - parent = None - for name, model in zip(names, models): - try: - obj = model.objects.get(name=name) - except model.DoesNotExist: - obj = model(name=name) - if parent: - setattr(obj, parent.__class__.__name__.lower(), parent) - obj.save() - parent = obj - -__test__ = {'API_TESTS':""" - -# Set up. -# The test runner sets settings.DEBUG to False, but we want to gather queries -# so we'll set it to True here and reset it at the end of the test suite. ->>> from django.conf import settings ->>> settings.DEBUG = True - ->>> create_tree("Eukaryota Animalia Anthropoda Insecta Diptera Drosophilidae Drosophila melanogaster") ->>> create_tree("Eukaryota Animalia Chordata Mammalia Primates Hominidae Homo sapiens") ->>> create_tree("Eukaryota Plantae Magnoliophyta Magnoliopsida Fabales Fabaceae Pisum sativum") ->>> create_tree("Eukaryota Fungi Basidiomycota Homobasidiomycatae Agaricales Amanitacae Amanita muscaria") - ->>> from django import db - -# Normally, accessing FKs doesn't fill in related objects: ->>> db.reset_queries() ->>> fly = Species.objects.get(name="melanogaster") ->>> fly.genus.family.order.klass.phylum.kingdom.domain - ->>> len(db.connection.queries) -8 - -# However, a select_related() call will fill in those related objects without any extra queries: ->>> db.reset_queries() ->>> person = Species.objects.select_related(depth=10).get(name="sapiens") ->>> person.genus.family.order.klass.phylum.kingdom.domain - ->>> len(db.connection.queries) -1 - -# select_related() also of course applies to entire lists, not just items. -# Without select_related() ->>> db.reset_queries() ->>> world = Species.objects.all() ->>> [o.genus.family for o in world] -[, , , ] ->>> len(db.connection.queries) -9 - -# With select_related(): ->>> db.reset_queries() ->>> world = Species.objects.all().select_related() ->>> [o.genus.family for o in world] -[, , , ] ->>> len(db.connection.queries) -1 - -# The "depth" argument to select_related() will stop the descent at a particular level: ->>> db.reset_queries() ->>> pea = Species.objects.select_related(depth=1).get(name="sativum") ->>> pea.genus.family.order.klass.phylum.kingdom.domain - - -# Notice: one fewer queries than above because of depth=1 ->>> len(db.connection.queries) -7 - ->>> db.reset_queries() ->>> pea = Species.objects.select_related(depth=5).get(name="sativum") ->>> pea.genus.family.order.klass.phylum.kingdom.domain - ->>> len(db.connection.queries) -3 - ->>> db.reset_queries() ->>> world = Species.objects.all().select_related(depth=2) ->>> [o.genus.family.order for o in world] -[, , , ] ->>> len(db.connection.queries) -5 - ->>> s = Species.objects.all().select_related(depth=1).extra(select={'a': 'select_related_species.id + 10'})[0] ->>> s.id + 10 == s.a -True - -# The optional fields passed to select_related() control which related models -# we pull in. This allows for smaller queries and can act as an alternative -# (or, in addition to) the depth parameter. - -# In the next two cases, we explicitly say to select the 'genus' and -# 'genus.family' models, leading to the same number of queries as before. ->>> db.reset_queries() ->>> world = Species.objects.select_related('genus__family') ->>> [o.genus.family for o in world] -[, , , ] ->>> len(db.connection.queries) -1 - ->>> db.reset_queries() ->>> world = Species.objects.filter(genus__name='Amanita').select_related('genus__family') ->>> [o.genus.family.order for o in world] -[] ->>> len(db.connection.queries) -2 - ->>> db.reset_queries() ->>> Species.objects.all().select_related('genus__family__order').order_by('id')[0:1].get().genus.family.order.name -u'Diptera' ->>> len(db.connection.queries) -1 - -# Specifying both "depth" and fields is an error. ->>> Species.objects.select_related('genus__family__order', depth=4) -Traceback (most recent call last): -... -TypeError: Cannot pass both "depth" and fields to select_related() - -# Reset DEBUG to where we found it. ->>> settings.DEBUG = False -"""} - + return self.name \ No newline at end of file diff --git a/tests/modeltests/select_related/tests.py b/tests/modeltests/select_related/tests.py new file mode 100644 index 000000000000..a2111026cd68 --- /dev/null +++ b/tests/modeltests/select_related/tests.py @@ -0,0 +1,166 @@ +from django.test import TestCase +from django.conf import settings +from django import db + +from models import Domain, Kingdom, Phylum, Klass, Order, Family, Genus, Species + +class SelectRelatedTests(TestCase): + + def create_tree(self, stringtree): + """ + Helper to create a complete tree. + """ + names = stringtree.split() + models = [Domain, Kingdom, Phylum, Klass, Order, Family, Genus, Species] + assert len(names) == len(models), (names, models) + + parent = None + for name, model in zip(names, models): + try: + obj = model.objects.get(name=name) + except model.DoesNotExist: + obj = model(name=name) + if parent: + setattr(obj, parent.__class__.__name__.lower(), parent) + obj.save() + parent = obj + + def create_base_data(self): + self.create_tree("Eukaryota Animalia Anthropoda Insecta Diptera Drosophilidae Drosophila melanogaster") + self.create_tree("Eukaryota Animalia Chordata Mammalia Primates Hominidae Homo sapiens") + self.create_tree("Eukaryota Plantae Magnoliophyta Magnoliopsida Fabales Fabaceae Pisum sativum") + self.create_tree("Eukaryota Fungi Basidiomycota Homobasidiomycatae Agaricales Amanitacae Amanita muscaria") + + def setUp(self): + # The test runner sets settings.DEBUG to False, but we want to gather + # queries so we'll set it to True here and reset it at the end of the + # test case. + self.create_base_data() + settings.DEBUG = True + db.reset_queries() + + def tearDown(self): + settings.DEBUG = False + + def test_access_fks_without_select_related(self): + """ + Normally, accessing FKs doesn't fill in related objects + """ + fly = Species.objects.get(name="melanogaster") + domain = fly.genus.family.order.klass.phylum.kingdom.domain + self.assertEqual(domain.name, 'Eukaryota') + self.assertEqual(len(db.connection.queries), 8) + + def test_access_fks_with_select_related(self): + """ + A select_related() call will fill in those related objects without any + extra queries + """ + person = Species.objects.select_related(depth=10).get(name="sapiens") + domain = person.genus.family.order.klass.phylum.kingdom.domain + self.assertEqual(domain.name, 'Eukaryota') + self.assertEqual(len(db.connection.queries), 1) + + def test_list_without_select_related(self): + """ + select_related() also of course applies to entire lists, not just + items. This test verifies the expected behavior without select_related. + """ + world = Species.objects.all() + families = [o.genus.family.name for o in world] + self.assertEqual(families, [ + 'Drosophilidae', + 'Hominidae', + 'Fabaceae', + 'Amanitacae', + ]) + self.assertEqual(len(db.connection.queries), 9) + + def test_list_with_select_related(self): + """ + select_related() also of course applies to entire lists, not just + items. This test verifies the expected behavior with select_related. + """ + world = Species.objects.all().select_related() + families = [o.genus.family.name for o in world] + self.assertEqual(families, [ + 'Drosophilidae', + 'Hominidae', + 'Fabaceae', + 'Amanitacae', + ]) + self.assertEqual(len(db.connection.queries), 1) + + def test_depth(self, depth=1, expected=7): + """ + The "depth" argument to select_related() will stop the descent at a + particular level. + """ + pea = Species.objects.select_related(depth=depth).get(name="sativum") + self.assertEqual( + pea.genus.family.order.klass.phylum.kingdom.domain.name, + 'Eukaryota' + ) + # Notice: one fewer queries than above because of depth=1 + self.assertEqual(len(db.connection.queries), expected) + + def test_larger_depth(self): + """ + The "depth" argument to select_related() will stop the descent at a + particular level. This tests a larger depth value. + """ + self.test_depth(depth=5, expected=3) + + def test_list_with_depth(self): + """ + The "depth" argument to select_related() will stop the descent at a + particular level. This can be used on lists as well. + """ + world = Species.objects.all().select_related(depth=2) + orders = [o.genus.family.order.name for o in world] + self.assertEqual(orders, + ['Diptera', 'Primates', 'Fabales', 'Agaricales']) + self.assertEqual(len(db.connection.queries), 5) + + def test_select_related_with_extra(self): + s = Species.objects.all().select_related(depth=1)\ + .extra(select={'a': 'select_related_species.id + 10'})[0] + self.assertEqual(s.id + 10, s.a) + + def test_certain_fields(self): + """ + The optional fields passed to select_related() control which related + models we pull in. This allows for smaller queries and can act as an + alternative (or, in addition to) the depth parameter. + + In this case, we explicitly say to select the 'genus' and + 'genus.family' models, leading to the same number of queries as before. + """ + world = Species.objects.select_related('genus__family') + families = [o.genus.family.name for o in world] + self.assertEqual(families, + ['Drosophilidae', 'Hominidae', 'Fabaceae', 'Amanitacae']) + self.assertEqual(len(db.connection.queries), 1) + + def test_more_certain_fields(self): + """ + In this case, we explicitly say to select the 'genus' and + 'genus.family' models, leading to the same number of queries as before. + """ + world = Species.objects.filter(genus__name='Amanita')\ + .select_related('genus__family') + orders = [o.genus.family.order.name for o in world] + self.assertEqual(orders, [u'Agaricales']) + self.assertEqual(len(db.connection.queries), 2) + + def test_field_traversal(self): + s = Species.objects.all().select_related('genus__family__order' + ).order_by('id')[0:1].get().genus.family.order.name + self.assertEqual(s, u'Diptera') + self.assertEqual(len(db.connection.queries), 1) + + def test_depth_fields_fails(self): + self.assertRaises(TypeError, + Species.objects.select_related, + 'genus__family__order', depth=4 + ) From 11f77de5337ba692985907d41e2186ac34ea87e5 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 13 Sep 2010 05:47:52 +0000 Subject: [PATCH 172/902] [1.2.X] Migrated reverse_lookup doctests. Thanks to Eric Florenzano Backport of r13829 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13838 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/reverse_lookup/models.py | 31 -------------- tests/modeltests/reverse_lookup/tests.py | 49 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 tests/modeltests/reverse_lookup/tests.py diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py index ef385b4b1886..2ffdc39b3be1 100644 --- a/tests/modeltests/reverse_lookup/models.py +++ b/tests/modeltests/reverse_lookup/models.py @@ -26,34 +26,3 @@ class Choice(models.Model): def __unicode__(self): return self.name - -__test__ = {'API_TESTS':""" ->>> john = User(name="John Doe") ->>> john.save() ->>> jim = User(name="Jim Bo") ->>> jim.save() ->>> first_poll = Poll(question="What's the first question?", creator=john) ->>> first_poll.save() ->>> second_poll = Poll(question="What's the second question?", creator=jim) ->>> second_poll.save() ->>> new_choice = Choice(poll=first_poll, related_poll=second_poll, name="This is the answer.") ->>> new_choice.save() - ->>> # Reverse lookups by field name: ->>> User.objects.get(poll__question__exact="What's the first question?") - ->>> User.objects.get(poll__question__exact="What's the second question?") - - ->>> # Reverse lookups by related_name: ->>> Poll.objects.get(poll_choice__name__exact="This is the answer.") - ->>> Poll.objects.get(related_choice__name__exact="This is the answer.") - - ->>> # If a related_name is given you can't use the field name instead: ->>> Poll.objects.get(choice__name__exact="This is the answer") -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'choice' into field. Choices are: creator, id, poll_choice, question, related_choice -"""} diff --git a/tests/modeltests/reverse_lookup/tests.py b/tests/modeltests/reverse_lookup/tests.py new file mode 100644 index 000000000000..9a6e3068fab0 --- /dev/null +++ b/tests/modeltests/reverse_lookup/tests.py @@ -0,0 +1,49 @@ +from django.test import TestCase +from django.core.exceptions import FieldError + +from models import User, Poll, Choice + +class ReverseLookupTests(TestCase): + + def setUp(self): + john = User.objects.create(name="John Doe") + jim = User.objects.create(name="Jim Bo") + first_poll = Poll.objects.create( + question="What's the first question?", + creator=john + ) + second_poll = Poll.objects.create( + question="What's the second question?", + creator=jim + ) + new_choice = Choice.objects.create( + poll=first_poll, + related_poll=second_poll, + name="This is the answer." + ) + + def test_reverse_by_field(self): + u1 = User.objects.get( + poll__question__exact="What's the first question?" + ) + self.assertEqual(u1.name, "John Doe") + + u2 = User.objects.get( + poll__question__exact="What's the second question?" + ) + self.assertEqual(u2.name, "Jim Bo") + + def test_reverse_by_related_name(self): + p1 = Poll.objects.get(poll_choice__name__exact="This is the answer.") + self.assertEqual(p1.question, "What's the first question?") + + p2 = Poll.objects.get( + related_choice__name__exact="This is the answer.") + self.assertEqual(p2.question, "What's the second question?") + + def test_reverse_field_name_disallowed(self): + """ + If a related_name is given you can't use the field name instead + """ + self.assertRaises(FieldError, Poll.objects.get, + choice__name__exact="This is the answer") From 896ebdcb71aaecde346dfc00594d8960045523ad Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 13 Sep 2010 05:48:06 +0000 Subject: [PATCH 173/902] [1.2.X] Migrated reserved_names doctests. Thanks to Eric Florenzano. Backport of r13830 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13839 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/reserved_names/models.py | 31 +-------------- tests/modeltests/reserved_names/tests.py | 48 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 30 deletions(-) create mode 100644 tests/modeltests/reserved_names/tests.py diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py index f698b5bc4940..d8c1238ff10d 100644 --- a/tests/modeltests/reserved_names/models.py +++ b/tests/modeltests/reserved_names/models.py @@ -22,33 +22,4 @@ class Meta: db_table = 'select' def __unicode__(self): - return self.when - -__test__ = {'API_TESTS':""" ->>> import datetime ->>> day1 = datetime.date(2005, 1, 1) ->>> day2 = datetime.date(2006, 2, 2) ->>> t = Thing(when='a', join='b', like='c', drop='d', alter='e', having='f', where=day1, has_hyphen='h') ->>> t.save() ->>> print t.when -a - ->>> u = Thing(when='h', join='i', like='j', drop='k', alter='l', having='m', where=day2) ->>> u.save() ->>> print u.when -h - ->>> Thing.objects.order_by('when') -[, ] ->>> v = Thing.objects.get(pk='a') ->>> print v.join -b ->>> print v.where -2005-01-01 - ->>> Thing.objects.dates('where', 'year') -[datetime.datetime(2005, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)] - ->>> Thing.objects.filter(where__month=1) -[] -"""} + return self.when \ No newline at end of file diff --git a/tests/modeltests/reserved_names/tests.py b/tests/modeltests/reserved_names/tests.py new file mode 100644 index 000000000000..b7e48674119b --- /dev/null +++ b/tests/modeltests/reserved_names/tests.py @@ -0,0 +1,48 @@ +import datetime + +from django.test import TestCase + +from models import Thing + +class ReservedNameTests(TestCase): + def generate(self): + day1 = datetime.date(2005, 1, 1) + t = Thing.objects.create(when='a', join='b', like='c', drop='d', + alter='e', having='f', where=day1, has_hyphen='h') + day2 = datetime.date(2006, 2, 2) + u = Thing.objects.create(when='h', join='i', like='j', drop='k', + alter='l', having='m', where=day2) + + def test_simple(self): + day1 = datetime.date(2005, 1, 1) + t = Thing.objects.create(when='a', join='b', like='c', drop='d', + alter='e', having='f', where=day1, has_hyphen='h') + self.assertEqual(t.when, 'a') + + day2 = datetime.date(2006, 2, 2) + u = Thing.objects.create(when='h', join='i', like='j', drop='k', + alter='l', having='m', where=day2) + self.assertEqual(u.when, 'h') + + def test_order_by(self): + self.generate() + things = [t.when for t in Thing.objects.order_by('when')] + self.assertEqual(things, ['a', 'h']) + + def test_fields(self): + self.generate() + v = Thing.objects.get(pk='a') + self.assertEqual(v.join, 'b') + self.assertEqual(v.where, datetime.date(year=2005, month=1, day=1)) + + def test_dates(self): + self.generate() + resp = Thing.objects.dates('where', 'year') + self.assertEqual(list(resp), [ + datetime.datetime(2005, 1, 1, 0, 0), + datetime.datetime(2006, 1, 1, 0, 0), + ]) + + def test_month_filter(self): + self.generate() + self.assertEqual(Thing.objects.filter(where__month=1)[0].when, 'a') From c6754100051d48511721cecaebd636b6f56be203 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 13 Sep 2010 05:48:18 +0000 Subject: [PATCH 174/902] [1.2.X] Migrated proxy_models doctests. Thanks to Eric Florenzano. Backport of r13831 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13840 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/proxy_models/models.py | 230 +---------------- tests/modeltests/proxy_models/tests.py | 314 ++++++++++++++++++++++++ 2 files changed, 315 insertions(+), 229 deletions(-) create mode 100644 tests/modeltests/proxy_models/tests.py diff --git a/tests/modeltests/proxy_models/models.py b/tests/modeltests/proxy_models/models.py index 28446b96c1d3..90d54d94dd72 100644 --- a/tests/modeltests/proxy_models/models.py +++ b/tests/modeltests/proxy_models/models.py @@ -161,232 +161,4 @@ class Improvement(Issue): class ProxyImprovement(Improvement): class Meta: - proxy = True - -__test__ = {'API_TESTS' : """ -# The MyPerson model should be generating the same database queries as the -# Person model (when the same manager is used in each case). ->>> from django.db import DEFAULT_DB_ALIAS ->>> MyPerson.other.all().query.get_compiler(DEFAULT_DB_ALIAS).as_sql() == Person.objects.order_by("name").query.get_compiler(DEFAULT_DB_ALIAS).as_sql() -True - -# The StatusPerson models should have its own table (it's using ORM-level -# inheritance). ->>> StatusPerson.objects.all().query.get_compiler(DEFAULT_DB_ALIAS).as_sql() == Person.objects.all().query.get_compiler(DEFAULT_DB_ALIAS).as_sql() -False - -# Creating a Person makes them accessible through the MyPerson proxy. ->>> _ = Person.objects.create(name="Foo McBar") ->>> len(Person.objects.all()) -1 ->>> len(MyPerson.objects.all()) -1 ->>> MyPerson.objects.get(name="Foo McBar").id -1 ->>> MyPerson.objects.get(id=1).has_special_name() -False - -# Person is not proxied by StatusPerson subclass, however. ->>> StatusPerson.objects.all() -[] - -# A new MyPerson also shows up as a standard Person ->>> _ = MyPerson.objects.create(name="Bazza del Frob") ->>> len(MyPerson.objects.all()) -2 ->>> len(Person.objects.all()) -2 - ->>> _ = LowerStatusPerson.objects.create(status="low", name="homer") ->>> LowerStatusPerson.objects.all() -[] - -# Correct type when querying a proxy of proxy - ->>> MyPersonProxy.objects.all() -[, , ] - -# Proxy models are included in the ancestors for a model's DoesNotExist and MultipleObjectsReturned ->>> try: -... MyPersonProxy.objects.get(name='Zathras') -... except Person.DoesNotExist: -... pass ->>> try: -... MyPersonProxy.objects.get(id__lt=10) -... except Person.MultipleObjectsReturned: -... pass ->>> try: -... StatusPerson.objects.get(name='Zathras') -... except Person.DoesNotExist: -... pass ->>> sp1 = StatusPerson.objects.create(name='Bazza Jr.') ->>> sp2 = StatusPerson.objects.create(name='Foo Jr.') ->>> try: -... StatusPerson.objects.get(id__lt=10) -... except Person.MultipleObjectsReturned: -... pass - -# And now for some things that shouldn't work... -# -# All base classes must be non-abstract ->>> class NoAbstract(Abstract): -... class Meta: -... proxy = True -Traceback (most recent call last): - .... -TypeError: Abstract base class containing model fields not permitted for proxy model 'NoAbstract'. - -# The proxy must actually have one concrete base class ->>> class TooManyBases(Person, Abstract): -... class Meta: -... proxy = True -Traceback (most recent call last): - .... -TypeError: Abstract base class containing model fields not permitted for proxy model 'TooManyBases'. - ->>> class NoBaseClasses(models.Model): -... class Meta: -... proxy = True -Traceback (most recent call last): - .... -TypeError: Proxy model 'NoBaseClasses' has no non-abstract model base class. - - -# A proxy cannot introduce any new fields ->>> class NoNewFields(Person): -... newfield = models.BooleanField() -... class Meta: -... proxy = True -Traceback (most recent call last): - .... -FieldError: Proxy model 'NoNewFields' contains model fields. - -# Manager tests. - ->>> Person.objects.all().delete() ->>> _ = Person.objects.create(name="fred") ->>> _ = Person.objects.create(name="wilma") ->>> _ = Person.objects.create(name="barney") - ->>> MyPerson.objects.all() -[, ] ->>> MyPerson._default_manager.all() -[, ] - ->>> OtherPerson.objects.all() -[, ] ->>> OtherPerson.excluder.all() -[, ] ->>> OtherPerson._default_manager.all() -[, ] - -# Test save signals for proxy models ->>> from django.db.models import signals ->>> def make_handler(model, event): -... def _handler(*args, **kwargs): -... print u"%s %s save" % (model, event) -... return _handler ->>> h1 = make_handler('MyPerson', 'pre') ->>> h2 = make_handler('MyPerson', 'post') ->>> h3 = make_handler('Person', 'pre') ->>> h4 = make_handler('Person', 'post') ->>> signals.pre_save.connect(h1, sender=MyPerson) ->>> signals.post_save.connect(h2, sender=MyPerson) ->>> signals.pre_save.connect(h3, sender=Person) ->>> signals.post_save.connect(h4, sender=Person) ->>> dino = MyPerson.objects.create(name=u"dino") -MyPerson pre save -MyPerson post save - -# Test save signals for proxy proxy models ->>> h5 = make_handler('MyPersonProxy', 'pre') ->>> h6 = make_handler('MyPersonProxy', 'post') ->>> signals.pre_save.connect(h5, sender=MyPersonProxy) ->>> signals.post_save.connect(h6, sender=MyPersonProxy) ->>> dino = MyPersonProxy.objects.create(name=u"pebbles") -MyPersonProxy pre save -MyPersonProxy post save - ->>> signals.pre_save.disconnect(h1, sender=MyPerson) ->>> signals.post_save.disconnect(h2, sender=MyPerson) ->>> signals.pre_save.disconnect(h3, sender=Person) ->>> signals.post_save.disconnect(h4, sender=Person) ->>> signals.pre_save.disconnect(h5, sender=MyPersonProxy) ->>> signals.post_save.disconnect(h6, sender=MyPersonProxy) - -# A proxy has the same content type as the model it is proxying for (at the -# storage level, it is meant to be essentially indistinguishable). ->>> ctype = ContentType.objects.get_for_model ->>> ctype(Person) is ctype(OtherPerson) -True - ->>> MyPersonProxy.objects.all() -[, , , ] - ->>> u = User.objects.create(name='Bruce') ->>> User.objects.all() -[] ->>> UserProxy.objects.all() -[] ->>> UserProxyProxy.objects.all() -[] - -# Proxy objects can be deleted ->>> u2 = UserProxy.objects.create(name='George') ->>> UserProxy.objects.all() -[, ] ->>> u2.delete() ->>> UserProxy.objects.all() -[] - - -# We can still use `select_related()` to include related models in our querysets. ->>> country = Country.objects.create(name='Australia') ->>> state = State.objects.create(name='New South Wales', country=country) - ->>> State.objects.select_related() -[] ->>> StateProxy.objects.select_related() -[] ->>> StateProxy.objects.get(name='New South Wales') - ->>> StateProxy.objects.select_related().get(name='New South Wales') - - ->>> contributor = TrackerUser.objects.create(name='Contributor',status='contrib') ->>> someone = BaseUser.objects.create(name='Someone') ->>> _ = Bug.objects.create(summary='fix this', version='1.1beta', -... assignee=contributor, reporter=someone) ->>> pcontributor = ProxyTrackerUser.objects.create(name='OtherContributor', -... status='proxy') ->>> _ = Improvement.objects.create(summary='improve that', version='1.1beta', -... assignee=contributor, reporter=pcontributor, -... associated_bug=ProxyProxyBug.objects.all()[0]) - -# Related field filter on proxy ->>> ProxyBug.objects.get(version__icontains='beta') - - -# Select related + filter on proxy ->>> ProxyBug.objects.select_related().get(version__icontains='beta') - - -# Proxy of proxy, select_related + filter ->>> ProxyProxyBug.objects.select_related().get(version__icontains='beta') - - -# Select related + filter on a related proxy field ->>> ProxyImprovement.objects.select_related().get(reporter__name__icontains='butor') - - -# Select related + filter on a related proxy of proxy field ->>> ProxyImprovement.objects.select_related().get(associated_bug__summary__icontains='fix') - - -Proxy models can be loaded from fixtures (Regression for #11194) ->>> from django.core import management ->>> management.call_command('loaddata', 'mypeople.json', verbosity=0) ->>> MyPerson.objects.get(pk=100) - - -"""} + proxy = True \ No newline at end of file diff --git a/tests/modeltests/proxy_models/tests.py b/tests/modeltests/proxy_models/tests.py new file mode 100644 index 000000000000..346a2a3296bf --- /dev/null +++ b/tests/modeltests/proxy_models/tests.py @@ -0,0 +1,314 @@ +from django.test import TestCase +from django.db import models, DEFAULT_DB_ALIAS +from django.db.models import signals +from django.core import management +from django.core.exceptions import FieldError + +from django.contrib.contenttypes.models import ContentType + +from models import MyPerson, Person, StatusPerson, LowerStatusPerson +from models import MyPersonProxy, Abstract, OtherPerson, User, UserProxy +from models import UserProxyProxy, Country, State, StateProxy, TrackerUser +from models import BaseUser, Bug, ProxyTrackerUser, Improvement, ProxyProxyBug +from models import ProxyBug, ProxyImprovement + +class ProxyModelTests(TestCase): + def test_same_manager_queries(self): + """ + The MyPerson model should be generating the same database queries as + the Person model (when the same manager is used in each case). + """ + my_person_sql = MyPerson.other.all().query.get_compiler( + DEFAULT_DB_ALIAS).as_sql() + person_sql = Person.objects.order_by("name").query.get_compiler( + DEFAULT_DB_ALIAS).as_sql() + self.assertEqual(my_person_sql, person_sql) + + def test_inheretance_new_table(self): + """ + The StatusPerson models should have its own table (it's using ORM-level + inheritance). + """ + sp_sql = StatusPerson.objects.all().query.get_compiler( + DEFAULT_DB_ALIAS).as_sql() + p_sql = Person.objects.all().query.get_compiler( + DEFAULT_DB_ALIAS).as_sql() + self.assertNotEqual(sp_sql, p_sql) + + def test_basic_proxy(self): + """ + Creating a Person makes them accessible through the MyPerson proxy. + """ + Person.objects.create(name="Foo McBar") + self.assertEqual(len(Person.objects.all()), 1) + self.assertEqual(len(MyPerson.objects.all()), 1) + self.assertEqual(MyPerson.objects.get(name="Foo McBar").id, 1) + self.assertFalse(MyPerson.objects.get(id=1).has_special_name()) + + def test_no_proxy(self): + """ + Person is not proxied by StatusPerson subclass. + """ + Person.objects.create(name="Foo McBar") + self.assertEqual(list(StatusPerson.objects.all()), []) + + def test_basic_proxy_reverse(self): + """ + A new MyPerson also shows up as a standard Person. + """ + MyPerson.objects.create(name="Bazza del Frob") + self.assertEqual(len(MyPerson.objects.all()), 1) + self.assertEqual(len(Person.objects.all()), 1) + + LowerStatusPerson.objects.create(status="low", name="homer") + lsps = [lsp.name for lsp in LowerStatusPerson.objects.all()] + self.assertEqual(lsps, ["homer"]) + + def test_correct_type_proxy_of_proxy(self): + """ + Correct type when querying a proxy of proxy + """ + Person.objects.create(name="Foo McBar") + MyPerson.objects.create(name="Bazza del Frob") + LowerStatusPerson.objects.create(status="low", name="homer") + pp = sorted([mpp.name for mpp in MyPersonProxy.objects.all()]) + self.assertEqual(pp, ['Bazza del Frob', 'Foo McBar', 'homer']) + + def test_proxy_included_in_ancestors(self): + """ + Proxy models are included in the ancestors for a model's DoesNotExist + and MultipleObjectsReturned + """ + Person.objects.create(name="Foo McBar") + MyPerson.objects.create(name="Bazza del Frob") + LowerStatusPerson.objects.create(status="low", name="homer") + max_id = Person.objects.aggregate(max_id=models.Max('id'))['max_id'] + + self.assertRaises(Person.DoesNotExist, + MyPersonProxy.objects.get, + name='Zathras' + ) + self.assertRaises(Person.MultipleObjectsReturned, + MyPersonProxy.objects.get, + id__lt=max_id+1 + ) + self.assertRaises(Person.DoesNotExist, + StatusPerson.objects.get, + name='Zathras' + ) + + sp1 = StatusPerson.objects.create(name='Bazza Jr.') + sp2 = StatusPerson.objects.create(name='Foo Jr.') + max_id = Person.objects.aggregate(max_id=models.Max('id'))['max_id'] + + self.assertRaises(Person.MultipleObjectsReturned, + StatusPerson.objects.get, + id__lt=max_id+1 + ) + + def test_abc(self): + """ + All base classes must be non-abstract + """ + def build_abc(): + class NoAbstract(Abstract): + class Meta: + proxy = True + self.assertRaises(TypeError, build_abc) + + def test_no_cbc(self): + """ + The proxy must actually have one concrete base class + """ + def build_no_cbc(): + class TooManyBases(Person, Abstract): + class Meta: + proxy = True + self.assertRaises(TypeError, build_no_cbc) + + def test_no_base_classes(self): + def build_no_base_classes(): + class NoBaseClasses(models.Model): + class Meta: + proxy = True + self.assertRaises(TypeError, build_no_base_classes) + + def test_new_fields(self): + def build_new_fields(): + class NoNewFields(Person): + newfield = models.BooleanField() + class Meta: + proxy = True + self.assertRaises(FieldError, build_new_fields) + + def test_myperson_manager(self): + Person.objects.create(name="fred") + Person.objects.create(name="wilma") + Person.objects.create(name="barney") + + resp = [p.name for p in MyPerson.objects.all()] + self.assertEqual(resp, ['barney', 'fred']) + + resp = [p.name for p in MyPerson._default_manager.all()] + self.assertEqual(resp, ['barney', 'fred']) + + def test_otherperson_manager(self): + Person.objects.create(name="fred") + Person.objects.create(name="wilma") + Person.objects.create(name="barney") + + resp = [p.name for p in OtherPerson.objects.all()] + self.assertEqual(resp, ['barney', 'wilma']) + + resp = [p.name for p in OtherPerson.excluder.all()] + self.assertEqual(resp, ['barney', 'fred']) + + resp = [p.name for p in OtherPerson._default_manager.all()] + self.assertEqual(resp, ['barney', 'wilma']) + + def test_proxy_model_signals(self): + """ + Test save signals for proxy models + """ + output = [] + + def make_handler(model, event): + def _handler(*args, **kwargs): + output.append('%s %s save' % (model, event)) + return _handler + + h1 = make_handler('MyPerson', 'pre') + h2 = make_handler('MyPerson', 'post') + h3 = make_handler('Person', 'pre') + h4 = make_handler('Person', 'post') + + signals.pre_save.connect(h1, sender=MyPerson) + signals.post_save.connect(h2, sender=MyPerson) + signals.pre_save.connect(h3, sender=Person) + signals.post_save.connect(h4, sender=Person) + + dino = MyPerson.objects.create(name=u"dino") + self.assertEqual(output, [ + 'MyPerson pre save', + 'MyPerson post save' + ]) + + output = [] + + h5 = make_handler('MyPersonProxy', 'pre') + h6 = make_handler('MyPersonProxy', 'post') + + signals.pre_save.connect(h5, sender=MyPersonProxy) + signals.post_save.connect(h6, sender=MyPersonProxy) + + dino = MyPersonProxy.objects.create(name=u"pebbles") + + self.assertEqual(output, [ + 'MyPersonProxy pre save', + 'MyPersonProxy post save' + ]) + + signals.pre_save.disconnect(h1, sender=MyPerson) + signals.post_save.disconnect(h2, sender=MyPerson) + signals.pre_save.disconnect(h3, sender=Person) + signals.post_save.disconnect(h4, sender=Person) + signals.pre_save.disconnect(h5, sender=MyPersonProxy) + signals.post_save.disconnect(h6, sender=MyPersonProxy) + + def test_content_type(self): + ctype = ContentType.objects.get_for_model + self.assertTrue(ctype(Person) is ctype(OtherPerson)) + + def test_user_userproxy_userproxyproxy(self): + User.objects.create(name='Bruce') + + resp = [u.name for u in User.objects.all()] + self.assertEqual(resp, ['Bruce']) + + resp = [u.name for u in UserProxy.objects.all()] + self.assertEqual(resp, ['Bruce']) + + resp = [u.name for u in UserProxyProxy.objects.all()] + self.assertEqual(resp, ['Bruce']) + + def test_proxy_delete(self): + """ + Proxy objects can be deleted + """ + User.objects.create(name='Bruce') + u2 = UserProxy.objects.create(name='George') + + resp = [u.name for u in UserProxy.objects.all()] + self.assertEqual(resp, ['Bruce', 'George']) + + u2.delete() + + resp = [u.name for u in UserProxy.objects.all()] + self.assertEqual(resp, ['Bruce']) + + def test_select_related(self): + """ + We can still use `select_related()` to include related models in our + querysets. + """ + country = Country.objects.create(name='Australia') + state = State.objects.create(name='New South Wales', country=country) + + resp = [s.name for s in State.objects.select_related()] + self.assertEqual(resp, ['New South Wales']) + + resp = [s.name for s in StateProxy.objects.select_related()] + self.assertEqual(resp, ['New South Wales']) + + self.assertEqual(StateProxy.objects.get(name='New South Wales').name, + 'New South Wales') + + resp = StateProxy.objects.select_related().get(name='New South Wales') + self.assertEqual(resp.name, 'New South Wales') + + def test_proxy_bug(self): + contributor = TrackerUser.objects.create(name='Contributor', + status='contrib') + someone = BaseUser.objects.create(name='Someone') + Bug.objects.create(summary='fix this', version='1.1beta', + assignee=contributor, reporter=someone) + pcontributor = ProxyTrackerUser.objects.create(name='OtherContributor', + status='proxy') + Improvement.objects.create(summary='improve that', version='1.1beta', + assignee=contributor, reporter=pcontributor, + associated_bug=ProxyProxyBug.objects.all()[0]) + + # Related field filter on proxy + resp = ProxyBug.objects.get(version__icontains='beta') + self.assertEqual(repr(resp), '') + + # Select related + filter on proxy + resp = ProxyBug.objects.select_related().get(version__icontains='beta') + self.assertEqual(repr(resp), '') + + # Proxy of proxy, select_related + filter + resp = ProxyProxyBug.objects.select_related().get( + version__icontains='beta' + ) + self.assertEqual(repr(resp), '') + + # Select related + filter on a related proxy field + resp = ProxyImprovement.objects.select_related().get( + reporter__name__icontains='butor' + ) + self.assertEqual(repr(resp), + '' + ) + + # Select related + filter on a related proxy of proxy field + resp = ProxyImprovement.objects.select_related().get( + associated_bug__summary__icontains='fix' + ) + self.assertEqual(repr(resp), + '' + ) + + def test_proxy_load_from_fixture(self): + management.call_command('loaddata', 'mypeople.json', verbosity=0, commit=False) + p = MyPerson.objects.get(pk=100) + self.assertEqual(p.name, 'Elvis Presley') From 0c8e8b15b59592ad0406d2fc416061225753799e Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 13 Sep 2010 19:43:41 +0000 Subject: [PATCH 175/902] [1.2.X] Fixed #12918 - Tutorial page 2 issues Thanks to Leam for report, cassidy for patch. Backport of [13841] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13842 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial02.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index fcdb812c81c5..d43df9ed53c2 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -284,7 +284,7 @@ registration code to read:: This tells Django: "Choice objects are edited on the Poll admin page. By default, provide enough fields for 3 choices." -Load the "Add poll" page to see how that looks: +Load the "Add poll" page to see how that looks, you may need to restart your development server: .. image:: _images/admin11t.png :alt: Add poll page now has choices on it @@ -350,7 +350,7 @@ method (in ``models.py``) a ``short_description`` attribute:: return self.pub_date.date() == datetime.date.today() was_published_today.short_description = 'Published today?' -Let's add another improvement to the Poll change list page: Filters. Add the +Edit your admin.py file again and add an improvement to the Poll change list page: Filters. Add the following line to ``PollAdmin``:: list_filter = ['pub_date'] From 21f60e9fa92dab7d80fbf47e97c1bd92a4f10382 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 13 Sep 2010 21:45:56 +0000 Subject: [PATCH 176/902] [1.2.X] Fixed #11594 - Inaccurate docstring for WhereNode.add() Thanks to garrison for report, dwillis for patch Backport of [13843] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13844 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/sql/where.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 4e5a6472594a..72b2d4cc82e1 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -36,10 +36,10 @@ class WhereNode(tree.Node): def add(self, data, connector): """ Add a node to the where-tree. If the data is a list or tuple, it is - expected to be of the form (alias, col_name, field_obj, lookup_type, - value), which is then slightly munged before being stored (to avoid - storing any reference to field objects). Otherwise, the 'data' is - stored unchanged and can be anything with an 'as_sql()' method. + expected to be of the form (obj, lookup_type, value), where obj is + a Constraint object, and is then slightly munged before being stored + (to avoid storing any reference to field objects). Otherwise, the 'data' + is stored unchanged and can be any class with an 'as_sql()' method. """ if not isinstance(data, (list, tuple)): super(WhereNode, self).add(data, connector) From 93e83546a35f268a4bbf0a9b30186fc4ee1b0885 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 13 Sep 2010 22:34:39 +0000 Subject: [PATCH 177/902] [1.2.X] Fixed #12965 - unordered_list template filter fails when given a non-iterable second item in a two item list Thanks to grahamu for the report and patch. Backport of [13845] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13846 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/defaultfilters.py | 4 ++++ tests/regressiontests/defaultfilters/tests.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 1e58ff71ca46..80002c3b0b9e 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -601,6 +601,10 @@ def convert_old_style_list(list_): first_item, second_item = list_ if second_item == []: return [first_item], True + try: + it = iter(second_item) # see if second item is iterable + except TypeError: + return list_, False old_style_list = True new_second_item = [] for sublist in second_item: diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index 25555081e498..ff49b65d6861 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -347,6 +347,17 @@ >>> unordered_list(['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]) u'\t
      • States\n\t
          \n\t\t
        • Kansas\n\t\t
            \n\t\t\t
          • Lawrence
          • \n\t\t\t
          • Topeka
          • \n\t\t
          \n\t\t
        • \n\t\t
        • Illinois
        • \n\t
        \n\t
      • ' +>>> class ULItem(object): +... def __init__(self, title): +... self.title = title +... def __unicode__(self): +... return u'ulitem-%s' % str(self.title) + +>>> a = ULItem('a') +>>> b = ULItem('b') +>>> unordered_list([a,b]) +u'\t
      • ulitem-a
      • \n\t
      • ulitem-b
      • ' + # Old format for unordered lists should still work >>> unordered_list([u'item 1', []]) u'\t
      • item 1
      • ' From b176bd4f1e626827899514c5552c41d4ac1453c6 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 13 Sep 2010 22:49:18 +0000 Subject: [PATCH 178/902] [1.2.X] Fixed #13623 - code in intro-tutorial03 missing an import statement Thanks to lescoutinhovr@gmail.com, AMJ for the report, and to zerok/timo for the patch. Backport of [13847] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13848 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial03.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 1865b1bd5c44..d3470359aaf8 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -474,10 +474,14 @@ Copy the file ``mysite/urls.py`` to ``mysite/polls/urls.py``. Then, change ``mysite/urls.py`` to remove the poll-specific URLs and insert an :func:`~django.conf.urls.defaults.include`:: + # This also imports the include function + from django.conf.urls.defaults import * + # ... urlpatterns = patterns('', (r'^polls/', include('mysite.polls.urls')), # ... + ) :func:`~django.conf.urls.defaults.include`, simply, references another URLconf. Note that the regular expression doesn't have a ``$`` (end-of-string match From 759625cd90f36976b1bc98c1dd9de0e8fe309bee Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 13 Sep 2010 23:02:29 +0000 Subject: [PATCH 179/902] [1.2.X] Fixed #13765 - 'safe' parameter for urlencode filter Thanks to KyleMac for the suggestion and SmileyChris for the patch Backport of [13849] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13850 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/defaultfilters.py | 16 +++++++++++++--- django/utils/http.py | 4 ++-- docs/ref/templates/builtins.txt | 13 +++++++++++++ tests/regressiontests/templates/filters.py | 4 ++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 80002c3b0b9e..55e19472e867 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -291,10 +291,20 @@ def upper(value): upper.is_safe = False upper = stringfilter(upper) -def urlencode(value): - """Escapes a value for use in a URL.""" +def urlencode(value, safe=None): + """ + Escapes a value for use in a URL. + + Takes an optional ``safe`` parameter used to determine the characters which + should not be escaped by Django's ``urlquote`` method. If not provided, the + default safe characters will be used (but an empty string can be provided + when *all* characters should be escaped). + """ from django.utils.http import urlquote - return urlquote(value) + kwargs = {} + if safe is not None: + kwargs['safe'] = safe + return urlquote(value, **kwargs) urlencode.is_safe = False urlencode = stringfilter(urlencode) diff --git a/django/utils/http.py b/django/utils/http.py index f0b1af9c586d..610fcbf685b4 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -14,7 +14,7 @@ def urlquote(url, safe='/'): can safely be used as part of an argument to a subsequent iri_to_uri() call without double-quoting occurring. """ - return force_unicode(urllib.quote(smart_str(url), safe)) + return force_unicode(urllib.quote(smart_str(url), smart_str(safe))) urlquote = allow_lazy(urlquote, unicode) @@ -25,7 +25,7 @@ def urlquote_plus(url, safe=''): returned string can safely be used as part of an argument to a subsequent iri_to_uri() call without double-quoting occurring. """ - return force_unicode(urllib.quote_plus(smart_str(url), safe)) + return force_unicode(urllib.quote_plus(smart_str(url), smart_str(safe))) urlquote_plus = allow_lazy(urlquote_plus, unicode) def urlencode(query, doseq=0): diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 4f33bd212cc6..85c0b6dc269c 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1967,6 +1967,19 @@ For example:: If ``value`` is ``"http://www.example.org/foo?a=b&c=d"``, the output will be ``"http%3A//www.example.org/foo%3Fa%3Db%26c%3Dd"``. +.. versionadded:: 1.1 + +An optional argument containing the characters which should not be escaped can +be provided. + +If not provided, the '/' character is assumed safe. An empty string can be +provided when *all* characters should be escaped. For example:: + + {{ value|urlencode:"" }} + +If ``value`` is ``"http://www.example.org/"``, the output will be +``"http%3A%2F%2Fwww.example.org%2F"``. + .. templatefilter:: urlize urlize diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py index af34c58a9f86..9ada4fb26e83 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/regressiontests/templates/filters.py @@ -265,6 +265,10 @@ def get_filter_tests(): 'filter-iriencode03': ('{{ url|iriencode }}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'), 'filter-iriencode04': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'), + # urlencode + 'filter-urlencode01': ('{{ url|urlencode }}', {'url': '/test&"/me?/'}, '/test%26%22/me%3F/'), + 'filter-urlencode02': ('/test/{{ urlbit|urlencode:"" }}/', {'urlbit': 'escape/slash'}, '/test/escape%2Fslash/'), + # Chaining a bunch of safeness-preserving filters should not alter # the safe status either way. 'chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), From 238eea44ebcd6bf897bb389154e6f1c18859acc0 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 13 Sep 2010 23:17:11 +0000 Subject: [PATCH 180/902] [1.2.X] Fixed #14252 - django.contrib.flatpages unit tests assume default value for settings.LOGIN_URL Thanks to pmclanahan for report and patch. Backport of [13851] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13853 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/flatpages/tests/csrf.py | 3 +++ django/contrib/flatpages/tests/middleware.py | 3 +++ django/contrib/flatpages/tests/views.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/django/contrib/flatpages/tests/csrf.py b/django/contrib/flatpages/tests/csrf.py index dca92bec5ee2..0f0ab08ed2ae 100644 --- a/django/contrib/flatpages/tests/csrf.py +++ b/django/contrib/flatpages/tests/csrf.py @@ -22,10 +22,13 @@ def setUp(self): 'templates' ), ) + self.old_LOGIN_URL = settings.LOGIN_URL + settings.LOGIN_URL = '/accounts/login/' def tearDown(self): settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS + settings.LOGIN_URL = self.old_LOGIN_URL def test_view_flatpage(self): "A flatpage can be served through a view, even when the middleware is in use" diff --git a/django/contrib/flatpages/tests/middleware.py b/django/contrib/flatpages/tests/middleware.py index 04396d190e3c..a41259626d24 100644 --- a/django/contrib/flatpages/tests/middleware.py +++ b/django/contrib/flatpages/tests/middleware.py @@ -18,10 +18,13 @@ def setUp(self): 'templates' ), ) + self.old_LOGIN_URL = settings.LOGIN_URL + settings.LOGIN_URL = '/accounts/login/' def tearDown(self): settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS + settings.LOGIN_URL = self.old_LOGIN_URL def test_view_flatpage(self): "A flatpage can be served through a view, even when the middleware is in use" diff --git a/django/contrib/flatpages/tests/views.py b/django/contrib/flatpages/tests/views.py index a9004209b7fe..a013ae982513 100644 --- a/django/contrib/flatpages/tests/views.py +++ b/django/contrib/flatpages/tests/views.py @@ -18,10 +18,13 @@ def setUp(self): 'templates' ), ) + self.old_LOGIN_URL = settings.LOGIN_URL + settings.LOGIN_URL = '/accounts/login/' def tearDown(self): settings.MIDDLEWARE_CLASSES = self.old_MIDDLEWARE_CLASSES settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS + settings.LOGIN_URL = self.old_LOGIN_URL def test_view_flatpage(self): "A flatpage can be served through a view" From 2d7a3e71c1e250ff23d412482fab89778c642ebe Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 13 Sep 2010 23:17:27 +0000 Subject: [PATCH 181/902] [1.2.X] Fixed #14254 - More tests for storage backends Thanks to steph for the patch, also to tobias for cleanups. Backport of [13852] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13854 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/file_storage/tests.py | 142 +++++++++++++++++++- 1 file changed, 138 insertions(+), 4 deletions(-) diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index a1470f9f05b6..d59fe9c30757 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -8,10 +8,11 @@ from cStringIO import StringIO from django.conf import settings from django.core.exceptions import SuspiciousOperation -from django.core.files.base import ContentFile +from django.core.files.base import ContentFile, File from django.core.files.images import get_image_dimensions -from django.core.files.storage import FileSystemStorage +from django.core.files.storage import FileSystemStorage, get_storage_class from django.core.files.uploadedfile import UploadedFile +from django.core.exceptions import ImproperlyConfigured from unittest import TestCase try: import threading @@ -29,16 +30,66 @@ except ImportError: Image = None +class GetStorageClassTests(unittest.TestCase): + def assertRaisesErrorWithMessage(self, error, message, callable, + *args, **kwargs): + self.assertRaises(error, callable, *args, **kwargs) + try: + callable(*args, **kwargs) + except error, e: + self.assertEqual(message, str(e)) + + def test_get_filesystem_storage(self): + """ + get_storage_class returns the class for a storage backend name/path. + """ + self.assertEqual( + get_storage_class('django.core.files.storage.FileSystemStorage'), + FileSystemStorage) + + def test_get_invalid_storage_module(self): + """ + get_storage_class raises an error if the requested import don't exist. + """ + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "NonExistingStorage isn't a storage module.", + get_storage_class, + 'NonExistingStorage') + + def test_get_nonexisting_storage_class(self): + """ + get_storage_class raises an error if the requested class don't exist. + """ + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + 'Storage module "django.core.files.storage" does not define a '\ + '"NonExistingStorage" class.', + get_storage_class, + 'django.core.files.storage.NonExistingStorage') + + def test_get_nonexisting_storage_module(self): + """ + get_storage_class raises an error if the requested module don't exist. + """ + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + 'Error importing storage module django.core.files.non_existing_'\ + 'storage: "No module named non_existing_storage"', + get_storage_class, + 'django.core.files.non_existing_storage.NonExistingStorage') + class FileStorageTests(unittest.TestCase): storage_class = FileSystemStorage def setUp(self): self.temp_dir = tempfile.mktemp() os.makedirs(self.temp_dir) - self.storage = self.storage_class(location=self.temp_dir) + self.storage = self.storage_class(location=self.temp_dir, + base_url='/test_media_url/') def tearDown(self): - os.rmdir(self.temp_dir) + shutil.rmtree(self.temp_dir) def test_file_access_options(self): """ @@ -57,6 +108,89 @@ def test_file_access_options(self): self.storage.delete('storage_test') self.failIf(self.storage.exists('storage_test')) + def test_file_save_without_name(self): + """ + File storage extracts the filename from the content object if no + name is given explicitly. + """ + self.failIf(self.storage.exists('test.file')) + + f = ContentFile('custom contents') + f.name = 'test.file' + + storage_f_name = self.storage.save(None, f) + + self.assertEqual(storage_f_name, f.name) + + self.assert_(os.path.exists(os.path.join(self.temp_dir, f.name))) + + self.storage.delete(storage_f_name) + + def test_file_path(self): + """ + File storage returns the full path of a file + """ + self.failIf(self.storage.exists('test.file')) + + f = ContentFile('custom contents') + f_name = self.storage.save('test.file', f) + + self.assertEqual(self.storage.path(f_name), + os.path.join(self.temp_dir, f_name)) + + self.storage.delete(f_name) + + def test_file_url(self): + """ + File storage returns a url to access a given file from the web. + """ + self.assertEqual(self.storage.url('test.file'), + '%s%s' % (self.storage.base_url, 'test.file')) + + self.storage.base_url = None + self.assertRaises(ValueError, self.storage.url, 'test.file') + + def test_file_with_mixin(self): + """ + File storage can get a mixin to extend the functionality of the + returned file. + """ + self.failIf(self.storage.exists('test.file')) + + class TestFileMixin(object): + mixed_in = True + + f = ContentFile('custom contents') + f_name = self.storage.save('test.file', f) + + self.assert_(isinstance( + self.storage.open('test.file', mixin=TestFileMixin), + TestFileMixin + )) + + self.storage.delete('test.file') + + def test_listdir(self): + """ + File storage returns a tuple containing directories and files. + """ + self.failIf(self.storage.exists('storage_test_1')) + self.failIf(self.storage.exists('storage_test_2')) + self.failIf(self.storage.exists('storage_dir_1')) + + f = self.storage.save('storage_test_1', ContentFile('custom content')) + f = self.storage.save('storage_test_2', ContentFile('custom content')) + os.mkdir(os.path.join(self.temp_dir, 'storage_dir_1')) + + dirs, files = self.storage.listdir('') + self.assertEqual(set(dirs), set([u'storage_dir_1'])) + self.assertEqual(set(files), + set([u'storage_test_1', u'storage_test_2'])) + + self.storage.delete('storage_test_1') + self.storage.delete('storage_test_2') + os.rmdir(os.path.join(self.temp_dir, 'storage_dir_1')) + def test_file_storage_prevents_directory_traversal(self): """ File storage prevents directory traversal (files can only be accessed if From efae9a166bc273253990ce34963d4fa715f3077d Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 14 Sep 2010 14:24:15 +0000 Subject: [PATCH 182/902] [1.2.X] Fixed #2283 (again) - comment form templates don't validate as XHTML Thanks to hjoreteg/thejaswi_puthraya for the report and patch. Backport of [13855] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13856 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/comments/templates/comments/400-debug.html | 4 ++-- django/contrib/comments/templates/comments/approve.html | 2 +- django/contrib/comments/templates/comments/base.html | 5 +++-- django/contrib/comments/templates/comments/delete.html | 2 +- django/contrib/comments/templates/comments/flag.html | 2 +- django/contrib/comments/templates/comments/form.html | 4 ++-- django/contrib/comments/templates/comments/preview.html | 4 ++-- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/django/contrib/comments/templates/comments/400-debug.html b/django/contrib/comments/templates/comments/400-debug.html index 3c4532b9b0ba..29593b574bb7 100644 --- a/django/contrib/comments/templates/comments/400-debug.html +++ b/django/contrib/comments/templates/comments/400-debug.html @@ -1,5 +1,5 @@ - - + + Comment post not allowed (400) diff --git a/django/contrib/comments/templates/comments/approve.html b/django/contrib/comments/templates/comments/approve.html index 1a3a3fd80c7f..78d15dbb0e00 100644 --- a/django/contrib/comments/templates/comments/approve.html +++ b/django/contrib/comments/templates/comments/approve.html @@ -7,7 +7,7 @@

        {% trans "Really make this comment public?" %}

        {{ comment|linebreaks }}
        {% csrf_token %} - {% if next %}{% endif %} + {% if next %}
        {% endif %}

        or cancel

        diff --git a/django/contrib/comments/templates/comments/base.html b/django/contrib/comments/templates/comments/base.html index 36fc66f7d1f7..b787246c9c20 100644 --- a/django/contrib/comments/templates/comments/base.html +++ b/django/contrib/comments/templates/comments/base.html @@ -1,5 +1,6 @@ - - + +.dtd"> + {% block title %}{% endblock %} diff --git a/django/contrib/comments/templates/comments/delete.html b/django/contrib/comments/templates/comments/delete.html index 5ff2add9c55e..50c9a4d1b18a 100644 --- a/django/contrib/comments/templates/comments/delete.html +++ b/django/contrib/comments/templates/comments/delete.html @@ -7,7 +7,7 @@

        {% trans "Really remove this comment?" %}

        {{ comment|linebreaks }}
        {% csrf_token %} - {% if next %}{% endif %} + {% if next %}
        {% endif %}

        or cancel

        diff --git a/django/contrib/comments/templates/comments/flag.html b/django/contrib/comments/templates/comments/flag.html index 0b9ab1ccb228..ca7c77f111be 100644 --- a/django/contrib/comments/templates/comments/flag.html +++ b/django/contrib/comments/templates/comments/flag.html @@ -7,7 +7,7 @@

        {% trans "Really flag this comment?" %}

        {{ comment|linebreaks }}
        {% csrf_token %} - {% if next %}{% endif %} + {% if next %}
        {% endif %}

        or cancel

        diff --git a/django/contrib/comments/templates/comments/form.html b/django/contrib/comments/templates/comments/form.html index 30f031128c74..2a9ad557dd88 100644 --- a/django/contrib/comments/templates/comments/form.html +++ b/django/contrib/comments/templates/comments/form.html @@ -1,9 +1,9 @@ {% load comments i18n %} {% csrf_token %} - {% if next %}{% endif %} + {% if next %}
        {% endif %} {% for field in form %} {% if field.is_hidden %} - {{ field }} +
        {{ field }}
        {% else %} {% if field.errors %}{{ field.errors }}{% endif %}

        {% csrf_token %} - {% if next %}{% endif %} + {% if next %}

        {% endif %} {% if form.errors %}

        {% blocktrans count form.errors|length as counter %}Please correct the error below{% plural %}Please correct the errors below{% endblocktrans %}

        {% else %} @@ -18,7 +18,7 @@

        {% trans "Preview your comment" %}

        {% endif %} {% for field in form %} {% if field.is_hidden %} - {{ field }} +
        {{ field }}
        {% else %} {% if field.errors %}{{ field.errors }}{% endif %}

        Date: Tue, 14 Sep 2010 21:42:06 +0000 Subject: [PATCH 183/902] [1.2.X] Fixed paste-o in [13855] Backport of [13857] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13858 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/comments/templates/comments/base.html | 1 - 1 file changed, 1 deletion(-) diff --git a/django/contrib/comments/templates/comments/base.html b/django/contrib/comments/templates/comments/base.html index b787246c9c20..0f58e3e6ecfc 100644 --- a/django/contrib/comments/templates/comments/base.html +++ b/django/contrib/comments/templates/comments/base.html @@ -1,5 +1,4 @@ -.dtd"> From a63bea3e3baf1d354667cfd8d5ddad09516c24ab Mon Sep 17 00:00:00 2001 From: Ian Kelly Date: Thu, 16 Sep 2010 19:56:39 +0000 Subject: [PATCH 184/902] [1.2.X] Fixed #14244: Allow lists of more than 1000 items to be used with the 'in' lookup in Oracle, by breaking them up into groups of 1000 items and ORing them together. Thanks to rlynch for the report and initial patch. Backport of [13859] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13860 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/queries/models.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index f3f0ad8857e0..f22cfb3af345 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -1339,3 +1339,23 @@ def __unicode__(self): [] """ + +# Sqlite 3 does not support passing in more than 1000 parameters except by +# changing a parameter at compilation time. +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != "django.db.backends.sqlite3": + __test__["API_TESTS"] += """ +Bug #14244: Test that the "in" lookup works with lists of 1000 items or more. +>>> Number.objects.all().delete() +>>> numbers = range(2500) +>>> for num in numbers: +... _ = Number.objects.create(num=num) +>>> Number.objects.filter(num__in=numbers[:1000]).count() +1000 +>>> Number.objects.filter(num__in=numbers[:1001]).count() +1001 +>>> Number.objects.filter(num__in=numbers[:2000]).count() +2000 +>>> Number.objects.filter(num__in=numbers).count() +2500 + +""" From 1fb572a6d628b1fb5c47ddd881ff2b38d5c58799 Mon Sep 17 00:00:00 2001 From: Ian Kelly Date: Thu, 16 Sep 2010 19:58:25 +0000 Subject: [PATCH 185/902] [1.2.X] Committing missing files from [13859]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13861 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/__init__.py | 7 +++++++ django/db/backends/oracle/base.py | 3 +++ django/db/models/sql/where.py | 21 +++++++++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index fe2c7c451bde..75f40cd36f7b 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -233,6 +233,13 @@ def lookup_cast(self, lookup_type): """ return "%s" + def max_in_list_size(self): + """ + Returns the maximum number of items that can be passed in a single 'IN' + list condition, or None if the backend does not impose a limit. + """ + return None + def max_name_length(self): """ Returns the maximum length of table and column names, or None if there diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 0cf26c406da1..a175c3933a5e 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -178,6 +178,9 @@ def lookup_cast(self, lookup_type): return "UPPER(%s)" return "%s" + def max_in_list_size(self): + return 1000 + def max_name_length(self): return 30 diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 72b2d4cc82e1..2427a528fb87 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -2,6 +2,7 @@ Code to manage the creation and SQL rendering of 'where' constraints. """ import datetime +from itertools import repeat from django.utils import tree from django.db.models.fields import Field @@ -178,8 +179,24 @@ def make_atom(self, child, qn, connection): raise EmptyResultSet if extra: return ('%s IN %s' % (field_sql, extra), params) - return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(params))), - params) + max_in_list_size = connection.ops.max_in_list_size() + if max_in_list_size and len(params) > max_in_list_size: + # Break up the params list into an OR of manageable chunks. + in_clause_elements = ['('] + for offset in xrange(0, len(params), max_in_list_size): + if offset > 0: + in_clause_elements.append(' OR ') + in_clause_elements.append('%s IN (' % field_sql) + group_size = min(len(params) - offset, max_in_list_size) + param_group = ', '.join(repeat('%s', group_size)) + in_clause_elements.append(param_group) + in_clause_elements.append(')') + in_clause_elements.append(')') + return ''.join(in_clause_elements), params + else: + return ('%s IN (%s)' % (field_sql, + ', '.join(repeat('%s', len(params)))), + params) elif lookup_type in ('range', 'year'): return ('%s BETWEEN %%s and %%s' % field_sql, params) elif lookup_type in ('month', 'day', 'week_day'): From 80b9233e4a45f87b1b6da555026119af5c587d55 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 19 Sep 2010 14:05:43 +0000 Subject: [PATCH 186/902] [1.2.X] Fixed #11486 -- Corrected the XML serializer to allow for the serialization of objects with a null PK value. Also includes migration of doctests to unittests (we have always been at war with doctests). Thanks to zdmytriv for the report, and Niall Kelly for the patch. Backport of r13862 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13863 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/core/serializers/xml_serializer.py | 25 +- tests/modeltests/serializers/models.py | 243 +------------ tests/modeltests/serializers/tests.py | 417 ++++++++++++++++++++++ 4 files changed, 442 insertions(+), 244 deletions(-) create mode 100644 tests/modeltests/serializers/tests.py diff --git a/AUTHORS b/AUTHORS index 69da9144ca95..15e82a88cf8b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -247,6 +247,7 @@ answer newbie questions, and generally made Django that much better: Erik Karulf Ben Dean Kawamura Ian G. Kelly + Niall Kelly Ryan Kelly Thomas Kerpe Wiley Kestner diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 5fef3b6936af..bcf5631e00f1 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -42,10 +42,16 @@ def start_object(self, obj): raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj)) self.indent(1) - self.xml.startElement("object", { - "pk" : smart_unicode(obj._get_pk_val()), - "model" : smart_unicode(obj._meta), - }) + obj_pk = obj._get_pk_val() + if obj_pk is None: + attrs = {"model": smart_unicode(obj._meta),} + else: + attrs = { + "pk": smart_unicode(obj._get_pk_val()), + "model": smart_unicode(obj._meta), + } + + self.xml.startElement("object", attrs) def end_object(self, obj): """ @@ -166,11 +172,12 @@ def _handle_object(self, node): # bail. Model = self._get_model_from_node(node, "model") - # Start building a data dictionary from the object. If the node is - # missing the pk attribute, bail. - pk = node.getAttribute("pk") - if not pk: - raise base.DeserializationError(" node is missing the 'pk' attribute") + # Start building a data dictionary from the object. + # If the node is missing the pk set it to None + if node.hasAttribute("pk"): + pk = node.getAttribute("pk") + else: + pk = None data = {Model._meta.pk.attname : Model._meta.pk.to_python(pk)} diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py index aac945077ba2..c12e73fd6e73 100644 --- a/tests/modeltests/serializers/models.py +++ b/tests/modeltests/serializers/models.py @@ -18,6 +18,7 @@ class Meta: def __unicode__(self): return self.name + class Author(models.Model): name = models.CharField(max_length=20) @@ -27,6 +28,7 @@ class Meta: def __unicode__(self): return self.name + class Article(models.Model): author = models.ForeignKey(Author) headline = models.CharField(max_length=50) @@ -39,6 +41,7 @@ class Meta: def __unicode__(self): return self.headline + class AuthorProfile(models.Model): author = models.OneToOneField(Author, primary_key=True) date_of_birth = models.DateField() @@ -46,6 +49,7 @@ class AuthorProfile(models.Model): def __unicode__(self): return u"Profile of %s" % self.author + class Actor(models.Model): name = models.CharField(max_length=20, primary_key=True) @@ -55,6 +59,7 @@ class Meta: def __unicode__(self): return self.name + class Movie(models.Model): actor = models.ForeignKey(Actor) title = models.CharField(max_length=50) @@ -66,6 +71,7 @@ class Meta: def __unicode__(self): return self.title + class Score(models.Model): score = models.FloatField() @@ -83,6 +89,7 @@ def __str__(self): def to_string(self): return "%s" % self.title + class TeamField(models.CharField): __metaclass__ = models.SubfieldBase @@ -100,6 +107,7 @@ def to_python(self, value): def value_to_string(self, obj): return self._get_val_from_obj(obj).to_string() + class Player(models.Model): name = models.CharField(max_length=50) rank = models.IntegerField() @@ -107,238 +115,3 @@ class Player(models.Model): def __unicode__(self): return u'%s (%d) playing for %s' % (self.name, self.rank, self.team.to_string()) - -__test__ = {'API_TESTS':""" -# Create some data: ->>> from datetime import datetime ->>> sports = Category(name="Sports") ->>> music = Category(name="Music") ->>> op_ed = Category(name="Op-Ed") ->>> sports.save(); music.save(); op_ed.save() - ->>> joe = Author(name="Joe") ->>> jane = Author(name="Jane") ->>> joe.save(); jane.save() - ->>> a1 = Article( -... author = jane, -... headline = "Poker has no place on ESPN", -... pub_date = datetime(2006, 6, 16, 11, 00)) ->>> a2 = Article( -... author = joe, -... headline = "Time to reform copyright", -... pub_date = datetime(2006, 6, 16, 13, 00, 11, 345)) ->>> a1.save(); a2.save() ->>> a1.categories = [sports, op_ed] ->>> a2.categories = [music, op_ed] - -# Serialize a queryset to XML ->>> from django.core import serializers ->>> xml = serializers.serialize("xml", Article.objects.all()) - -# The output is valid XML ->>> from xml.dom import minidom ->>> dom = minidom.parseString(xml) - -# Deserializing has a similar interface, except that special DeserializedObject -# instances are returned. This is because data might have changed in the -# database since the data was serialized (we'll simulate that below). ->>> for obj in serializers.deserialize("xml", xml): -... print obj - - - -# Deserializing data with different field values doesn't change anything in the -# database until we call save(): ->>> xml = xml.replace("Poker has no place on ESPN", "Poker has no place on television") ->>> objs = list(serializers.deserialize("xml", xml)) - -# Even those I deserialized, the database hasn't been touched ->>> Article.objects.all() -[, ] - -# But when I save, the data changes as you might except. ->>> objs[0].save() ->>> Article.objects.all() -[, ] - -# Django also ships with a built-in JSON serializers ->>> json = serializers.serialize("json", Category.objects.filter(pk=2)) ->>> json -'[{"pk": 2, "model": "serializers.category", "fields": {"name": "Music"}}]' - -# You can easily create new objects by deserializing data with an empty PK -# (It's easier to demo this with JSON...) ->>> new_author_json = '[{"pk": null, "model": "serializers.author", "fields": {"name": "Bill"}}]' ->>> for obj in serializers.deserialize("json", new_author_json): -... obj.save() ->>> Author.objects.all() -[, , ] - -# All the serializers work the same ->>> json = serializers.serialize("json", Article.objects.all()) ->>> for obj in serializers.deserialize("json", json): -... print obj - - - ->>> json = json.replace("Poker has no place on television", "Just kidding; I love TV poker") ->>> for obj in serializers.deserialize("json", json): -... obj.save() - ->>> Article.objects.all() -[, ] - -# If you use your own primary key field (such as a OneToOneField), -# it doesn't appear in the serialized field list - it replaces the -# pk identifier. ->>> profile = AuthorProfile(author=joe, date_of_birth=datetime(1970,1,1)) ->>> profile.save() - ->>> json = serializers.serialize("json", AuthorProfile.objects.all()) ->>> json -'[{"pk": 1, "model": "serializers.authorprofile", "fields": {"date_of_birth": "1970-01-01"}}]' - ->>> for obj in serializers.deserialize("json", json): -... print obj - - -# Objects ids can be referenced before they are defined in the serialization data -# However, the deserialization process will need to be contained within a transaction ->>> json = '[{"pk": 3, "model": "serializers.article", "fields": {"headline": "Forward references pose no problem", "pub_date": "2006-06-16 15:00:00", "categories": [4, 1], "author": 4}}, {"pk": 4, "model": "serializers.category", "fields": {"name": "Reference"}}, {"pk": 4, "model": "serializers.author", "fields": {"name": "Agnes"}}]' ->>> from django.db import transaction ->>> transaction.enter_transaction_management() ->>> transaction.managed(True) ->>> for obj in serializers.deserialize("json", json): -... obj.save() - ->>> transaction.commit() ->>> transaction.leave_transaction_management() - ->>> article = Article.objects.get(pk=3) ->>> article - ->>> article.categories.all() -[, ] ->>> article.author - - -# Serializer output can be restricted to a subset of fields ->>> print serializers.serialize("json", Article.objects.all(), fields=('headline','pub_date')) -[{"pk": 1, "model": "serializers.article", "fields": {"headline": "Just kidding; I love TV poker", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 2, "model": "serializers.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:11"}}, {"pk": 3, "model": "serializers.article", "fields": {"headline": "Forward references pose no problem", "pub_date": "2006-06-16 15:00:00"}}] - -# Every string is serialized as a unicode object, also primary key -# which is 'varchar' ->>> ac = Actor(name="Zażółć") ->>> mv = Movie(title="Gęślą jaźń", actor=ac) ->>> ac.save(); mv.save() - -# Let's serialize our movie ->>> print serializers.serialize("json", [mv]) -[{"pk": 1, "model": "serializers.movie", "fields": {"price": "0.00", "actor": "Za\u017c\u00f3\u0142\u0107", "title": "G\u0119\u015bl\u0105 ja\u017a\u0144"}}] - -# Deserialization of movie ->>> list(serializers.deserialize('json', serializers.serialize('json', [mv])))[0].object.title -u'G\u0119\u015bl\u0105 ja\u017a\u0144' - -# None is null after serialization to json -# Primary key is None in case of not saved model ->>> mv2 = Movie(title="Movie 2", actor=ac) ->>> print serializers.serialize("json", [mv2]) -[{"pk": null, "model": "serializers.movie", "fields": {"price": "0.00", "actor": "Za\u017c\u00f3\u0142\u0107", "title": "Movie 2"}}] - -# Deserialization of null returns None for pk ->>> print list(serializers.deserialize('json', serializers.serialize('json', [mv2])))[0].object.id -None - -# Serialization and deserialization of floats: ->>> sc = Score(score=3.4) ->>> print serializers.serialize("json", [sc]) -[{"pk": null, "model": "serializers.score", "fields": {"score": 3.4}}] ->>> print list(serializers.deserialize('json', serializers.serialize('json', [sc])))[0].object.score -3.4 - -# Custom field with non trivial to string convertion value ->>> player = Player() ->>> player.name = "Soslan Djanaev" ->>> player.rank = 1 ->>> player.team = Team("Spartak Moskva") ->>> player.save() - ->>> serialized = serializers.serialize("json", Player.objects.all()) ->>> print serialized -[{"pk": 1, "model": "serializers.player", "fields": {"name": "Soslan Djanaev", "rank": 1, "team": "Spartak Moskva"}}] - ->>> obj = list(serializers.deserialize("json", serialized))[0] ->>> print obj - - -# Regression for #12524 -- dates before 1000AD get prefixed 0's on the year ->>> a = Article.objects.create( -... pk=4, -... author = jane, -... headline = "Nobody remembers the early years", -... pub_date = datetime(1, 2, 3, 4, 5, 6)) - ->>> serialized = serializers.serialize("json", [a]) ->>> print serialized -[{"pk": 4, "model": "serializers.article", "fields": {"headline": "Nobody remembers the early years", "pub_date": "0001-02-03 04:05:06", "categories": [], "author": 2}}] - ->>> obj = list(serializers.deserialize("json", serialized))[0] ->>> print obj.object.pub_date -0001-02-03 04:05:06 - -"""} - -try: - import yaml - __test__['YAML'] = """ -# Create some data: - ->>> articles = Article.objects.all().order_by("id")[:2] ->>> from django.core import serializers - -# test if serial - ->>> serialized = serializers.serialize("yaml", articles) ->>> print serialized -- fields: - author: 2 - categories: [3, 1] - headline: Just kidding; I love TV poker - pub_date: 2006-06-16 11:00:00 - model: serializers.article - pk: 1 -- fields: - author: 1 - categories: [2, 3] - headline: Time to reform copyright - pub_date: 2006-06-16 13:00:11 - model: serializers.article - pk: 2 - - ->>> obs = list(serializers.deserialize("yaml", serialized)) ->>> for i in obs: -... print i - - - -# Custom field with non trivial to string convertion value with YAML serializer - ->>> print serializers.serialize("yaml", Player.objects.all()) -- fields: {name: Soslan Djanaev, rank: 1, team: Spartak Moskva} - model: serializers.player - pk: 1 - - ->>> serialized = serializers.serialize("yaml", Player.objects.all()) ->>> obj = list(serializers.deserialize("yaml", serialized))[0] ->>> print obj - - - -""" -except ImportError: - pass - diff --git a/tests/modeltests/serializers/tests.py b/tests/modeltests/serializers/tests.py new file mode 100644 index 000000000000..9b648a8d4e04 --- /dev/null +++ b/tests/modeltests/serializers/tests.py @@ -0,0 +1,417 @@ +# -*- coding: utf-8 -*- +from datetime import datetime +from StringIO import StringIO +from xml.dom import minidom + +from django.core import serializers +from django.db import transaction +from django.test import TestCase, TransactionTestCase, Approximate +from django.utils import simplejson + +from models import Category, Author, Article, AuthorProfile, Actor, \ + Movie, Score, Player, Team + +class SerializersTestBase(object): + @staticmethod + def _comparison_value(value): + return value + + def setUp(self): + sports = Category.objects.create(name="Sports") + music = Category.objects.create(name="Music") + op_ed = Category.objects.create(name="Op-Ed") + + self.joe = Author.objects.create(name="Joe") + self.jane = Author.objects.create(name="Jane") + + self.a1 = Article( + author=self.jane, + headline="Poker has no place on ESPN", + pub_date=datetime(2006, 6, 16, 11, 00) + ) + self.a1.save() + self.a1.categories = [sports, op_ed] + + self.a2 = Article( + author=self.joe, + headline="Time to reform copyright", + pub_date=datetime(2006, 6, 16, 13, 00, 11, 345) + ) + self.a2.save() + self.a2.categories = [music, op_ed] + + def test_serialize(self): + """Tests that basic serialization works.""" + serial_str = serializers.serialize(self.serializer_name, + Article.objects.all()) + self.assertTrue(self._validate_output(serial_str)) + + def test_serializer_roundtrip(self): + """Tests that serialized content can be deserialized.""" + serial_str = serializers.serialize(self.serializer_name, + Article.objects.all()) + models = list(serializers.deserialize(self.serializer_name, serial_str)) + self.assertEqual(len(models), 2) + + def test_altering_serialized_output(self): + """ + Tests the ability to create new objects by + modifying serialized content. + """ + old_headline = "Poker has no place on ESPN" + new_headline = "Poker has no place on television" + serial_str = serializers.serialize(self.serializer_name, + Article.objects.all()) + serial_str = serial_str.replace(old_headline, new_headline) + models = list(serializers.deserialize(self.serializer_name, serial_str)) + + # Prior to saving, old headline is in place + self.assertTrue(Article.objects.filter(headline=old_headline)) + self.assertFalse(Article.objects.filter(headline=new_headline)) + + for model in models: + model.save() + + # After saving, new headline is in place + self.assertTrue(Article.objects.filter(headline=new_headline)) + self.assertFalse(Article.objects.filter(headline=old_headline)) + + def test_one_to_one_as_pk(self): + """ + Tests that if you use your own primary key field + (such as a OneToOneField), it doesn't appear in the + serialized field list - it replaces the pk identifier. + """ + profile = AuthorProfile(author=self.joe, + date_of_birth=datetime(1970,1,1)) + profile.save() + serial_str = serializers.serialize(self.serializer_name, + AuthorProfile.objects.all()) + self.assertFalse(self._get_field_values(serial_str, 'author')) + + for obj in serializers.deserialize(self.serializer_name, serial_str): + self.assertEqual(obj.object.pk, self._comparison_value(self.joe.pk)) + + def test_serialize_field_subset(self): + """Tests that output can be restricted to a subset of fields""" + valid_fields = ('headline','pub_date') + invalid_fields = ("author", "categories") + serial_str = serializers.serialize(self.serializer_name, + Article.objects.all(), + fields=valid_fields) + for field_name in invalid_fields: + self.assertFalse(self._get_field_values(serial_str, field_name)) + + for field_name in valid_fields: + self.assertTrue(self._get_field_values(serial_str, field_name)) + + def test_serialize_unicode(self): + """Tests that unicode makes the roundtrip intact""" + actor_name = u"Za\u017c\u00f3\u0142\u0107" + movie_title = u'G\u0119\u015bl\u0105 ja\u017a\u0144' + ac = Actor(name=actor_name) + mv = Movie(title=movie_title, actor=ac) + ac.save() + mv.save() + + serial_str = serializers.serialize(self.serializer_name, [mv]) + self.assertEqual(self._get_field_values(serial_str, "title")[0], movie_title) + self.assertEqual(self._get_field_values(serial_str, "actor")[0], actor_name) + + obj_list = list(serializers.deserialize(self.serializer_name, serial_str)) + mv_obj = obj_list[0].object + self.assertEqual(mv_obj.title, movie_title) + + def test_serialize_with_null_pk(self): + """ + Tests that serialized data with no primary key results + in a model instance with no id + """ + category = Category(name="Reference") + serial_str = serializers.serialize(self.serializer_name, [category]) + pk_value = self._get_pk_values(serial_str)[0] + self.assertFalse(pk_value) + + cat_obj = list(serializers.deserialize(self.serializer_name, + serial_str))[0].object + self.assertEqual(cat_obj.id, None) + + def test_float_serialization(self): + """Tests that float values serialize and deserialize intact""" + sc = Score(score=3.4) + sc.save() + serial_str = serializers.serialize(self.serializer_name, [sc]) + deserial_objs = list(serializers.deserialize(self.serializer_name, + serial_str)) + self.assertEqual(deserial_objs[0].object.score, Approximate(3.4, places=1)) + + def test_custom_field_serialization(self): + """Tests that custom fields serialize and deserialize intact""" + team_str = "Spartak Moskva" + player = Player() + player.name = "Soslan Djanaev" + player.rank = 1 + player.team = Team(team_str) + player.save() + serial_str = serializers.serialize(self.serializer_name, + Player.objects.all()) + team = self._get_field_values(serial_str, "team") + self.assertTrue(team) + self.assertEqual(team[0], team_str) + + deserial_objs = list(serializers.deserialize(self.serializer_name, serial_str)) + self.assertEqual(deserial_objs[0].object.team.to_string(), + player.team.to_string()) + + def test_pre_1000ad_date(self): + """Tests that year values before 1000AD are properly formatted""" + # Regression for #12524 -- dates before 1000AD get prefixed + # 0's on the year + a = Article.objects.create( + author = self.jane, + headline = "Nobody remembers the early years", + pub_date = datetime(1, 2, 3, 4, 5, 6)) + + serial_str = serializers.serialize(self.serializer_name, [a]) + date_values = self._get_field_values(serial_str, "pub_date") + self.assertEquals(date_values[0], "0001-02-03 04:05:06") + + def test_pkless_serialized_strings(self): + """ + Tests that serialized strings without PKs + can be turned into models + """ + deserial_objs = list(serializers.deserialize(self.serializer_name, + self.pkless_str)) + for obj in deserial_objs: + self.assertFalse(obj.object.id) + obj.save() + self.assertEqual(Category.objects.all().count(), 4) + + +class SerializersTransactionTestBase(object): + def test_forward_refs(self): + """ + Tests that objects ids can be referenced before they are + defined in the serialization data. + """ + # The deserialization process needs to be contained + # within a transaction in order to test forward reference + # handling. + transaction.enter_transaction_management() + transaction.managed(True) + objs = serializers.deserialize(self.serializer_name, self.fwd_ref_str) + for obj in objs: + obj.save() + transaction.commit() + transaction.leave_transaction_management() + + for model_cls in (Category, Author, Article): + self.assertEqual(model_cls.objects.all().count(), 1) + art_obj = Article.objects.all()[0] + self.assertEqual(art_obj.categories.all().count(), 1) + self.assertEqual(art_obj.author.name, "Agnes") + + +class XmlSerializerTestCase(SerializersTestBase, TestCase): + serializer_name = "xml" + pkless_str = """ + + + Reference + +""" + + @staticmethod + def _comparison_value(value): + # The XML serializer handles everything as strings, so comparisons + # need to be performed on the stringified value + return unicode(value) + + @staticmethod + def _validate_output(serial_str): + try: + minidom.parseString(serial_str) + except Exception: + return False + else: + return True + + @staticmethod + def _get_pk_values(serial_str): + ret_list = [] + dom = minidom.parseString(serial_str) + fields = dom.getElementsByTagName("object") + for field in fields: + ret_list.append(field.getAttribute("pk")) + return ret_list + + @staticmethod + def _get_field_values(serial_str, field_name): + ret_list = [] + dom = minidom.parseString(serial_str) + fields = dom.getElementsByTagName("field") + for field in fields: + if field.getAttribute("name") == field_name: + temp = [] + for child in field.childNodes: + temp.append(child.nodeValue) + ret_list.append("".join(temp)) + return ret_list + +class XmlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): + serializer_name = "xml" + fwd_ref_str = """ + + + 1 + Forward references pose no problem + 2006-06-16 15:00:00 + + + + + + Agnes + + + Reference +""" + + +class JsonSerializerTestCase(SerializersTestBase, TestCase): + serializer_name = "json" + pkless_str = """[{"pk": null, "model": "serializers.category", "fields": {"name": "Reference"}}]""" + + @staticmethod + def _validate_output(serial_str): + try: + simplejson.loads(serial_str) + except Exception: + return False + else: + return True + + @staticmethod + def _get_pk_values(serial_str): + ret_list = [] + serial_list = simplejson.loads(serial_str) + for obj_dict in serial_list: + ret_list.append(obj_dict["pk"]) + return ret_list + + @staticmethod + def _get_field_values(serial_str, field_name): + ret_list = [] + serial_list = simplejson.loads(serial_str) + for obj_dict in serial_list: + if field_name in obj_dict["fields"]: + ret_list.append(obj_dict["fields"][field_name]) + return ret_list + +class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): + serializer_name = "json" + fwd_ref_str = """[ + { + "pk": 1, + "model": "serializers.article", + "fields": { + "headline": "Forward references pose no problem", + "pub_date": "2006-06-16 15:00:00", + "categories": [1], + "author": 1 + } + }, + { + "pk": 1, + "model": "serializers.category", + "fields": { + "name": "Reference" + } + }, + { + "pk": 1, + "model": "serializers.author", + "fields": { + "name": "Agnes" + } + }]""" + +try: + import yaml +except ImportError: + pass +else: + class YamlSerializerTestCase(SerializersTestBase, TestCase): + serializer_name = "yaml" + fwd_ref_str = """- fields: + headline: Forward references pose no problem + pub_date: 2006-06-16 15:00:00 + categories: [1] + author: 1 + pk: 1 + model: serializers.article +- fields: + name: Reference + pk: 1 + model: serializers.category +- fields: + name: Agnes + pk: 1 + model: serializers.author""" + + pkless_str = """- fields: + name: Reference + pk: null + model: serializers.category""" + + @staticmethod + def _validate_output(serial_str): + try: + yaml.load(StringIO(serial_str)) + except Exception: + return False + else: + return True + + @staticmethod + def _get_pk_values(serial_str): + ret_list = [] + stream = StringIO(serial_str) + for obj_dict in yaml.load(stream): + ret_list.append(obj_dict["pk"]) + return ret_list + + @staticmethod + def _get_field_values(serial_str, field_name): + ret_list = [] + stream = StringIO(serial_str) + for obj_dict in yaml.load(stream): + if "fields" in obj_dict and field_name in obj_dict["fields"]: + field_value = obj_dict["fields"][field_name] + # yaml.load will return non-string objects for some + # of the fields we are interested in, this ensures that + # everything comes back as a string + if isinstance(field_value, basestring): + ret_list.append(field_value) + else: + ret_list.append(str(field_value)) + return ret_list + + class YamlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): + serializer_name = "yaml" + fwd_ref_str = """- fields: + headline: Forward references pose no problem + pub_date: 2006-06-16 15:00:00 + categories: [1] + author: 1 + pk: 1 + model: serializers.article +- fields: + name: Reference + pk: 1 + model: serializers.category +- fields: + name: Agnes + pk: 1 + model: serializers.author""" From 804719ff23a8fc80b871900663b99085021d9b1b Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 21 Sep 2010 19:33:53 +0000 Subject: [PATCH 187/902] [1.2.X] Fixed #12019 - backwards compatibility issues with cache_page decorator. Thanks to rokclimb15 for the report, and j4mie/rokclimb15 for the patches. Backport of [13864] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13865 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/decorators/cache.py | 13 +++++++++++-- tests/regressiontests/decorators/tests.py | 4 ++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py index 2296d14daaa8..bba7cd962f7a 100644 --- a/django/views/decorators/cache.py +++ b/django/views/decorators/cache.py @@ -33,6 +33,10 @@ def cache_page(*args, **kwargs): # my_view = cache_page(123, key_prefix="foo")(my_view) # and possibly this way (?): # my_view = cache_page(123, my_view) + # and also this way: + # my_view = cache_page(my_view) + # and also this way: + # my_view = cache_page()(my_view) # We also add some asserts to give better error messages in case people are # using other ways to call cache_page that no longer work. @@ -45,9 +49,14 @@ def cache_page(*args, **kwargs): elif callable(args[1]): return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix)(args[1]) else: - assert False, "cache_page must be passed either a single argument (timeout) or a view function and a timeout" + assert False, "cache_page must be passed a view function if called with two arguments" + elif len(args) == 1: + if callable(args[0]): + return decorator_from_middleware_with_args(CacheMiddleware)(key_prefix=key_prefix)(args[0]) + else: + return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix) else: - return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix) + return decorator_from_middleware_with_args(CacheMiddleware)(key_prefix=key_prefix) def cache_control(**kwargs): diff --git a/tests/regressiontests/decorators/tests.py b/tests/regressiontests/decorators/tests.py index 7855fef63861..ea2e10e965ea 100644 --- a/tests/regressiontests/decorators/tests.py +++ b/tests/regressiontests/decorators/tests.py @@ -112,6 +112,10 @@ def my_view(request): self.assertEqual(my_view_cached(HttpRequest()), "response") my_view_cached2 = cache_page(my_view, 123, key_prefix="test") self.assertEqual(my_view_cached2(HttpRequest()), "response") + my_view_cached3 = cache_page(my_view) + self.assertEqual(my_view_cached3(HttpRequest()), "response") + my_view_cached4 = cache_page()(my_view) + self.assertEqual(my_view_cached4(HttpRequest()), "response") # For testing method_decorator, a decorator that assumes a single argument. From 5f4fe3cdfc720c46baf37a2453de77545d64535a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 26 Sep 2010 09:56:37 +0000 Subject: [PATCH 188/902] [1.2.X] Migrated the custom_managers_regress doctests. Thanks to Paul McMillan. Backport of r13866 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13867 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../custom_managers_regress/models.py | 42 ----------------- .../custom_managers_regress/tests.py | 47 +++++++++++++++++++ 2 files changed, 47 insertions(+), 42 deletions(-) create mode 100644 tests/regressiontests/custom_managers_regress/tests.py diff --git a/tests/regressiontests/custom_managers_regress/models.py b/tests/regressiontests/custom_managers_regress/models.py index 898730ea9a49..747972b4413a 100644 --- a/tests/regressiontests/custom_managers_regress/models.py +++ b/tests/regressiontests/custom_managers_regress/models.py @@ -38,45 +38,3 @@ class OneToOneRestrictedModel(models.Model): def __unicode__(self): return self.name - -__test__ = {"tests": """ -Even though the default manager filters out some records, we must still be able -to save (particularly, save by updating existing records) those filtered -instances. This is a regression test for #8990, #9527 ->>> related = RelatedModel.objects.create(name="xyzzy") ->>> obj = RestrictedModel.objects.create(name="hidden", related=related) ->>> obj.name = "still hidden" ->>> obj.save() - -# If the hidden object wasn't seen during the save process, there would now be -# two objects in the database. ->>> RestrictedModel.plain_manager.count() -1 - -Deleting related objects should also not be distracted by a restricted manager -on the related object. This is a regression test for #2698. ->>> RestrictedModel.plain_manager.all().delete() ->>> for name, public in (('one', True), ('two', False), ('three', False)): -... _ = RestrictedModel.objects.create(name=name, is_public=public, related=related) - -# Reload the RelatedModel instance, just to avoid any instance artifacts. ->>> obj = RelatedModel.objects.get(name="xyzzy") ->>> obj.delete() - -# All of the RestrictedModel instances should have been deleted, since they -# *all* pointed to the RelatedModel. If the default manager is used, only the -# public one will be deleted. ->>> RestrictedModel.plain_manager.all() -[] - -# The same test case as the last one, but for one-to-one models, which are -# implemented slightly different internally, so it's a different code path. ->>> obj = RelatedModel.objects.create(name="xyzzy") ->>> _ = OneToOneRestrictedModel.objects.create(name="foo", is_public=False, related=obj) ->>> obj = RelatedModel.objects.get(name="xyzzy") ->>> obj.delete() ->>> OneToOneRestrictedModel.plain_manager.all() -[] - -""" -} diff --git a/tests/regressiontests/custom_managers_regress/tests.py b/tests/regressiontests/custom_managers_regress/tests.py new file mode 100644 index 000000000000..6dd668a13d5a --- /dev/null +++ b/tests/regressiontests/custom_managers_regress/tests.py @@ -0,0 +1,47 @@ +from django.test import TestCase + +from models import RelatedModel, RestrictedModel, OneToOneRestrictedModel + +class CustomManagersRegressTestCase(TestCase): + def test_filtered_default_manager(self): + """Even though the default manager filters out some records, + we must still be able to save (particularly, save by updating + existing records) those filtered instances. This is a + regression test for #8990, #9527""" + related = RelatedModel.objects.create(name="xyzzy") + obj = RestrictedModel.objects.create(name="hidden", related=related) + obj.name = "still hidden" + obj.save() + + # If the hidden object wasn't seen during the save process, + # there would now be two objects in the database. + self.assertEqual(RestrictedModel.plain_manager.count(), 1) + + def test_delete_related_on_filtered_manager(self): + """Deleting related objects should also not be distracted by a + restricted manager on the related object. This is a regression + test for #2698.""" + related = RelatedModel.objects.create(name="xyzzy") + + for name, public in (('one', True), ('two', False), ('three', False)): + RestrictedModel.objects.create(name=name, is_public=public, related=related) + + obj = RelatedModel.objects.get(name="xyzzy") + obj.delete() + + # All of the RestrictedModel instances should have been + # deleted, since they *all* pointed to the RelatedModel. If + # the default manager is used, only the public one will be + # deleted. + self.assertEqual(len(RestrictedModel.plain_manager.all()), 0) + + def test_delete_one_to_one_manager(self): + # The same test case as the last one, but for one-to-one + # models, which are implemented slightly different internally, + # so it's a different code path. + obj = RelatedModel.objects.create(name="xyzzy") + OneToOneRestrictedModel.objects.create(name="foo", is_public=False, related=obj) + obj = RelatedModel.objects.get(name="xyzzy") + obj.delete() + self.assertEqual(len(OneToOneRestrictedModel.plain_manager.all()), 0) + From c37add7c473e8893af17a39745525364bf79f3dc Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 26 Sep 2010 15:12:38 +0000 Subject: [PATCH 189/902] [1.2.X] Fixed #7535 -- Correctly set Content-Encoding header in static files serving view. Thanks for the report and patch, Kevin Hunter. Backport from trunk (r13868). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13869 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/static.py | 5 ++++- tests/regressiontests/views/media/file.txt.gz | Bin 0 -> 51 bytes tests/regressiontests/views/tests/static.py | 8 +++++--- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 tests/regressiontests/views/media/file.txt.gz diff --git a/django/views/static.py b/django/views/static.py index d9117f282ed3..a2990f1d1df1 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -56,7 +56,8 @@ def serve(request, path, document_root=None, show_indexes=False): raise Http404('"%s" does not exist' % fullpath) # Respect the If-Modified-Since header. statobj = os.stat(fullpath) - mimetype = mimetypes.guess_type(fullpath)[0] or 'application/octet-stream' + mimetype, encoding = mimetypes.guess_type(fullpath) + mimetype = mimetype or 'application/octet-stream' if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'), statobj[stat.ST_MTIME], statobj[stat.ST_SIZE]): return HttpResponseNotModified(mimetype=mimetype) @@ -64,6 +65,8 @@ def serve(request, path, document_root=None, show_indexes=False): response = HttpResponse(contents, mimetype=mimetype) response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) response["Content-Length"] = len(contents) + if encoding: + response["Content-Encoding"] = encoding return response DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ diff --git a/tests/regressiontests/views/media/file.txt.gz b/tests/regressiontests/views/media/file.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..0ee7d18311e16da167d42c12dab43af90b2e8abd GIT binary patch literal 51 zcmb2|=HO6B*yP2)oR*oBs#j7`!ccrB*ki578Ql|}p(lKOPx^#-oIc}um6;(Sci%EG H1_lNI)m##& literal 0 HcmV?d00001 diff --git a/tests/regressiontests/views/tests/static.py b/tests/regressiontests/views/tests/static.py index d7e87d19d2dd..fdd31b466090 100644 --- a/tests/regressiontests/views/tests/static.py +++ b/tests/regressiontests/views/tests/static.py @@ -1,3 +1,4 @@ +import mimetypes from os import path from django.test import TestCase @@ -8,12 +9,13 @@ class StaticTests(TestCase): def test_serve(self): "The static view can serve static media" - media_files = ['file.txt',] + media_files = ['file.txt', 'file.txt.gz'] for filename in media_files: response = self.client.get('/views/site_media/%s' % filename) - file = open(path.join(media_dir, filename)) - self.assertEquals(file.read(), response.content) + file_path = path.join(media_dir, filename) + self.assertEquals(open(file_path).read(), response.content) self.assertEquals(len(response.content), int(response['Content-Length'])) + self.assertEquals(mimetypes.guess_type(file_path)[1], response.get('Content-Encoding', None)) def test_unknown_mime_type(self): response = self.client.get('/views/site_media/file.unknown') From 4d966ee2021e0ba982dbb0407128aec21480014e Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 26 Sep 2010 20:46:37 +0000 Subject: [PATCH 190/902] Fixed #12544 and #13600 -- Fixed static files serving view to catch invalid date from If-Modified-Since header. Thanks akaihola and SmileyChris for patches. Backport from trunk (r13870). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13871 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/static.py | 2 +- tests/regressiontests/views/tests/static.py | 34 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/django/views/static.py b/django/views/static.py index a2990f1d1df1..cc8821187600 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -135,6 +135,6 @@ def was_modified_since(header=None, mtime=0, size=0): raise ValueError if mtime > header_mtime: raise ValueError - except (AttributeError, ValueError): + except (AttributeError, ValueError, OverflowError): return True return False diff --git a/tests/regressiontests/views/tests/static.py b/tests/regressiontests/views/tests/static.py index fdd31b466090..25153e86b03d 100644 --- a/tests/regressiontests/views/tests/static.py +++ b/tests/regressiontests/views/tests/static.py @@ -2,6 +2,7 @@ from os import path from django.test import TestCase +from django.http import HttpResponseNotModified from regressiontests.views.urls import media_dir class StaticTests(TestCase): @@ -27,3 +28,36 @@ def test_copes_with_empty_path_component(self): file = open(path.join(media_dir, file_name)) self.assertEquals(file.read(), response.content) + def test_is_modified_since(self): + file_name = 'file.txt' + response = self.client.get( + '/views/site_media/%s' % file_name, + HTTP_IF_MODIFIED_SINCE='Thu, 1 Jan 1970 00:00:00 GMT') + file = open(path.join(media_dir, file_name)) + self.assertEquals(file.read(), response.content) + + def test_not_modified_since(self): + file_name = 'file.txt' + response = self.client.get( + '/views/site_media/%s' % file_name, + HTTP_IF_MODIFIED_SINCE='Mon, 18 Jan 2038 05:14:07 UTC' + # This is 24h before max Unix time. Remember to fix Django and + # update this test well before 2038 :) + ) + self.assertTrue(isinstance(response, HttpResponseNotModified)) + + def test_invalid_if_modified_since(self): + """Handle bogus If-Modified-Since values gracefully + + Assume that a file is modified since an invalid timestamp as per RFC + 2616, section 14.25. + """ + file_name = 'file.txt' + invalid_date = 'Mon, 28 May 999999999999 28:25:26 GMT' + response = self.client.get('/views/site_media/%s' % file_name, + HTTP_IF_MODIFIED_SINCE=invalid_date) + file = open(path.join(media_dir, file_name)) + self.assertEquals(file.read(), response.content) + self.assertEquals(len(response.content), + int(response['Content-Length'])) + From 7b556c2f1bb6e82f5e2a8cd353be3d8f74ba2a11 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 26 Sep 2010 20:53:45 +0000 Subject: [PATCH 191/902] [1.2.X] Fixed #14122 -- Added example for builtin yesno template filter. Thanks, idahogray. Backport from trunk (r13872). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13873 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/templates/builtins.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 85c0b6dc269c..a20ab170c08f 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2058,6 +2058,10 @@ yesno Given a string mapping values for true, false and (optionally) None, returns one of those strings according to the value: +For example:: + + {{ value|yesno:"yeah,no,maybe" }} + ========== ====================== ================================== Value Argument Outputs ========== ====================== ================================== From ad3b281c97cc86d0f82f0d06399dcffd77a7e9b6 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 26 Sep 2010 20:57:00 +0000 Subject: [PATCH 192/902] [1.2.X] Fixed #14271 -- Use absolute path for admin media tests. Thanks, Alex Gaynor. Backport from trunk (r13874). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13875 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/servers/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py index 7621439032c9..47639828b0ca 100644 --- a/tests/regressiontests/servers/tests.py +++ b/tests/regressiontests/servers/tests.py @@ -13,8 +13,9 @@ class AdminMediaHandlerTests(TestCase): def setUp(self): - self.admin_media_file_path = \ + self.admin_media_file_path = os.path.abspath( os.path.join(django.__path__[0], 'contrib', 'admin', 'media') + ) self.handler = AdminMediaHandler(WSGIHandler()) def test_media_urls(self): From bb857bf4d4eff0567cefcea17861b3728fc02b55 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 26 Sep 2010 23:00:44 +0000 Subject: [PATCH 193/902] [1.2.X] Fixed #14280 -- Fixed duplicate import of deepcopy. Thanks, Carl Meyer. Backport from trunk (r13877). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13878 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/query.py | 1 - 1 file changed, 1 deletion(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index d9fbd9b8cb43..def50934b73e 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -2,7 +2,6 @@ The main QuerySet implementation. This provides the public API for the ORM. """ -from copy import deepcopy from itertools import izip from django.db import connections, router, transaction, IntegrityError From 3640abd9763dcb8002c024a2f1d749a247481021 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:25:57 +0000 Subject: [PATCH 194/902] [1.2.X] Migrated admin_inlines doctest. Thanks to Sebastian Hillig. Backport of r13880 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13900 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/admin_inlines/models.py | 22 ------------------- tests/regressiontests/admin_inlines/tests.py | 21 ++++++++++++++++++ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py index 5a12e0743c13..4e5c4e3ae348 100644 --- a/tests/regressiontests/admin_inlines/models.py +++ b/tests/regressiontests/admin_inlines/models.py @@ -123,25 +123,3 @@ class InlineWeakness(admin.TabularInline): extra = 1 admin.site.register(Fashionista, inlines=[InlineWeakness]) - - -__test__ = {'API_TESTS': """ - -# Regression test for #9362 - ->>> sally = Teacher.objects.create(name='Sally') ->>> john = Parent.objects.create(name='John') ->>> joe = Child.objects.create(name='Joe', teacher=sally, parent=john) - -The problem depends only on InlineAdminForm and its "original" argument, so -we can safely set the other arguments to None/{}. We just need to check that -the content_type argument of Child isn't altered by the internals of the -inline form. - ->>> from django.contrib.admin.helpers import InlineAdminForm ->>> iaf = InlineAdminForm(None, None, {}, {}, joe) ->>> iaf.original - - -""" -} diff --git a/tests/regressiontests/admin_inlines/tests.py b/tests/regressiontests/admin_inlines/tests.py index c27873ceecbc..c0c2bb725823 100644 --- a/tests/regressiontests/admin_inlines/tests.py +++ b/tests/regressiontests/admin_inlines/tests.py @@ -1,9 +1,13 @@ from django.test import TestCase +from django.contrib.admin.helpers import InlineAdminForm +from django.contrib.contenttypes.models import ContentType # local test models from models import Holder, Inner, InnerInline from models import Holder2, Inner2, Holder3, Inner3 from models import Person, OutfitItem, Fashionista +from models import Teacher, Parent, Child + class TestInline(TestCase): fixtures = ['admin-views-users.xml'] @@ -100,3 +104,20 @@ def test_all_inline_media(self): response = self.client.get(change_url) self.assertContains(response, 'my_awesome_admin_scripts.js') self.assertContains(response, 'my_awesome_inline_scripts.js') + +class TestInlineAdminForm(TestCase): + + def test_immutable_content_type(self): + """Regression for #9362 + The problem depends only on InlineAdminForm and its "original" + argument, so we can safely set the other arguments to None/{}. We just + need to check that the content_type argument of Child isn't altered by + the internals of the inline form.""" + + sally = Teacher.objects.create(name='Sally') + john = Parent.objects.create(name='John') + joe = Child.objects.create(name='Joe', teacher=sally, parent=john) + + iaf = InlineAdminForm(None, None, {}, {}, joe) + parent_ct = ContentType.objects.get_for_model(Parent) + self.assertEqual(iaf.original.content_type, parent_ct) From 3a66f15a68c589de07251f3de6e155ddf0422af2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:26:29 +0000 Subject: [PATCH 195/902] [1.2.X] Migrated admin_ordering doctests. Thanks to Sebastian Hillig. Backport of r13881 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13901 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../regressiontests/admin_ordering/models.py | 36 ----------------- tests/regressiontests/admin_ordering/tests.py | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 36 deletions(-) create mode 100644 tests/regressiontests/admin_ordering/tests.py diff --git a/tests/regressiontests/admin_ordering/models.py b/tests/regressiontests/admin_ordering/models.py index 601f06bb0aeb..ad63685b2f3a 100644 --- a/tests/regressiontests/admin_ordering/models.py +++ b/tests/regressiontests/admin_ordering/models.py @@ -8,39 +8,3 @@ class Band(models.Model): class Meta: ordering = ('name',) - -__test__ = {'API_TESTS': """ - -Let's make sure that ModelAdmin.queryset uses the ordering we define in -ModelAdmin rather that ordering defined in the model's inner Meta -class. - ->>> from django.contrib.admin.options import ModelAdmin - ->>> b1 = Band(name='Aerosmith', bio='', rank=3) ->>> b1.save() ->>> b2 = Band(name='Radiohead', bio='', rank=1) ->>> b2.save() ->>> b3 = Band(name='Van Halen', bio='', rank=2) ->>> b3.save() - -The default ordering should be by name, as specified in the inner Meta class. - ->>> ma = ModelAdmin(Band, None) ->>> [b.name for b in ma.queryset(None)] -[u'Aerosmith', u'Radiohead', u'Van Halen'] - - -Let's use a custom ModelAdmin that changes the ordering, and make sure it -actually changes. - ->>> class BandAdmin(ModelAdmin): -... ordering = ('rank',) # default ordering is ('name',) -... - ->>> ma = BandAdmin(Band, None) ->>> [b.name for b in ma.queryset(None)] -[u'Radiohead', u'Van Halen', u'Aerosmith'] - -""" -} diff --git a/tests/regressiontests/admin_ordering/tests.py b/tests/regressiontests/admin_ordering/tests.py new file mode 100644 index 000000000000..f63f202ce165 --- /dev/null +++ b/tests/regressiontests/admin_ordering/tests.py @@ -0,0 +1,39 @@ +from django.test import TestCase +from django.contrib.admin.options import ModelAdmin + +from models import Band + +class TestAdminOrdering(TestCase): + """ + Let's make sure that ModelAdmin.queryset uses the ordering we define in + ModelAdmin rather that ordering defined in the model's inner Meta + class. + """ + + def setUp(self): + b1 = Band(name='Aerosmith', bio='', rank=3) + b1.save() + b2 = Band(name='Radiohead', bio='', rank=1) + b2.save() + b3 = Band(name='Van Halen', bio='', rank=2) + b3.save() + + def test_default_ordering(self): + """ + The default ordering should be by name, as specified in the inner Meta + class. + """ + ma = ModelAdmin(Band, None) + names = [b.name for b in ma.queryset(None)] + self.assertEqual([u'Aerosmith', u'Radiohead', u'Van Halen'], names) + + def test_specified_ordering(self): + """ + Let's use a custom ModelAdmin that changes the ordering, and make sure + it actually changes. + """ + class BandAdmin(ModelAdmin): + ordering = ('rank',) # default ordering is ('name',) + ma = BandAdmin(Band, None) + names = [b.name for b in ma.queryset(None)] + self.assertEqual([u'Radiohead', u'Van Halen', u'Aerosmith'], names) From 6ee04e0f97b95c19c8b8cbb80c029f7d55df76c3 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:26:57 +0000 Subject: [PATCH 196/902] [1.2.X] Migrated admin_registration doctests. Thanks to Sebastian Hillig. Backport of r13882 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13902 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admin_registration/models.py | 53 ------------------ .../admin_registration/tests.py | 54 +++++++++++++++++++ 2 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 tests/regressiontests/admin_registration/tests.py diff --git a/tests/regressiontests/admin_registration/models.py b/tests/regressiontests/admin_registration/models.py index 35cf8afce8a8..4a2d4e9614b2 100644 --- a/tests/regressiontests/admin_registration/models.py +++ b/tests/regressiontests/admin_registration/models.py @@ -3,62 +3,9 @@ """ from django.db import models -from django.contrib import admin class Person(models.Model): name = models.CharField(max_length=200) class Place(models.Model): name = models.CharField(max_length=200) - -__test__ = {'API_TESTS':""" - - -# Bare registration ->>> site = admin.AdminSite() ->>> site.register(Person) ->>> site._registry[Person] - - -# Registration with a ModelAdmin ->>> site = admin.AdminSite() ->>> class NameAdmin(admin.ModelAdmin): -... list_display = ['name'] -... save_on_top = True - ->>> site.register(Person, NameAdmin) ->>> site._registry[Person] - - -# You can't register the same model twice ->>> site.register(Person) -Traceback (most recent call last): - ... -AlreadyRegistered: The model Person is already registered - -# Registration using **options ->>> site = admin.AdminSite() ->>> site.register(Person, search_fields=['name']) ->>> site._registry[Person].search_fields -['name'] - -# With both admin_class and **options the **options override the fields in -# the admin class. ->>> site = admin.AdminSite() ->>> site.register(Person, NameAdmin, search_fields=["name"], list_display=['__str__']) ->>> site._registry[Person].search_fields -['name'] ->>> site._registry[Person].list_display -['action_checkbox', '__str__'] ->>> site._registry[Person].save_on_top -True - -# You can also register iterables instead of single classes -- a nice shortcut ->>> site = admin.AdminSite() ->>> site.register([Person, Place], search_fields=['name']) ->>> site._registry[Person] - ->>> site._registry[Place] - - -"""} diff --git a/tests/regressiontests/admin_registration/tests.py b/tests/regressiontests/admin_registration/tests.py new file mode 100644 index 000000000000..e2a5d7e01791 --- /dev/null +++ b/tests/regressiontests/admin_registration/tests.py @@ -0,0 +1,54 @@ +from django.test import TestCase + +from django.contrib import admin + +from models import Person, Place + +class NameAdmin(admin.ModelAdmin): + list_display = ['name'] + save_on_top = True + +class TestRegistration(TestCase): + def setUp(self): + self.site = admin.AdminSite() + + def test_bare_registration(self): + self.site.register(Person) + self.assertTrue( + isinstance(self.site._registry[Person], admin.options.ModelAdmin) + ) + + def test_registration_with_model_admin(self): + self.site.register(Person, NameAdmin) + self.assertTrue( + isinstance(self.site._registry[Person], NameAdmin) + ) + + def test_prevent_double_registration(self): + self.site.register(Person) + self.assertRaises(admin.sites.AlreadyRegistered, + self.site.register, + Person) + + def test_registration_with_star_star_options(self): + self.site.register(Person, search_fields=['name']) + self.assertEqual(self.site._registry[Person].search_fields, ['name']) + + def test_star_star_overrides(self): + self.site.register(Person, NameAdmin, + search_fields=["name"], list_display=['__str__']) + self.assertEqual(self.site._registry[Person].search_fields, ['name']) + self.assertEqual(self.site._registry[Person].list_display, + ['action_checkbox', '__str__']) + self.assertTrue(self.site._registry[Person].save_on_top) + + def test_iterable_registration(self): + self.site.register([Person, Place], search_fields=['name']) + self.assertTrue( + isinstance(self.site._registry[Person], admin.options.ModelAdmin) + ) + self.assertEqual(self.site._registry[Person].search_fields, ['name']) + self.assertTrue( + isinstance(self.site._registry[Place], admin.options.ModelAdmin) + ) + self.assertEqual(self.site._registry[Place].search_fields, ['name']) From d99f19fb6ae069f8c39d4fab837746f7fb3bef7a Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 27 Sep 2010 15:27:07 +0000 Subject: [PATCH 197/902] =?UTF-8?q?[1.2.X]=20Fixed=20#14290=20--=20Made=20?= =?UTF-8?q?format=20localization=20faster=20by=20caching=20the=20format=20?= =?UTF-8?q?modules.=20Thanks,=20Teemu=20Kurppa=20and=20Anssi=20K=C3=A4?= =?UTF-8?q?=C3=A4ri=C3=A4inen=20for=20the=20report=20and=20initial=20patch?= =?UTF-8?q?es.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backport from trunk (r13898). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13903 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/formats.py | 64 +++++++++++-------- django/utils/numberformat.py | 12 +++- tests/regressiontests/i18n/other/__init__.py | 0 .../i18n/other/locale/__init__.py | 0 .../i18n/other/locale/de/__init__.py | 0 .../i18n/other/locale/de/formats.py | 0 tests/regressiontests/i18n/tests.py | 21 +++++- 7 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 tests/regressiontests/i18n/other/__init__.py create mode 100644 tests/regressiontests/i18n/other/locale/__init__.py create mode 100644 tests/regressiontests/i18n/other/locale/de/__init__.py create mode 100644 tests/regressiontests/i18n/other/locale/de/formats.py diff --git a/django/utils/formats.py b/django/utils/formats.py index 372998f22157..b26065c7c2be 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -7,29 +7,36 @@ from django.utils.encoding import smart_str from django.utils import dateformat, numberformat, datetime_safe +# format_cache is a mapping from (format_type, lang) to the format string. +# By using the cache, it is possible to avoid running get_format_modules +# repeatedly. +_format_cache = {} +_format_modules_cache = {} + +def iter_format_modules(lang): + """ + Does the heavy lifting of finding format modules. + """ + if check_for_language(lang) or settings.USE_L10N: + format_locations = ['django.conf.locale.%s'] + if settings.FORMAT_MODULE_PATH: + format_locations.append(settings.FORMAT_MODULE_PATH + '.%s') + format_locations.reverse() + locale = to_locale(lang) + locales = set((locale, locale.split('_')[0])) + for location in format_locations: + for loc in locales: + try: + yield import_module('.formats', location % loc) + except ImportError: + pass + def get_format_modules(reverse=False): """ - Returns an iterator over the format modules found in the project and Django + Returns an iterator over the format modules found """ - modules = [] - if not check_for_language(get_language()) or not settings.USE_L10N: - return modules - locale = to_locale(get_language()) - if settings.FORMAT_MODULE_PATH: - format_locations = [settings.FORMAT_MODULE_PATH + '.%s'] - else: - format_locations = [] - format_locations.append('django.conf.locale.%s') - for location in format_locations: - for l in (locale, locale.split('_')[0]): - try: - mod = import_module('.formats', location % l) - except ImportError: - pass - else: - # Don't return duplicates - if mod not in modules: - modules.append(mod) + lang = get_language() + modules = _format_modules_cache.setdefault(lang, list(iter_format_modules(lang))) if reverse: modules.reverse() return modules @@ -42,11 +49,18 @@ def get_format(format_type): """ format_type = smart_str(format_type) if settings.USE_L10N: - for module in get_format_modules(): - try: - return getattr(module, format_type) - except AttributeError: - pass + cache_key = (format_type, get_language()) + try: + return _format_cache[cache_key] or getattr(settings, format_type) + except KeyError: + for module in get_format_modules(): + try: + val = getattr(module, format_type) + _format_cache[cache_key] = val + return val + except AttributeError: + pass + _format_cache[cache_key] = None return getattr(settings, format_type) def date_format(value, format=None): diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index 129c27f4da19..069f49851bdd 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -1,4 +1,6 @@ from django.conf import settings +from django.utils.safestring import mark_safe + def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''): """ @@ -11,15 +13,20 @@ def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''): * thousand_sep: Thousand separator symbol (for example ",") """ + use_grouping = settings.USE_L10N and \ + settings.USE_THOUSAND_SEPARATOR and grouping + # Make the common case fast: + if isinstance(number, int) and not use_grouping and not decimal_pos: + return mark_safe(unicode(number)) # sign if float(number) < 0: sign = '-' else: sign = '' - # decimal part str_number = unicode(number) if str_number[0] == '-': str_number = str_number[1:] + # decimal part if '.' in str_number: int_part, dec_part = str_number.split('.') if decimal_pos: @@ -30,13 +37,12 @@ def format(number, decimal_sep, decimal_pos, grouping=0, thousand_sep=''): dec_part = dec_part + ('0' * (decimal_pos - len(dec_part))) if dec_part: dec_part = decimal_sep + dec_part # grouping - if settings.USE_L10N and settings.USE_THOUSAND_SEPARATOR and grouping: + if use_grouping: int_part_gd = '' for cnt, digit in enumerate(int_part[::-1]): if cnt and not cnt % grouping: int_part_gd += thousand_sep int_part_gd += digit int_part = int_part_gd[::-1] - return sign + int_part + dec_part diff --git a/tests/regressiontests/i18n/other/__init__.py b/tests/regressiontests/i18n/other/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/regressiontests/i18n/other/locale/__init__.py b/tests/regressiontests/i18n/other/locale/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/regressiontests/i18n/other/locale/de/__init__.py b/tests/regressiontests/i18n/other/locale/de/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/regressiontests/i18n/other/locale/de/formats.py b/tests/regressiontests/i18n/other/locale/de/formats.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 7e2e9f8228c7..75f8d2591700 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -8,10 +8,11 @@ from django.conf import settings from django.template import Template, Context from django.test import TestCase -from django.utils.formats import get_format, date_format, time_format, localize, localize_input +from django.utils.formats import get_format, date_format, time_format, localize, localize_input, iter_format_modules from django.utils.numberformat import format as nformat from django.utils.safestring import mark_safe, SafeString, SafeUnicode from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy, to_locale +from django.utils.importlib import import_module from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm @@ -423,6 +424,24 @@ def test_localized_input(self): finally: deactivate() + def test_iter_format_modules(self): + """ + Tests the iter_format_modules function. + """ + activate('de-at') + old_format_module_path = settings.FORMAT_MODULE_PATH + try: + settings.USE_L10N = True + de_format_mod = import_module('django.conf.locale.de.formats') + self.assertEqual(list(iter_format_modules('de')), [de_format_mod]) + settings.FORMAT_MODULE_PATH = 'regressiontests.i18n.other.locale' + test_de_format_mod = import_module('regressiontests.i18n.other.locale.de.formats') + self.assertEqual(list(iter_format_modules('de')), [test_de_format_mod, de_format_mod]) + finally: + settings.FORMAT_MODULE_PATH = old_format_module_path + deactivate() + + class MiscTests(TestCase): def test_parse_spec_http_header(self): From e8ccba765cd9c2db624dcaf7fcc2cf638193f517 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:27:25 +0000 Subject: [PATCH 198/902] [1.2.X] Migrated admin_validation doctests. Thanks to Sebastian Hillig. Backport of r13883 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13904 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../admin_validation/models.py | 217 ---------------- .../regressiontests/admin_validation/tests.py | 235 +++++++++++++++++- 2 files changed, 232 insertions(+), 220 deletions(-) diff --git a/tests/regressiontests/admin_validation/models.py b/tests/regressiontests/admin_validation/models.py index d9275745388d..24387cc363a1 100644 --- a/tests/regressiontests/admin_validation/models.py +++ b/tests/regressiontests/admin_validation/models.py @@ -45,220 +45,3 @@ class Book(models.Model): class AuthorsBooks(models.Model): author = models.ForeignKey(Author) book = models.ForeignKey(Book) - - -__test__ = {'API_TESTS':""" - ->>> from django import forms ->>> from django.contrib import admin ->>> from django.contrib.admin.validation import validate, validate_inline - -# Regression test for #8027: custom ModelForms with fields/fieldsets - ->>> class SongForm(forms.ModelForm): -... pass - ->>> class ValidFields(admin.ModelAdmin): -... form = SongForm -... fields = ['title'] - ->>> class InvalidFields(admin.ModelAdmin): -... form = SongForm -... fields = ['spam'] - ->>> validate(ValidFields, Song) ->>> validate(InvalidFields, Song) -Traceback (most recent call last): - ... -ImproperlyConfigured: 'InvalidFields.fields' refers to field 'spam' that is missing from the form. - -# Tests for basic validation of 'exclude' option values (#12689) - ->>> class ExcludedFields1(admin.ModelAdmin): -... exclude = ('foo') - ->>> validate(ExcludedFields1, Book) -Traceback (most recent call last): - ... -ImproperlyConfigured: 'ExcludedFields1.exclude' must be a list or tuple. - ->>> class ExcludedFields2(admin.ModelAdmin): -... exclude = ('name', 'name') - ->>> validate(ExcludedFields2, Book) -Traceback (most recent call last): - ... -ImproperlyConfigured: There are duplicate field(s) in ExcludedFields2.exclude - ->>> class ExcludedFieldsInline(admin.TabularInline): -... model = Song -... exclude = ('foo') - ->>> class ExcludedFieldsAlbumAdmin(admin.ModelAdmin): -... model = Album -... inlines = [ExcludedFieldsInline] - ->>> validate(ExcludedFieldsAlbumAdmin, Album) -Traceback (most recent call last): - ... -ImproperlyConfigured: 'ExcludedFieldsInline.exclude' must be a list or tuple. - -# Regression test for #9932 - exclude in InlineModelAdmin -# should not contain the ForeignKey field used in ModelAdmin.model - ->>> class SongInline(admin.StackedInline): -... model = Song -... exclude = ['album'] - ->>> class AlbumAdmin(admin.ModelAdmin): -... model = Album -... inlines = [SongInline] - ->>> validate(AlbumAdmin, Album) -Traceback (most recent call last): - ... -ImproperlyConfigured: SongInline cannot exclude the field 'album' - this is the foreign key to the parent model Album. - -# Regression test for #11709 - when testing for fk excluding (when exclude is -# given) make sure fk_name is honored or things blow up when there is more -# than one fk to the parent model. - ->>> class TwoAlbumFKAndAnEInline(admin.TabularInline): -... model = TwoAlbumFKAndAnE -... exclude = ("e",) -... fk_name = "album1" - ->>> validate_inline(TwoAlbumFKAndAnEInline, None, Album) - -# Ensure inlines validate that they can be used correctly. - ->>> class TwoAlbumFKAndAnEInline(admin.TabularInline): -... model = TwoAlbumFKAndAnE - ->>> validate_inline(TwoAlbumFKAndAnEInline, None, Album) -Traceback (most recent call last): - ... -Exception: has more than 1 ForeignKey to - ->>> class TwoAlbumFKAndAnEInline(admin.TabularInline): -... model = TwoAlbumFKAndAnE -... fk_name = "album1" - ->>> validate_inline(TwoAlbumFKAndAnEInline, None, Album) - ->>> class SongAdmin(admin.ModelAdmin): -... readonly_fields = ("title",) - ->>> validate(SongAdmin, Song) - ->>> def my_function(obj): -... # does nothing -... pass ->>> class SongAdmin(admin.ModelAdmin): -... readonly_fields = (my_function,) - ->>> validate(SongAdmin, Song) - ->>> class SongAdmin(admin.ModelAdmin): -... readonly_fields = ("readonly_method_on_modeladmin",) -... -... def readonly_method_on_modeladmin(self, obj): -... # does nothing -... pass - ->>> validate(SongAdmin, Song) - ->>> class SongAdmin(admin.ModelAdmin): -... readonly_fields = ("readonly_method_on_model",) - ->>> validate(SongAdmin, Song) - ->>> class SongAdmin(admin.ModelAdmin): -... readonly_fields = ("title", "nonexistant") - ->>> validate(SongAdmin, Song) -Traceback (most recent call last): - ... -ImproperlyConfigured: SongAdmin.readonly_fields[1], 'nonexistant' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'. - ->>> class SongAdmin(admin.ModelAdmin): -... readonly_fields = ("title", "awesome_song") -... fields = ("album", "title", "awesome_song") - ->>> validate(SongAdmin, Song) -Traceback (most recent call last): - ... -ImproperlyConfigured: SongAdmin.readonly_fields[1], 'awesome_song' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'. - ->>> class SongAdmin(SongAdmin): -... def awesome_song(self, instance): -... if instance.title == "Born to Run": -... return "Best Ever!" -... return "Status unknown." - ->>> validate(SongAdmin, Song) - ->>> class SongAdmin(admin.ModelAdmin): -... readonly_fields = (lambda obj: "test",) - ->>> validate(SongAdmin, Song) - -# Regression test for #12203/#12237 - Fail more gracefully when a M2M field that -# specifies the 'through' option is included in the 'fields' or the 'fieldsets' -# ModelAdmin options. - ->>> class BookAdmin(admin.ModelAdmin): -... fields = ['authors'] - ->>> validate(BookAdmin, Book) -Traceback (most recent call last): - ... -ImproperlyConfigured: 'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model. - ->>> class FieldsetBookAdmin(admin.ModelAdmin): -... fieldsets = ( -... ('Header 1', {'fields': ('name',)}), -... ('Header 2', {'fields': ('authors',)}), -... ) - ->>> validate(FieldsetBookAdmin, Book) -Traceback (most recent call last): - ... -ImproperlyConfigured: 'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model. - ->>> class NestedFieldsetAdmin(admin.ModelAdmin): -... fieldsets = ( -... ('Main', {'fields': ('price', ('name', 'subtitle'))}), -... ) - ->>> validate(NestedFieldsetAdmin, Book) - -# Regression test for #12209 -- If the explicitly provided through model -# is specified as a string, the admin should still be able use -# Model.m2m_field.through - ->>> class AuthorsInline(admin.TabularInline): -... model = Book.authors.through - ->>> class BookAdmin(admin.ModelAdmin): -... inlines = [AuthorsInline] - -# If the through model is still a string (and hasn't been resolved to a model) -# the validation will fail. ->>> validate(BookAdmin, Book) - -# Regression for ensuring ModelAdmin.fields can contain non-model fields -# that broke with r11737 - ->>> class SongForm(forms.ModelForm): -... extra_data = forms.CharField() -... class Meta: -... model = Song - ->>> class FieldsOnFormOnlyAdmin(admin.ModelAdmin): -... form = SongForm -... fields = ['title', 'extra_data'] - ->>> validate(FieldsOnFormOnlyAdmin, Song) - -"""} diff --git a/tests/regressiontests/admin_validation/tests.py b/tests/regressiontests/admin_validation/tests.py index 9166360ae3d2..1872ca55e2d0 100644 --- a/tests/regressiontests/admin_validation/tests.py +++ b/tests/regressiontests/admin_validation/tests.py @@ -1,11 +1,30 @@ from django.contrib import admin -from django.contrib.admin.validation import validate +from django import forms +from django.contrib.admin.validation import validate, validate_inline, \ + ImproperlyConfigured from django.test import TestCase -from models import Song +from models import Song, Book, Album, TwoAlbumFKAndAnE +class SongForm(forms.ModelForm): + pass + +class ValidFields(admin.ModelAdmin): + form = SongForm + fields = ['title'] + +class InvalidFields(admin.ModelAdmin): + form = SongForm + fields = ['spam'] class ValidationTestCase(TestCase): + def assertRaisesMessage(self, exc, msg, func, *args, **kwargs): + try: + func(*args, **kwargs) + except Exception, e: + self.assertEqual(msg, str(e)) + self.assertTrue(isinstance(e, exc), "Expected %s, got %s" % (exc, type(e))) + def test_readonly_and_editable(self): class SongAdmin(admin.ModelAdmin): readonly_fields = ["original_release"] @@ -14,5 +33,215 @@ class SongAdmin(admin.ModelAdmin): "fields": ["title", "original_release"], }), ] - validate(SongAdmin, Song) + + def test_custom_modelforms_with_fields_fieldsets(self): + """ + # Regression test for #8027: custom ModelForms with fields/fieldsets + """ + validate(ValidFields, Song) + self.assertRaisesMessage(ImproperlyConfigured, + "'InvalidFields.fields' refers to field 'spam' that is missing from the form.", + validate, + InvalidFields, Song) + + def test_exclude_values(self): + """ + Tests for basic validation of 'exclude' option values (#12689) + """ + class ExcludedFields1(admin.ModelAdmin): + exclude = ('foo') + self.assertRaisesMessage(ImproperlyConfigured, + "'ExcludedFields1.exclude' must be a list or tuple.", + validate, + ExcludedFields1, Book) + + def test_exclude_duplicate_values(self): + class ExcludedFields2(admin.ModelAdmin): + exclude = ('name', 'name') + self.assertRaisesMessage(ImproperlyConfigured, + "There are duplicate field(s) in ExcludedFields2.exclude", + validate, + ExcludedFields2, Book) + + def test_exclude_in_inline(self): + class ExcludedFieldsInline(admin.TabularInline): + model = Song + exclude = ('foo') + + class ExcludedFieldsAlbumAdmin(admin.ModelAdmin): + model = Album + inlines = [ExcludedFieldsInline] + + self.assertRaisesMessage(ImproperlyConfigured, + "'ExcludedFieldsInline.exclude' must be a list or tuple.", + validate, + ExcludedFieldsAlbumAdmin, Album) + + def test_exclude_inline_model_admin(self): + """ + # Regression test for #9932 - exclude in InlineModelAdmin + # should not contain the ForeignKey field used in ModelAdmin.model + """ + class SongInline(admin.StackedInline): + model = Song + exclude = ['album'] + + class AlbumAdmin(admin.ModelAdmin): + model = Album + inlines = [SongInline] + + self.assertRaisesMessage(ImproperlyConfigured, + "SongInline cannot exclude the field 'album' - this is the foreign key to the parent model Album.", + validate, + AlbumAdmin, Album) + + def test_fk_exclusion(self): + """ + Regression test for #11709 - when testing for fk excluding (when exclude is + given) make sure fk_name is honored or things blow up when there is more + than one fk to the parent model. + """ + class TwoAlbumFKAndAnEInline(admin.TabularInline): + model = TwoAlbumFKAndAnE + exclude = ("e",) + fk_name = "album1" + validate_inline(TwoAlbumFKAndAnEInline, None, Album) + + def test_inline_self_validation(self): + class TwoAlbumFKAndAnEInline(admin.TabularInline): + model = TwoAlbumFKAndAnE + + self.assertRaisesMessage(Exception, + " has more than 1 ForeignKey to ", + validate_inline, + TwoAlbumFKAndAnEInline, None, Album) + + def test_inline_with_specified(self): + class TwoAlbumFKAndAnEInline(admin.TabularInline): + model = TwoAlbumFKAndAnE + fk_name = "album1" + validate_inline(TwoAlbumFKAndAnEInline, None, Album) + + def test_readonly(self): + class SongAdmin(admin.ModelAdmin): + readonly_fields = ("title",) + + validate(SongAdmin, Song) + + def test_readonly_on_method(self): + def my_function(obj): + pass + + class SongAdmin(admin.ModelAdmin): + readonly_fields = (my_function,) + + validate(SongAdmin, Song) + + def test_readonly_on_modeladmin(self): + class SongAdmin(admin.ModelAdmin): + readonly_fields = ("readonly_method_on_modeladmin",) + + def readonly_method_on_modeladmin(self, obj): + pass + + validate(SongAdmin, Song) + + def test_readonly_method_on_model(self): + class SongAdmin(admin.ModelAdmin): + readonly_fields = ("readonly_method_on_model",) + + validate(SongAdmin, Song) + + def test_nonexistant_field(self): + class SongAdmin(admin.ModelAdmin): + readonly_fields = ("title", "nonexistant") + + self.assertRaisesMessage(ImproperlyConfigured, + "SongAdmin.readonly_fields[1], 'nonexistant' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'.", + validate, + SongAdmin, Song) + + def test_extra(self): + class SongAdmin(admin.ModelAdmin): + def awesome_song(self, instance): + if instance.title == "Born to Run": + return "Best Ever!" + return "Status unknown." + validate(SongAdmin, Song) + + def test_readonly_lambda(self): + class SongAdmin(admin.ModelAdmin): + readonly_fields = (lambda obj: "test",) + + validate(SongAdmin, Song) + + def test_graceful_m2m_fail(self): + """ + Regression test for #12203/#12237 - Fail more gracefully when a M2M field that + specifies the 'through' option is included in the 'fields' or the 'fieldsets' + ModelAdmin options. + """ + + class BookAdmin(admin.ModelAdmin): + fields = ['authors'] + + self.assertRaisesMessage(ImproperlyConfigured, + "'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.", + validate, + BookAdmin, Book) + + def test_cannon_include_through(self): + class FieldsetBookAdmin(admin.ModelAdmin): + fieldsets = ( + ('Header 1', {'fields': ('name',)}), + ('Header 2', {'fields': ('authors',)}), + ) + self.assertRaisesMessage(ImproperlyConfigured, + "'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.", + validate, + FieldsetBookAdmin, Book) + + def test_nested_fieldsets(self): + class NestedFieldsetAdmin(admin.ModelAdmin): + fieldsets = ( + ('Main', {'fields': ('price', ('name', 'subtitle'))}), + ) + validate(NestedFieldsetAdmin, Book) + + def test_explicit_through_override(self): + """ + Regression test for #12209 -- If the explicitly provided through model + is specified as a string, the admin should still be able use + Model.m2m_field.through + """ + + class AuthorsInline(admin.TabularInline): + model = Book.authors.through + + class BookAdmin(admin.ModelAdmin): + inlines = [AuthorsInline] + + # If the through model is still a string (and hasn't been resolved to a model) + # the validation will fail. + validate(BookAdmin, Book) + + def test_non_model_fields(self): + """ + Regression for ensuring ModelAdmin.fields can contain non-model fields + that broke with r11737 + """ + class SongForm(forms.ModelForm): + extra_data = forms.CharField() + class Meta: + model = Song + + class FieldsOnFormOnlyAdmin(admin.ModelAdmin): + form = SongForm + fields = ['title', 'extra_data'] + + validate(FieldsOnFormOnlyAdmin, Song) + + + + From 804b94e27dc0146e2ceed8191b74c7ef4d7f58a1 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:32:19 +0000 Subject: [PATCH 199/902] [1.2.X] Migrated backends doctests. Thanks to Sebastian Hillig. Backport of r13884 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13905 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/backends/models.py | 40 ------------------------ tests/regressiontests/backends/tests.py | 39 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/tests/regressiontests/backends/models.py b/tests/regressiontests/backends/models.py index 423bead1adf6..498769abfa0a 100644 --- a/tests/regressiontests/backends/models.py +++ b/tests/regressiontests/backends/models.py @@ -22,43 +22,3 @@ class SchoolClass(models.Model): qn = connection.ops.quote_name -__test__ = {'API_TESTS': """ - -#4896: Test cursor.executemany ->>> from django.db import connection ->>> cursor = connection.cursor() ->>> opts = Square._meta ->>> f1, f2 = opts.get_field('root'), opts.get_field('square') ->>> query = ('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)' -... % (connection.introspection.table_name_converter(opts.db_table), qn(f1.column), qn(f2.column))) ->>> cursor.executemany(query, [(i, i**2) for i in range(-5, 6)]) and None or None ->>> Square.objects.order_by('root') -[, , , , , , , , , , ] - -#4765: executemany with params=[] does nothing ->>> cursor.executemany(query, []) and None or None ->>> Square.objects.count() -11 - -#6254: fetchone, fetchmany, fetchall return strings as unicode objects ->>> Person(first_name="John", last_name="Doe").save() ->>> Person(first_name="Jane", last_name="Doe").save() ->>> Person(first_name="Mary", last_name="Agnelline").save() ->>> Person(first_name="Peter", last_name="Parker").save() ->>> Person(first_name="Clark", last_name="Kent").save() ->>> opts2 = Person._meta ->>> f3, f4 = opts2.get_field('first_name'), opts2.get_field('last_name') ->>> query2 = ('SELECT %s, %s FROM %s ORDER BY %s' -... % (qn(f3.column), qn(f4.column), connection.introspection.table_name_converter(opts2.db_table), -... qn(f3.column))) ->>> cursor.execute(query2) and None or None ->>> cursor.fetchone() -(u'Clark', u'Kent') - ->>> list(cursor.fetchmany(2)) -[(u'Jane', u'Doe'), (u'John', u'Doe')] - ->>> list(cursor.fetchall()) -[(u'Mary', u'Agnelline'), (u'Peter', u'Parker')] - -"""} diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 4984cdf4f773..d955a098afe2 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -120,3 +120,42 @@ def receiver(sender, connection, **kwargs): data.clear() cursor = connection.cursor() self.assertTrue(data == {}) + + +class BackendTestCase(TestCase): + def test_cursor_executemany(self): + #4896: Test cursor.executemany + cursor = connection.cursor() + qn = connection.ops.quote_name + opts = models.Square._meta + f1, f2 = opts.get_field('root'), opts.get_field('square') + query = ('INSERT INTO %s (%s, %s) VALUES (%%s, %%s)' + % (connection.introspection.table_name_converter(opts.db_table), qn(f1.column), qn(f2.column))) + cursor.executemany(query, [(i, i**2) for i in range(-5, 6)]) + self.assertEqual(models.Square.objects.count(), 11) + for i in range(-5, 6): + square = models.Square.objects.get(root=i) + self.assertEqual(square.square, i**2) + + #4765: executemany with params=[] does nothing + cursor.executemany(query, []) + self.assertEqual(models.Square.objects.count(), 11) + + def test_unicode_fetches(self): + #6254: fetchone, fetchmany, fetchall return strings as unicode objects + qn = connection.ops.quote_name + models.Person(first_name="John", last_name="Doe").save() + models.Person(first_name="Jane", last_name="Doe").save() + models.Person(first_name="Mary", last_name="Agnelline").save() + models.Person(first_name="Peter", last_name="Parker").save() + models.Person(first_name="Clark", last_name="Kent").save() + opts2 = models.Person._meta + f3, f4 = opts2.get_field('first_name'), opts2.get_field('last_name') + query2 = ('SELECT %s, %s FROM %s ORDER BY %s' + % (qn(f3.column), qn(f4.column), connection.introspection.table_name_converter(opts2.db_table), + qn(f3.column))) + cursor = connection.cursor() + cursor.execute(query2) + self.assertEqual(cursor.fetchone(), (u'Clark', u'Kent')) + self.assertEqual(list(cursor.fetchmany(2)), [(u'Jane', u'Doe'), (u'John', u'Doe')]) + self.assertEqual(list(cursor.fetchall()), [(u'Mary', u'Agnelline'), (u'Peter', u'Parker')]) From f1328b3adb3b471ce36efb423815def1a85f7e66 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:32:49 +0000 Subject: [PATCH 200/902] [1.2.X] Migrated custom_columns_regress doctests. Thanks to Sebastian Hillig. Backport of r13885 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13906 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/backends/models.py | 1 - .../custom_columns_regress/models.py | 64 -------------- .../custom_columns_regress/tests.py | 86 +++++++++++++++++++ 3 files changed, 86 insertions(+), 65 deletions(-) create mode 100644 tests/regressiontests/custom_columns_regress/tests.py diff --git a/tests/regressiontests/backends/models.py b/tests/regressiontests/backends/models.py index 498769abfa0a..4ab00414db38 100644 --- a/tests/regressiontests/backends/models.py +++ b/tests/regressiontests/backends/models.py @@ -20,5 +20,4 @@ class SchoolClass(models.Model): day = models.CharField(max_length=9, blank=True) last_updated = models.DateTimeField() -qn = connection.ops.quote_name diff --git a/tests/regressiontests/custom_columns_regress/models.py b/tests/regressiontests/custom_columns_regress/models.py index c55daf2588a2..93de2370aff8 100644 --- a/tests/regressiontests/custom_columns_regress/models.py +++ b/tests/regressiontests/custom_columns_regress/models.py @@ -33,68 +33,4 @@ class Meta: ordering = ('last_name','first_name') -__test__ = {'API_TESTS':""" -# Create a Author. ->>> a = Author(first_name='John', last_name='Smith') ->>> a.save() ->>> a.Author_ID -1 - -# Create another author ->>> a2 = Author(first_name='Peter', last_name='Jones') ->>> a2.save() - -# Create an article ->>> art = Article(headline='Django lets you build web apps easily', primary_author=a) ->>> art.save() ->>> art.authors = [a, a2] - -# Although the table and column names on Author have been set to custom values, -# nothing about using the Author model has changed... - -# Query the available authors ->>> Author.objects.all() -[, ] - ->>> Author.objects.filter(first_name__exact='John') -[] - ->>> Author.objects.get(first_name__exact='John') - - ->>> Author.objects.filter(firstname__exact='John') -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'firstname' into field. Choices are: Author_ID, article, first_name, last_name, primary_set - ->>> a = Author.objects.get(last_name__exact='Smith') ->>> a.first_name -u'John' ->>> a.last_name -u'Smith' ->>> a.firstname -Traceback (most recent call last): - ... -AttributeError: 'Author' object has no attribute 'firstname' ->>> a.last -Traceback (most recent call last): - ... -AttributeError: 'Author' object has no attribute 'last' - -# Although the Article table uses a custom m2m table, -# nothing about using the m2m relationship has changed... - -# Get all the authors for an article ->>> art.authors.all() -[, ] - -# Get the articles for an author ->>> a.article_set.all() -[] - -# Query the authors across the m2m relation ->>> art.authors.filter(last_name='Jones') -[] - -"""} diff --git a/tests/regressiontests/custom_columns_regress/tests.py b/tests/regressiontests/custom_columns_regress/tests.py new file mode 100644 index 000000000000..6f6c4ecf948d --- /dev/null +++ b/tests/regressiontests/custom_columns_regress/tests.py @@ -0,0 +1,86 @@ +from django.test import TestCase +from django.core.exceptions import FieldError + +from models import Author, Article + +def pks(objects): + """ Return pks to be able to compare lists""" + return [o.pk for o in objects] + +class CustomColumnRegression(TestCase): + + def assertRaisesMessage(self, exc, msg, func, *args, **kwargs): + try: + func(*args, **kwargs) + except Exception, e: + self.assertEqual(msg, str(e)) + self.assertTrue(isinstance(e, exc), "Expected %s, got %s" % (exc, type(e))) + + def setUp(self): + self.a1 = Author.objects.create(first_name='John', last_name='Smith') + self.a2 = Author.objects.create(first_name='Peter', last_name='Jones') + self.authors = [self.a1, self.a2] + + def test_basic_creation(self): + self.assertEqual(self.a1.Author_ID, 1) + + art = Article(headline='Django lets you build web apps easily', primary_author=self.a1) + art.save() + art.authors = [self.a1, self.a2] + + def test_author_querying(self): + self.assertQuerysetEqual( + Author.objects.all().order_by('last_name'), + ['', ''] + ) + + def test_author_filtering(self): + self.assertQuerysetEqual( + Author.objects.filter(first_name__exact='John'), + [''] + ) + + def test_author_get(self): + self.assertEqual(self.a1, Author.objects.get(first_name__exact='John')) + + def test_filter_on_nonexistant_field(self): + self.assertRaisesMessage( + FieldError, + "Cannot resolve keyword 'firstname' into field. Choices are: Author_ID, article, first_name, last_name, primary_set", + Author.objects.filter, + firstname__exact='John' + ) + + def test_author_get_attributes(self): + a = Author.objects.get(last_name__exact='Smith') + self.assertEqual('John', a.first_name) + self.assertEqual('Smith', a.last_name) + self.assertRaisesMessage( + AttributeError, + "'Author' object has no attribute 'firstname'", + getattr, + a, 'firstname' + ) + + self.assertRaisesMessage( + AttributeError, + "'Author' object has no attribute 'last'", + getattr, + a, 'last' + ) + + def test_m2m_table(self): + art = Article.objects.create(headline='Django lets you build web apps easily', primary_author=self.a1) + art.authors = self.authors + self.assertQuerysetEqual( + art.authors.all().order_by('last_name'), + ['', ''] + ) + self.assertQuerysetEqual( + self.a1.article_set.all(), + [''] + ) + self.assertQuerysetEqual( + art.authors.filter(last_name='Jones'), + [''] + ) From 46448ec51e1b876c48ab9d9d6fdfb1d80ace48c7 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:33:17 +0000 Subject: [PATCH 201/902] [1.2.X] Migrated datatypes doctests. Thanks to Sebastian Hillig. Backport of r13886 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13907 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/datatypes/models.py | 95 +---------------------- tests/regressiontests/datatypes/tests.py | 83 ++++++++++++++++++++ 2 files changed, 84 insertions(+), 94 deletions(-) create mode 100644 tests/regressiontests/datatypes/tests.py diff --git a/tests/regressiontests/datatypes/models.py b/tests/regressiontests/datatypes/models.py index 0898b649b109..47834b73cd49 100644 --- a/tests/regressiontests/datatypes/models.py +++ b/tests/regressiontests/datatypes/models.py @@ -3,8 +3,7 @@ types, which in the past were problematic for some database backends. """ -from django.db import models, DEFAULT_DB_ALIAS -from django.conf import settings +from django.db import models class Donut(models.Model): name = models.CharField(max_length=100) @@ -20,95 +19,3 @@ class Meta: def __str__(self): return self.name - -__test__ = {'API_TESTS': """ -# No donuts are in the system yet. ->>> Donut.objects.all() -[] - ->>> d = Donut(name='Apple Fritter') - -# Ensure we're getting True and False, not 0 and 1 ->>> d.is_frosted -False ->>> d.has_sprinkles ->>> d.has_sprinkles = True ->>> d.has_sprinkles == True -True ->>> d.save() ->>> d2 = Donut.objects.all()[0] ->>> d2 - ->>> d2.is_frosted == False -True ->>> d2.has_sprinkles == True -True - ->>> import datetime ->>> d2.baked_date = datetime.date(year=1938, month=6, day=4) ->>> d2.baked_time = datetime.time(hour=5, minute=30) ->>> d2.consumed_at = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59) ->>> d2.save() - ->>> d3 = Donut.objects.all()[0] ->>> d3.baked_date -datetime.date(1938, 6, 4) ->>> d3.baked_time -datetime.time(5, 30) ->>> d3.consumed_at -datetime.datetime(2007, 4, 20, 16, 19, 59) - -# Test for ticket #12059: TimeField wrongly handling datetime.datetime object. - ->>> d2.baked_time = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59) ->>> d2.save() - ->>> d3 = Donut.objects.all()[0] ->>> d3.baked_time -datetime.time(16, 19, 59) - -# Year boundary tests (ticket #3689) - ->>> d = Donut(name='Date Test 2007', baked_date=datetime.datetime(year=2007, month=12, day=31), consumed_at=datetime.datetime(year=2007, month=12, day=31, hour=23, minute=59, second=59)) ->>> d.save() ->>> d1 = Donut(name='Date Test 2006', baked_date=datetime.datetime(year=2006, month=1, day=1), consumed_at=datetime.datetime(year=2006, month=1, day=1)) ->>> d1.save() - ->>> Donut.objects.filter(baked_date__year=2007) -[] - ->>> Donut.objects.filter(baked_date__year=2006) -[] - ->>> Donut.objects.filter(consumed_at__year=2007).order_by('name') -[, ] - ->>> Donut.objects.filter(consumed_at__year=2006) -[] - ->>> Donut.objects.filter(consumed_at__year=2005) -[] - ->>> Donut.objects.filter(consumed_at__year=2008) -[] - -# Regression test for #10238: TextField values returned from the database -# should be unicode. ->>> d2 = Donut.objects.create(name=u'Jelly Donut', review=u'Outstanding') ->>> Donut.objects.get(id=d2.id).review -u'Outstanding' - -"""} - -# Regression test for #8354: the MySQL backend should raise an error if given -# a timezone-aware datetime object. -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.mysql': - __test__['API_TESTS'] += """ ->>> from django.utils import tzinfo ->>> dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(0)) ->>> d = Donut(name='Bear claw', consumed_at=dt) ->>> d.save() -Traceback (most recent call last): - .... -ValueError: MySQL backend does not support timezone-aware datetimes. -""" diff --git a/tests/regressiontests/datatypes/tests.py b/tests/regressiontests/datatypes/tests.py new file mode 100644 index 000000000000..fc8085c89357 --- /dev/null +++ b/tests/regressiontests/datatypes/tests.py @@ -0,0 +1,83 @@ +import datetime +from django.db import DEFAULT_DB_ALIAS +from django.test import TestCase +from django.utils import tzinfo + +from models import Donut +from django.conf import settings + +class DataTypesTestCase(TestCase): + + def test_boolean_type(self): + d = Donut(name='Apple Fritter') + self.assertFalse(d.is_frosted) + self.assertTrue(d.has_sprinkles is None) + d.has_sprinkles = True + self.assertTrue(d.has_sprinkles) + + d.save() + + d2 = Donut.objects.get(name='Apple Fritter') + self.assertFalse(d2.is_frosted) + self.assertTrue(d2.has_sprinkles) + + def test_date_type(self): + d = Donut(name='Apple Fritter') + d.baked_date = datetime.date(year=1938, month=6, day=4) + d.baked_time = datetime.time(hour=5, minute=30) + d.consumed_at = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59) + d.save() + + d2 = Donut.objects.get(name='Apple Fritter') + self.assertEqual(d2.baked_date, datetime.date(1938, 6, 4)) + self.assertEqual(d2.baked_time, datetime.time(5, 30)) + self.assertEqual(d2.consumed_at, datetime.datetime(2007, 4, 20, 16, 19, 59)) + + def test_time_field(self): + #Test for ticket #12059: TimeField wrongly handling datetime.datetime object. + d = Donut(name='Apple Fritter') + d.baked_time = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59) + d.save() + + d2 = Donut.objects.get(name='Apple Fritter') + self.assertEqual(d2.baked_time, datetime.time(16, 19, 59)) + + def test_year_boundaries(self): + # Year boundary tests (ticket #3689) + d = Donut.objects.create(name='Date Test 2007', + baked_date=datetime.datetime(year=2007, month=12, day=31), + consumed_at=datetime.datetime(year=2007, month=12, day=31, hour=23, minute=59, second=59)) + d1 = Donut.objects.create(name='Date Test 2006', + baked_date=datetime.datetime(year=2006, month=1, day=1), + consumed_at=datetime.datetime(year=2006, month=1, day=1)) + + self.assertEqual("Date Test 2007", + Donut.objects.filter(baked_date__year=2007)[0].name) + + self.assertEqual("Date Test 2006", + Donut.objects.filter(baked_date__year=2006)[0].name) + + d2 = Donut.objects.create(name='Apple Fritter', + consumed_at = datetime.datetime(year=2007, month=4, day=20, hour=16, minute=19, second=59)) + + self.assertEqual([u'Apple Fritter', u'Date Test 2007'], + list(Donut.objects.filter(consumed_at__year=2007).order_by('name').values_list('name', flat=True))) + + self.assertEqual(0, Donut.objects.filter(consumed_at__year=2005).count()) + self.assertEqual(0, Donut.objects.filter(consumed_at__year=2008).count()) + + def test_textfields_unicode(self): + # Regression test for #10238: TextField values returned from the database + # should be unicode. + d = Donut.objects.create(name=u'Jelly Donut', review=u'Outstanding') + newd = Donut.objects.get(id=d.id) + self.assert_(isinstance(newd.review, unicode)) + + def test_tz_awareness_mysql(self): + # Regression test for #8354: the MySQL backend should raise an error if given + # a timezone-aware datetime object. + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.mysql': + dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(0)) + d = Donut(name='Bear claw', consumed_at=dt) + self.assertRaises(ValueError, d.save) + # ValueError: MySQL backend does not support timezone-aware datetimes. From 1917a43c4b6f4f310ec63dcb55927ddcd0132f4d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:33:42 +0000 Subject: [PATCH 202/902] [1.2.X] Migrated tzinfo doctests. Thanks to Stephan Jaekel. Backport of r13887 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13908 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/utils/tzinfo.py | 44 ++++++++++----------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/tests/regressiontests/utils/tzinfo.py b/tests/regressiontests/utils/tzinfo.py index b62570c2253f..60fc0b942e31 100644 --- a/tests/regressiontests/utils/tzinfo.py +++ b/tests/regressiontests/utils/tzinfo.py @@ -1,30 +1,18 @@ -""" ->>> from django.utils.tzinfo import FixedOffset +from django.test import TestCase ->>> FixedOffset(0) -+0000 ->>> FixedOffset(60) -+0100 ->>> FixedOffset(-60) --0100 ->>> FixedOffset(280) -+0440 ->>> FixedOffset(-280) --0440 ->>> FixedOffset(-78.4) --0118 ->>> FixedOffset(78.4) -+0118 ->>> FixedOffset(-5.5*60) --0530 ->>> FixedOffset(5.5*60) -+0530 ->>> FixedOffset(-.5*60) --0030 ->>> FixedOffset(.5*60) -+0030 -""" +from django.utils.tzinfo import FixedOffset -if __name__ == "__main__": - import doctest - doctest.testmod() +class TzinfoTests(TestCase): + + def test_fixedoffset(self): + self.assertEquals(repr(FixedOffset(0)), '+0000') + self.assertEquals(repr(FixedOffset(60)), '+0100') + self.assertEquals(repr(FixedOffset(-60)), '-0100') + self.assertEquals(repr(FixedOffset(280)), '+0440') + self.assertEquals(repr(FixedOffset(-280)), '-0440') + self.assertEquals(repr(FixedOffset(-78.4)), '-0118') + self.assertEquals(repr(FixedOffset(78.4)), '+0118') + self.assertEquals(repr(FixedOffset(-5.5*60)), '-0530') + self.assertEquals(repr(FixedOffset(5.5*60)), '+0530') + self.assertEquals(repr(FixedOffset(-.5*60)), '-0030') + self.assertEquals(repr(FixedOffset(.5*60)), '+0030') From 494933a36bbd78d041c8c3ac60da418392d50860 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:34:06 +0000 Subject: [PATCH 203/902] [1.2.X] Migrated datetime_safe doctests. Thanks to Stephan Jaekel. Backport of r13888 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13909 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/utils/datetime_safe.py | 86 ++++++++++---------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/tests/regressiontests/utils/datetime_safe.py b/tests/regressiontests/utils/datetime_safe.py index 6c3e7df3595f..62b9c7481fe1 100644 --- a/tests/regressiontests/utils/datetime_safe.py +++ b/tests/regressiontests/utils/datetime_safe.py @@ -1,44 +1,42 @@ -""" ->>> from datetime import date as original_date, datetime as original_datetime ->>> from django.utils.datetime_safe import date, datetime ->>> just_safe = (1900, 1, 1) ->>> just_unsafe = (1899, 12, 31, 23, 59, 59) ->>> really_old = (20, 1, 1) ->>> more_recent = (2006, 1, 1) - ->>> original_datetime(*more_recent) == datetime(*more_recent) -True ->>> original_datetime(*really_old) == datetime(*really_old) -True ->>> original_date(*more_recent) == date(*more_recent) -True ->>> original_date(*really_old) == date(*really_old) -True - ->>> original_date(*just_safe).strftime('%Y-%m-%d') == date(*just_safe).strftime('%Y-%m-%d') -True ->>> original_datetime(*just_safe).strftime('%Y-%m-%d') == datetime(*just_safe).strftime('%Y-%m-%d') -True - ->>> date(*just_unsafe[:3]).strftime('%Y-%m-%d (weekday %w)') -'1899-12-31 (weekday 0)' ->>> date(*just_safe).strftime('%Y-%m-%d (weekday %w)') -'1900-01-01 (weekday 1)' - ->>> datetime(*just_unsafe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)') -'1899-12-31 23:59:59 (weekday 0)' ->>> datetime(*just_safe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)') -'1900-01-01 00:00:00 (weekday 1)' - ->>> date(*just_safe).strftime('%y') # %y will error before this date -'00' ->>> datetime(*just_safe).strftime('%y') -'00' - ->>> date(1850, 8, 2).strftime("%Y/%m/%d was a %A") -'1850/08/02 was a Friday' - -# Regression for #12524 -- Check that pre-1000AD dates are padded with zeros if necessary ->>> date(1, 1, 1).strftime("%Y/%m/%d was a %A") -'0001/01/01 was a Monday' -""" +from django.test import TestCase + +from datetime import date as original_date, datetime as original_datetime +from django.utils.datetime_safe import date, datetime + +class DatetimeTests(TestCase): + + def setUp(self): + self.just_safe = (1900, 1, 1) + self.just_unsafe = (1899, 12, 31, 23, 59, 59) + self.really_old = (20, 1, 1) + self.more_recent = (2006, 1, 1) + + def test_compare_datetimes(self): + self.assertEqual(original_datetime(*self.more_recent), datetime(*self.more_recent)) + self.assertEqual(original_datetime(*self.really_old), datetime(*self.really_old)) + self.assertEqual(original_date(*self.more_recent), date(*self.more_recent)) + self.assertEqual(original_date(*self.really_old), date(*self.really_old)) + + self.assertEqual(original_date(*self.just_safe).strftime('%Y-%m-%d'), date(*self.just_safe).strftime('%Y-%m-%d')) + self.assertEqual(original_datetime(*self.just_safe).strftime('%Y-%m-%d'), datetime(*self.just_safe).strftime('%Y-%m-%d')) + + def test_safe_strftime(self): + self.assertEquals(date(*self.just_unsafe[:3]).strftime('%Y-%m-%d (weekday %w)'), '1899-12-31 (weekday 0)') + self.assertEquals(date(*self.just_safe).strftime('%Y-%m-%d (weekday %w)'), '1900-01-01 (weekday 1)') + + self.assertEquals(datetime(*self.just_unsafe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1899-12-31 23:59:59 (weekday 0)') + self.assertEquals(datetime(*self.just_safe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1900-01-01 00:00:00 (weekday 1)') + + # %y will error before this date + self.assertEquals(date(*self.just_safe).strftime('%y'), '00') + self.assertEquals(datetime(*self.just_safe).strftime('%y'), '00') + + self.assertEquals(date(1850, 8, 2).strftime("%Y/%m/%d was a %A"), '1850/08/02 was a Friday') + + def test_zero_padding(self): + """ + Regression for #12524 + + Check that pre-1000AD dates are padded with zeros if necessary + """ + self.assertEquals(date(1, 1, 1).strftime("%Y/%m/%d was a %A"), '0001/01/01 was a Monday') From 4cd11f8681db0ec8efabbdcb3ebf73f650d1635c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:34:41 +0000 Subject: [PATCH 204/902] [1.2.X] Reorganized utils tests so it's all in separate modules. Thanks to Stephan Jaekel. Backport of r13889 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13910 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/utils/checksums.py | 29 ++ tests/regressiontests/utils/html.py | 111 ++++++++ .../regressiontests/utils/simplelazyobject.py | 77 ++++++ tests/regressiontests/utils/tests.py | 256 +----------------- tests/regressiontests/utils/text.py | 20 ++ 5 files changed, 243 insertions(+), 250 deletions(-) create mode 100644 tests/regressiontests/utils/checksums.py create mode 100644 tests/regressiontests/utils/html.py create mode 100644 tests/regressiontests/utils/simplelazyobject.py create mode 100644 tests/regressiontests/utils/text.py diff --git a/tests/regressiontests/utils/checksums.py b/tests/regressiontests/utils/checksums.py new file mode 100644 index 000000000000..bb73576a8043 --- /dev/null +++ b/tests/regressiontests/utils/checksums.py @@ -0,0 +1,29 @@ +from django.test import TestCase + +from django.utils import checksums + +class TestUtilsChecksums(TestCase): + + def check_output(self, function, value, output=None): + """ + Check that function(value) equals output. If output is None, + check that function(value) equals value. + """ + if output is None: + output = value + self.assertEqual(function(value), output) + + def test_luhn(self): + f = checksums.luhn + items = ( + (4111111111111111, True), ('4111111111111111', True), + (4222222222222, True), (378734493671000, True), + (5424000000000015, True), (5555555555554444, True), + (1008, True), ('0000001008', True), ('000000001008', True), + (4012888888881881, True), (1234567890123456789012345678909, True), + (4111111111211111, False), (42222222222224, False), + (100, False), ('100', False), ('0000100', False), + ('abc', False), (None, False), (object(), False), + ) + for value, output in items: + self.check_output(f, value, output) diff --git a/tests/regressiontests/utils/html.py b/tests/regressiontests/utils/html.py new file mode 100644 index 000000000000..61ca6f4c53af --- /dev/null +++ b/tests/regressiontests/utils/html.py @@ -0,0 +1,111 @@ +from django.test import TestCase + +from django.utils import html + +class TestUtilsHtml(TestCase): + + def check_output(self, function, value, output=None): + """ + Check that function(value) equals output. If output is None, + check that function(value) equals value. + """ + if output is None: + output = value + self.assertEqual(function(value), output) + + def test_escape(self): + f = html.escape + items = ( + ('&','&'), + ('<', '<'), + ('>', '>'), + ('"', '"'), + ("'", '''), + ) + # Substitution patterns for testing the above items. + patterns = ("%s", "asdf%sfdsa", "%s1", "1%sb") + for value, output in items: + for pattern in patterns: + self.check_output(f, pattern % value, pattern % output) + # Check repeated values. + self.check_output(f, value * 2, output * 2) + # Verify it doesn't double replace &. + self.check_output(f, '<&', '<&') + + def test_linebreaks(self): + f = html.linebreaks + items = ( + ("para1\n\npara2\r\rpara3", "

        para1

        \n\n

        para2

        \n\n

        para3

        "), + ("para1\nsub1\rsub2\n\npara2", "

        para1
        sub1
        sub2

        \n\n

        para2

        "), + ("para1\r\n\r\npara2\rsub1\r\rpara4", "

        para1

        \n\n

        para2
        sub1

        \n\n

        para4

        "), + ("para1\tmore\n\npara2", "

        para1\tmore

        \n\n

        para2

        "), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_strip_tags(self): + f = html.strip_tags + items = ( + ('a', 'a'), + ('a', 'a'), + ('e', 'e'), + ('b', 'b'), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_strip_spaces_between_tags(self): + f = html.strip_spaces_between_tags + # Strings that should come out untouched. + items = (' ', ' ', ' ', ' x') + for value in items: + self.check_output(f, value) + # Strings that have spaces to strip. + items = ( + (' ', ''), + ('

        hello

        \n

        world

        ', '

        hello

        world

        '), + ('\n

        \t

        \n

        \n', '\n

        \n'), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_strip_entities(self): + f = html.strip_entities + # Strings that should come out untouched. + values = ("&", "&a", "&a", "a&#a") + for value in values: + self.check_output(f, value) + # Valid entities that should be stripped from the patterns. + entities = ("", " ", "&a;", "&fdasdfasdfasdf;") + patterns = ( + ("asdf %(entity)s ", "asdf "), + ("%(entity)s%(entity)s", ""), + ("&%(entity)s%(entity)s", "&"), + ("%(entity)s3", "3"), + ) + for entity in entities: + for in_pattern, output in patterns: + self.check_output(f, in_pattern % {'entity': entity}, output) + + def test_fix_ampersands(self): + f = html.fix_ampersands + # Strings without ampersands or with ampersands already encoded. + values = ("a", "b", "&a;", "& &x; ", "asdf") + patterns = ( + ("%s", "%s"), + ("&%s", "&%s"), + ("&%s&", "&%s&"), + ) + for value in values: + for in_pattern, out_pattern in patterns: + self.check_output(f, in_pattern % value, out_pattern % value) + # Strings with ampersands that need encoding. + items = ( + ("&#;", "&#;"), + ("ͫ ;", "&#875 ;"), + ("abc;", "&#4abc;"), + ) + for value, output in items: + self.check_output(f, value, output) diff --git a/tests/regressiontests/utils/simplelazyobject.py b/tests/regressiontests/utils/simplelazyobject.py new file mode 100644 index 000000000000..1df55aa88549 --- /dev/null +++ b/tests/regressiontests/utils/simplelazyobject.py @@ -0,0 +1,77 @@ +from django.test import TestCase + +from django.utils.functional import SimpleLazyObject + +class _ComplexObject(object): + def __init__(self, name): + self.name = name + + def __eq__(self, other): + return self.name == other.name + + def __hash__(self): + return hash(self.name) + + def __str__(self): + return "I am _ComplexObject(%r)" % self.name + + def __unicode__(self): + return unicode(self.name) + + def __repr__(self): + return "_ComplexObject(%r)" % self.name + +complex_object = lambda: _ComplexObject("joe") + +class TestUtilsSimpleLazyObject(TestCase): + """ + Tests for SimpleLazyObject + """ + # Note that concrete use cases for SimpleLazyObject are also found in the + # auth context processor tests (unless the implementation of that function + # is changed). + + def test_equality(self): + self.assertEqual(complex_object(), SimpleLazyObject(complex_object)) + self.assertEqual(SimpleLazyObject(complex_object), complex_object()) + + def test_hash(self): + # hash() equality would not be true for many objects, but it should be + # for _ComplexObject + self.assertEqual(hash(complex_object()), + hash(SimpleLazyObject(complex_object))) + + def test_repr(self): + # For debugging, it will really confuse things if there is no clue that + # SimpleLazyObject is actually a proxy object. So we don't + # proxy __repr__ + self.assert_("SimpleLazyObject" in repr(SimpleLazyObject(complex_object))) + + def test_str(self): + self.assertEqual("I am _ComplexObject('joe')", str(SimpleLazyObject(complex_object))) + + def test_unicode(self): + self.assertEqual(u"joe", unicode(SimpleLazyObject(complex_object))) + + def test_class(self): + # This is important for classes that use __class__ in things like + # equality tests. + self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__) + + def test_deepcopy(self): + import django.utils.copycompat as copy + # Check that we *can* do deep copy, and that it returns the right + # objects. + + # First, for an unevaluated SimpleLazyObject + s = SimpleLazyObject(complex_object) + assert s._wrapped is None + s2 = copy.deepcopy(s) + assert s._wrapped is None # something has gone wrong is s is evaluated + self.assertEqual(s2, complex_object()) + + # Second, for an evaluated SimpleLazyObject + name = s.name # evaluate + assert s._wrapped is not None + s3 = copy.deepcopy(s) + self.assertEqual(s3, complex_object()) diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index cfefc6139a19..05a3253b9c9f 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -2,258 +2,14 @@ Tests for django.utils. """ -from unittest import TestCase - -from django.utils import html, checksums, text -from django.utils.functional import SimpleLazyObject - -import timesince -import datastructures -import datetime_safe -import tzinfo - -from decorators import DecoratorFromMiddlewareTests -from functional import FunctionalTestCase - -# Extra tests -__test__ = { - 'timesince': timesince, - 'datastructures': datastructures, - 'datetime_safe': datetime_safe, - 'tzinfo': tzinfo, -} - from dateformat import * from feedgenerator import * from module_loading import * from termcolors import * +from html import * +from checksums import * +from text import * +from simplelazyobject import * +from decorators import * +from functional import * -class TestUtilsHtml(TestCase): - - def check_output(self, function, value, output=None): - """ - Check that function(value) equals output. If output is None, - check that function(value) equals value. - """ - if output is None: - output = value - self.assertEqual(function(value), output) - - def test_escape(self): - f = html.escape - items = ( - ('&','&'), - ('<', '<'), - ('>', '>'), - ('"', '"'), - ("'", '''), - ) - # Substitution patterns for testing the above items. - patterns = ("%s", "asdf%sfdsa", "%s1", "1%sb") - for value, output in items: - for pattern in patterns: - self.check_output(f, pattern % value, pattern % output) - # Check repeated values. - self.check_output(f, value * 2, output * 2) - # Verify it doesn't double replace &. - self.check_output(f, '<&', '<&') - - def test_linebreaks(self): - f = html.linebreaks - items = ( - ("para1\n\npara2\r\rpara3", "

        para1

        \n\n

        para2

        \n\n

        para3

        "), - ("para1\nsub1\rsub2\n\npara2", "

        para1
        sub1
        sub2

        \n\n

        para2

        "), - ("para1\r\n\r\npara2\rsub1\r\rpara4", "

        para1

        \n\n

        para2
        sub1

        \n\n

        para4

        "), - ("para1\tmore\n\npara2", "

        para1\tmore

        \n\n

        para2

        "), - ) - for value, output in items: - self.check_output(f, value, output) - - def test_strip_tags(self): - f = html.strip_tags - items = ( - ('a', 'a'), - ('a', 'a'), - ('e', 'e'), - ('b', 'b'), - ) - for value, output in items: - self.check_output(f, value, output) - - def test_strip_spaces_between_tags(self): - f = html.strip_spaces_between_tags - # Strings that should come out untouched. - items = (' ', ' ', ' ', ' x') - for value in items: - self.check_output(f, value) - # Strings that have spaces to strip. - items = ( - (' ', ''), - ('

        hello

        \n

        world

        ', '

        hello

        world

        '), - ('\n

        \t

        \n

        \n', '\n

        \n'), - ) - for value, output in items: - self.check_output(f, value, output) - - def test_strip_entities(self): - f = html.strip_entities - # Strings that should come out untouched. - values = ("&", "&a", "&a", "a&#a") - for value in values: - self.check_output(f, value) - # Valid entities that should be stripped from the patterns. - entities = ("", " ", "&a;", "&fdasdfasdfasdf;") - patterns = ( - ("asdf %(entity)s ", "asdf "), - ("%(entity)s%(entity)s", ""), - ("&%(entity)s%(entity)s", "&"), - ("%(entity)s3", "3"), - ) - for entity in entities: - for in_pattern, output in patterns: - self.check_output(f, in_pattern % {'entity': entity}, output) - - def test_fix_ampersands(self): - f = html.fix_ampersands - # Strings without ampersands or with ampersands already encoded. - values = ("a", "b", "&a;", "& &x; ", "asdf") - patterns = ( - ("%s", "%s"), - ("&%s", "&%s"), - ("&%s&", "&%s&"), - ) - for value in values: - for in_pattern, out_pattern in patterns: - self.check_output(f, in_pattern % value, out_pattern % value) - # Strings with ampersands that need encoding. - items = ( - ("&#;", "&#;"), - ("ͫ ;", "&#875 ;"), - ("abc;", "&#4abc;"), - ) - for value, output in items: - self.check_output(f, value, output) - -class TestUtilsChecksums(TestCase): - - def check_output(self, function, value, output=None): - """ - Check that function(value) equals output. If output is None, - check that function(value) equals value. - """ - if output is None: - output = value - self.assertEqual(function(value), output) - - def test_luhn(self): - f = checksums.luhn - items = ( - (4111111111111111, True), ('4111111111111111', True), - (4222222222222, True), (378734493671000, True), - (5424000000000015, True), (5555555555554444, True), - (1008, True), ('0000001008', True), ('000000001008', True), - (4012888888881881, True), (1234567890123456789012345678909, True), - (4111111111211111, False), (42222222222224, False), - (100, False), ('100', False), ('0000100', False), - ('abc', False), (None, False), (object(), False), - ) - for value, output in items: - self.check_output(f, value, output) - -class _ComplexObject(object): - def __init__(self, name): - self.name = name - - def __eq__(self, other): - return self.name == other.name - - def __hash__(self): - return hash(self.name) - - def __str__(self): - return "I am _ComplexObject(%r)" % self.name - - def __unicode__(self): - return unicode(self.name) - - def __repr__(self): - return "_ComplexObject(%r)" % self.name - -complex_object = lambda: _ComplexObject("joe") - -class TestUtilsSimpleLazyObject(TestCase): - """ - Tests for SimpleLazyObject - """ - # Note that concrete use cases for SimpleLazyObject are also found in the - # auth context processor tests (unless the implementation of that function - # is changed). - - def test_equality(self): - self.assertEqual(complex_object(), SimpleLazyObject(complex_object)) - self.assertEqual(SimpleLazyObject(complex_object), complex_object()) - - def test_hash(self): - # hash() equality would not be true for many objects, but it should be - # for _ComplexObject - self.assertEqual(hash(complex_object()), - hash(SimpleLazyObject(complex_object))) - - def test_repr(self): - # For debugging, it will really confuse things if there is no clue that - # SimpleLazyObject is actually a proxy object. So we don't - # proxy __repr__ - self.assert_("SimpleLazyObject" in repr(SimpleLazyObject(complex_object))) - - def test_str(self): - self.assertEqual("I am _ComplexObject('joe')", str(SimpleLazyObject(complex_object))) - - def test_unicode(self): - self.assertEqual(u"joe", unicode(SimpleLazyObject(complex_object))) - - def test_class(self): - # This is important for classes that use __class__ in things like - # equality tests. - self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__) - - def test_deepcopy(self): - import django.utils.copycompat as copy - # Check that we *can* do deep copy, and that it returns the right - # objects. - - # First, for an unevaluated SimpleLazyObject - s = SimpleLazyObject(complex_object) - assert s._wrapped is None - s2 = copy.deepcopy(s) - assert s._wrapped is None # something has gone wrong is s is evaluated - self.assertEqual(s2, complex_object()) - - # Second, for an evaluated SimpleLazyObject - name = s.name # evaluate - assert s._wrapped is not None - s3 = copy.deepcopy(s) - self.assertEqual(s3, complex_object()) - -class TestUtilsText(TestCase): - - def test_truncate_words(self): - self.assertEqual(u'The quick brown fox jumped over the lazy dog.', - text.truncate_words(u'The quick brown fox jumped over the lazy dog.', 10)) - self.assertEqual(u'The quick brown fox ...', - text.truncate_words('The quick brown fox jumped over the lazy dog.', 4)) - self.assertEqual(u'The quick brown fox ....', - text.truncate_words('The quick brown fox jumped over the lazy dog.', 4, '....')) - self.assertEqual(u'

        The quick brown fox jumped over the lazy dog.

        ', - text.truncate_html_words('

        The quick brown fox jumped over the lazy dog.

        ', 10)) - self.assertEqual(u'

        The quick brown fox ...

        ', - text.truncate_html_words('

        The quick brown fox jumped over the lazy dog.

        ', 4)) - self.assertEqual(u'

        The quick brown fox ....

        ', - text.truncate_html_words('

        The quick brown fox jumped over the lazy dog.

        ', 4, '....')) - self.assertEqual(u'

        The quick brown fox

        ', - text.truncate_html_words('

        The quick brown fox jumped over the lazy dog.

        ', 4, None)) - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/tests/regressiontests/utils/text.py b/tests/regressiontests/utils/text.py new file mode 100644 index 000000000000..7cf52ca92c35 --- /dev/null +++ b/tests/regressiontests/utils/text.py @@ -0,0 +1,20 @@ +from django.test import TestCase + +from django.utils import text + +class TestUtilsText(TestCase): + def test_truncate_words(self): + self.assertEqual(u'The quick brown fox jumped over the lazy dog.', + text.truncate_words(u'The quick brown fox jumped over the lazy dog.', 10)) + self.assertEqual(u'The quick brown fox ...', + text.truncate_words('The quick brown fox jumped over the lazy dog.', 4)) + self.assertEqual(u'The quick brown fox ....', + text.truncate_words('The quick brown fox jumped over the lazy dog.', 4, '....')) + self.assertEqual(u'

        The quick brown fox jumped over the lazy dog.

        ', + text.truncate_html_words('

        The quick brown fox jumped over the lazy dog.

        ', 10)) + self.assertEqual(u'

        The quick brown fox ...

        ', + text.truncate_html_words('

        The quick brown fox jumped over the lazy dog.

        ', 4)) + self.assertEqual(u'

        The quick brown fox ....

        ', + text.truncate_html_words('

        The quick brown fox jumped over the lazy dog.

        ', 4, '....')) + self.assertEqual(u'

        The quick brown fox

        ', + text.truncate_html_words('

        The quick brown fox jumped over the lazy dog.

        ', 4, None)) From 46d1d951c272093cee14b4b095cebd234854a5f3 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:35:07 +0000 Subject: [PATCH 205/902] [1.2.X] Migrated timesince utils doctests. Thanks to Stephan Jaekel. Backport of r13890 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13911 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/utils/timesince.py | 186 ++++++++++++----------- 1 file changed, 98 insertions(+), 88 deletions(-) diff --git a/tests/regressiontests/utils/timesince.py b/tests/regressiontests/utils/timesince.py index 5a54bf4c8c68..d7511217303c 100644 --- a/tests/regressiontests/utils/timesince.py +++ b/tests/regressiontests/utils/timesince.py @@ -1,98 +1,108 @@ -""" ->>> import datetime ->>> from django.utils.timesince import timesince, timeuntil ->>> from django.utils.tzinfo import LocalTimezone, FixedOffset +from django.test import TestCase ->>> t = datetime.datetime(2007, 8, 14, 13, 46, 0) +import datetime ->>> onemicrosecond = datetime.timedelta(microseconds=1) ->>> onesecond = datetime.timedelta(seconds=1) ->>> oneminute = datetime.timedelta(minutes=1) ->>> onehour = datetime.timedelta(hours=1) ->>> oneday = datetime.timedelta(days=1) ->>> oneweek = datetime.timedelta(days=7) ->>> onemonth = datetime.timedelta(days=30) ->>> oneyear = datetime.timedelta(days=365) +from django.utils.timesince import timesince, timeuntil +from django.utils.tzinfo import LocalTimezone, FixedOffset -# equal datetimes. ->>> timesince(t, t) -u'0 minutes' +class TimesinceTests(TestCase): -# Microseconds and seconds are ignored. ->>> timesince(t, t+onemicrosecond) -u'0 minutes' ->>> timesince(t, t+onesecond) -u'0 minutes' + def setUp(self): + self.t = datetime.datetime(2007, 8, 14, 13, 46, 0) + self.onemicrosecond = datetime.timedelta(microseconds=1) + self.onesecond = datetime.timedelta(seconds=1) + self.oneminute = datetime.timedelta(minutes=1) + self.onehour = datetime.timedelta(hours=1) + self.oneday = datetime.timedelta(days=1) + self.oneweek = datetime.timedelta(days=7) + self.onemonth = datetime.timedelta(days=30) + self.oneyear = datetime.timedelta(days=365) -# Test other units. ->>> timesince(t, t+oneminute) -u'1 minute' ->>> timesince(t, t+onehour) -u'1 hour' ->>> timesince(t, t+oneday) -u'1 day' ->>> timesince(t, t+oneweek) -u'1 week' ->>> timesince(t, t+onemonth) -u'1 month' ->>> timesince(t, t+oneyear) -u'1 year' + def test_equal_datetimes(self): + """ equal datetimes. """ + self.assertEquals(timesince(self.t, self.t), u'0 minutes') -# Test multiple units. ->>> timesince(t, t+2*oneday+6*onehour) -u'2 days, 6 hours' ->>> timesince(t, t+2*oneweek+2*oneday) -u'2 weeks, 2 days' + def test_ignore_microseconds_and_seconds(self): + """ Microseconds and seconds are ignored. """ + self.assertEquals(timesince(self.t, self.t+self.onemicrosecond), + u'0 minutes') + self.assertEquals(timesince(self.t, self.t+self.onesecond), + u'0 minutes') -# If the two differing units aren't adjacent, only the first unit is displayed. ->>> timesince(t, t+2*oneweek+3*onehour+4*oneminute) -u'2 weeks' ->>> timesince(t, t+4*oneday+5*oneminute) -u'4 days' + def test_other_units(self): + """ Test other units. """ + self.assertEquals(timesince(self.t, self.t+self.oneminute), + u'1 minute') + self.assertEquals(timesince(self.t, self.t+self.onehour), u'1 hour') + self.assertEquals(timesince(self.t, self.t+self.oneday), u'1 day') + self.assertEquals(timesince(self.t, self.t+self.oneweek), u'1 week') + self.assertEquals(timesince(self.t, self.t+self.onemonth), + u'1 month') + self.assertEquals(timesince(self.t, self.t+self.oneyear), u'1 year') -# When the second date occurs before the first, we should always get 0 minutes. ->>> timesince(t, t-onemicrosecond) -u'0 minutes' ->>> timesince(t, t-onesecond) -u'0 minutes' ->>> timesince(t, t-oneminute) -u'0 minutes' ->>> timesince(t, t-onehour) -u'0 minutes' ->>> timesince(t, t-oneday) -u'0 minutes' ->>> timesince(t, t-oneweek) -u'0 minutes' ->>> timesince(t, t-onemonth) -u'0 minutes' ->>> timesince(t, t-oneyear) -u'0 minutes' ->>> timesince(t, t-2*oneday-6*onehour) -u'0 minutes' ->>> timesince(t, t-2*oneweek-2*oneday) -u'0 minutes' ->>> timesince(t, t-2*oneweek-3*onehour-4*oneminute) -u'0 minutes' ->>> timesince(t, t-4*oneday-5*oneminute) -u'0 minutes' + def test_multiple_units(self): + """ Test multiple units. """ + self.assertEquals(timesince(self.t, + self.t+2*self.oneday+6*self.onehour), u'2 days, 6 hours') + self.assertEquals(timesince(self.t, + self.t+2*self.oneweek+2*self.oneday), u'2 weeks, 2 days') -# When using two different timezones. ->>> now = datetime.datetime.now() ->>> now_tz = datetime.datetime.now(LocalTimezone(now)) ->>> now_tz_i = datetime.datetime.now(FixedOffset((3 * 60) + 15)) ->>> timesince(now) -u'0 minutes' ->>> timesince(now_tz) -u'0 minutes' ->>> timeuntil(now_tz, now_tz_i) -u'0 minutes' + def test_display_first_unit(self): + """ + If the two differing units aren't adjacent, only the first unit is + displayed. + """ + self.assertEquals(timesince(self.t, + self.t+2*self.oneweek+3*self.onehour+4*self.oneminute), + u'2 weeks') -# Timesince should work with both date objects (#9672) ->>> today = datetime.date.today() ->>> timeuntil(today+oneday, today) -u'1 day' ->>> timeuntil(today-oneday, today) -u'0 minutes' ->>> timeuntil(today+oneweek, today) -u'1 week' -""" + self.assertEquals(timesince(self.t, + self.t+4*self.oneday+5*self.oneminute), u'4 days') + + def test_display_second_before_first(self): + """ + When the second date occurs before the first, we should always + get 0 minutes. + """ + self.assertEquals(timesince(self.t, self.t-self.onemicrosecond), + u'0 minutes') + self.assertEquals(timesince(self.t, self.t-self.onesecond), + u'0 minutes') + self.assertEquals(timesince(self.t, self.t-self.oneminute), + u'0 minutes') + self.assertEquals(timesince(self.t, self.t-self.onehour), + u'0 minutes') + self.assertEquals(timesince(self.t, self.t-self.oneday), + u'0 minutes') + self.assertEquals(timesince(self.t, self.t-self.oneweek), + u'0 minutes') + self.assertEquals(timesince(self.t, self.t-self.onemonth), + u'0 minutes') + self.assertEquals(timesince(self.t, self.t-self.oneyear), + u'0 minutes') + self.assertEquals(timesince(self.t, + self.t-2*self.oneday-6*self.onehour), u'0 minutes') + self.assertEquals(timesince(self.t, + self.t-2*self.oneweek-2*self.oneday), u'0 minutes') + self.assertEquals(timesince(self.t, + self.t-2*self.oneweek-3*self.onehour-4*self.oneminute), + u'0 minutes') + self.assertEquals(timesince(self.t, + self.t-4*self.oneday-5*self.oneminute), u'0 minutes') + + def test_different_timezones(self): + """ When using two different timezones. """ + now = datetime.datetime.now() + now_tz = datetime.datetime.now(LocalTimezone(now)) + now_tz_i = datetime.datetime.now(FixedOffset((3 * 60) + 15)) + + self.assertEquals(timesince(now), u'0 minutes') + self.assertEquals(timesince(now_tz), u'0 minutes') + self.assertEquals(timeuntil(now_tz, now_tz_i), u'0 minutes') + + def test_both_date_objects(self): + """ Timesince should work with both date objects (#9672) """ + today = datetime.date.today() + self.assertEquals(timeuntil(today+self.oneday, today), u'1 day') + self.assertEquals(timeuntil(today-self.oneday, today), u'0 minutes') + self.assertEquals(timeuntil(today+self.oneweek, today), u'1 week') From 0b7b3e1ae8737c8ea68a2fef10d4295be61478d1 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:35:39 +0000 Subject: [PATCH 206/902] [1.2.X] Migrated datastructures utils doctests. Thanks to Stephan Jaekel. Backport of r13891 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13912 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/utils/datastructures.py | 117 ++++++++++-------- 1 file changed, 66 insertions(+), 51 deletions(-) diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index 8ac6b9ec7e89..a353a324042b 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -1,52 +1,67 @@ -""" ->>> from django.utils.datastructures import SortedDict - ->>> d = SortedDict() ->>> d[7] = 'seven' ->>> d[1] = 'one' ->>> d[9] = 'nine' ->>> d.keys() -[7, 1, 9] ->>> d.values() -['seven', 'one', 'nine'] ->>> d.items() -[(7, 'seven'), (1, 'one'), (9, 'nine')] - -# Overwriting an item keeps it's place. ->>> d[1] = 'ONE' ->>> d.values() -['seven', 'ONE', 'nine'] - -# New items go to the end. ->>> d[0] = 'nil' ->>> d.keys() -[7, 1, 9, 0] - -# Deleting an item, then inserting the same key again will place it at the end. ->>> del d[7] ->>> d.keys() -[1, 9, 0] ->>> d[7] = 'lucky number 7' ->>> d.keys() -[1, 9, 0, 7] - -# Changing the keys won't do anything, it's only a copy of the keys dict. ->>> k = d.keys() ->>> k.remove(9) ->>> d.keys() -[1, 9, 0, 7] - -# Initialising a SortedDict with two keys will just take the first one. A real -# dict will actually take the second value so we will too, but we'll keep the -# ordering from the first key found. ->>> tuples = ((2, 'two'), (1, 'one'), (2, 'second-two')) ->>> d = SortedDict(tuples) ->>> d.keys() -[2, 1] ->>> real_dict = dict(tuples) ->>> sorted(real_dict.values()) -['one', 'second-two'] ->>> d.values() # Here the order of SortedDict values *is* what we are testing -['second-two', 'one'] -""" +from django.test import TestCase +from django.utils.datastructures import SortedDict +class DatastructuresTests(TestCase): + def setUp(self): + self.d1 = SortedDict() + self.d1[7] = 'seven' + self.d1[1] = 'one' + self.d1[9] = 'nine' + + self.d2 = SortedDict() + self.d2[1] = 'one' + self.d2[9] = 'nine' + self.d2[0] = 'nil' + self.d2[7] = 'seven' + + def test_basic_methods(self): + self.assertEquals(self.d1.keys(), [7, 1, 9]) + self.assertEquals(self.d1.values(), ['seven', 'one', 'nine']) + self.assertEquals(self.d1.items(), [(7, 'seven'), (1, 'one'), (9, 'nine')]) + + def test_overwrite_ordering(self): + """ Overwriting an item keeps it's place. """ + self.d1[1] = 'ONE' + self.assertEquals(self.d1.values(), ['seven', 'ONE', 'nine']) + + def test_append_items(self): + """ New items go to the end. """ + self.d1[0] = 'nil' + self.assertEquals(self.d1.keys(), [7, 1, 9, 0]) + + def test_delete_and_insert(self): + """ + Deleting an item, then inserting the same key again will place it + at the end. + """ + del self.d2[7] + self.assertEquals(self.d2.keys(), [1, 9, 0]) + self.d2[7] = 'lucky number 7' + self.assertEquals(self.d2.keys(), [1, 9, 0, 7]) + + def test_change_keys(self): + """ + Changing the keys won't do anything, it's only a copy of the + keys dict. + """ + k = self.d2.keys() + k.remove(9) + self.assertEquals(self.d2.keys(), [1, 9, 0, 7]) + + def test_init_keys(self): + """ + Initialising a SortedDict with two keys will just take the first one. + + A real dict will actually take the second value so we will too, but + we'll keep the ordering from the first key found. + """ + tuples = ((2, 'two'), (1, 'one'), (2, 'second-two')) + d = SortedDict(tuples) + + self.assertEquals(d.keys(), [2, 1]) + + real_dict = dict(tuples) + self.assertEquals(sorted(real_dict.values()), ['one', 'second-two']) + + # Here the order of SortedDict values *is* what we are testing + self.assertEquals(d.values(), ['second-two', 'one']) From 8425cdef452bae10e1c23199f0a89a98e785bc8d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:36:07 +0000 Subject: [PATCH 207/902] [1.2.X] Added imports for some new unittest modules. Thanks to Stephan Jaekel. Backport of r13892 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13913 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/utils/tests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index 05a3253b9c9f..6d3bbfa86cc1 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -12,4 +12,7 @@ from simplelazyobject import * from decorators import * from functional import * - +from timesince import * +from datastructures import * +from tzinfo import * +from datetime_safe import * From 2f480e088244e4fb81d3cf27d1ca7f533698a41a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:36:30 +0000 Subject: [PATCH 208/902] [1.2.X] Migrated urlpatterns_reverse doctests. Thanks to Stephan Jaekel. Backport of r13893 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13914 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../urlpatterns_reverse/tests.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index eed7c65dd7ae..5b691eafa3f7 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -1,19 +1,6 @@ """ Unit tests for reverse URL lookups. """ -__test__ = {'API_TESTS': """ - -RegexURLResolver should raise an exception when no urlpatterns exist. - ->>> from django.core.urlresolvers import RegexURLResolver ->>> no_urls = 'regressiontests.urlpatterns_reverse.no_urls' ->>> resolver = RegexURLResolver(r'^$', no_urls) ->>> resolver.url_patterns -Traceback (most recent call last): -... -ImproperlyConfigured: The included urlconf regressiontests.urlpatterns_reverse.no_urls doesn't have any patterns in it -"""} - import unittest from django.conf import settings @@ -107,6 +94,27 @@ ) +class NoURLPatternsTests(TestCase): + urls = 'regressiontests.urlpatterns_reverse.no_urls' + + def assertRaisesErrorWithMessage(self, error, message, callable, + *args, **kwargs): + self.assertRaises(error, callable, *args, **kwargs) + try: + callable(*args, **kwargs) + except error, e: + self.assertEqual(message, str(e)) + + def test_no_urls_exception(self): + """ + RegexURLResolver should raise an exception when no urlpatterns exist. + """ + resolver = RegexURLResolver(r'^$', self.urls) + + self.assertRaisesErrorWithMessage(ImproperlyConfigured, + "The included urlconf regressiontests.urlpatterns_reverse.no_urls "\ + "doesn't have any patterns in it", getattr, resolver, 'url_patterns') + class URLPatternReverse(TestCase): urls = 'regressiontests.urlpatterns_reverse.urls' From 74b6c76e25404d9ca5c8a8d76a29597582adec4d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:36:54 +0000 Subject: [PATCH 209/902] [1.2.X] Migrated text doctests. Thanks to Stephan Jaekel. Backport of r13894 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13915 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/text/tests.py | 143 ++++++++++++++++------------ 1 file changed, 80 insertions(+), 63 deletions(-) diff --git a/tests/regressiontests/text/tests.py b/tests/regressiontests/text/tests.py index 82cb1267d1b7..fd02036f99e8 100644 --- a/tests/regressiontests/text/tests.py +++ b/tests/regressiontests/text/tests.py @@ -1,64 +1,81 @@ # coding: utf-8 -r""" -# Tests for stuff in django.utils.text and other text munging util functions. - ->>> from django.utils.text import * - -### smart_split ########################################################### ->>> list(smart_split(r'''This is "a person" test.''')) -[u'This', u'is', u'"a person"', u'test.'] ->>> print list(smart_split(r'''This is "a person's" test.'''))[2] -"a person's" ->>> print list(smart_split(r'''This is "a person\"s" test.'''))[2] -"a person\"s" ->>> list(smart_split('''"a 'one''')) -[u'"a', u"'one"] ->>> print list(smart_split(r'''all friends' tests'''))[1] -friends' ->>> list(smart_split(u'url search_page words="something else"')) -[u'url', u'search_page', u'words="something else"'] ->>> list(smart_split(u"url search_page words='something else'")) -[u'url', u'search_page', u"words='something else'"] ->>> list(smart_split(u'url search_page words "something else"')) -[u'url', u'search_page', u'words', u'"something else"'] ->>> list(smart_split(u'url search_page words-"something else"')) -[u'url', u'search_page', u'words-"something else"'] ->>> list(smart_split(u'url search_page words=hello')) -[u'url', u'search_page', u'words=hello'] ->>> list(smart_split(u'url search_page words="something else')) -[u'url', u'search_page', u'words="something', u'else'] ->>> list(smart_split("cut:','|cut:' '")) -[u"cut:','|cut:' '"] - -### urlquote ############################################################# ->>> from django.utils.http import urlquote, urlquote_plus ->>> urlquote(u'Paris & Orl\xe9ans') -u'Paris%20%26%20Orl%C3%A9ans' ->>> urlquote(u'Paris & Orl\xe9ans', safe="&") -u'Paris%20&%20Orl%C3%A9ans' ->>> urlquote_plus(u'Paris & Orl\xe9ans') -u'Paris+%26+Orl%C3%A9ans' ->>> urlquote_plus(u'Paris & Orl\xe9ans', safe="&") -u'Paris+&+Orl%C3%A9ans' - -### cookie_date, http_date ############################################### ->>> from django.utils.http import cookie_date, http_date ->>> t = 1167616461.0 ->>> cookie_date(t) -'Mon, 01-Jan-2007 01:54:21 GMT' ->>> http_date(t) -'Mon, 01 Jan 2007 01:54:21 GMT' - -### iri_to_uri ########################################################### ->>> from django.utils.encoding import iri_to_uri ->>> iri_to_uri(u'red%09ros\xe9#red') -'red%09ros%C3%A9#red' ->>> iri_to_uri(u'/blog/for/J\xfcrgen M\xfcnster/') -'/blog/for/J%C3%BCrgen%20M%C3%BCnster/' ->>> iri_to_uri(u'locations/%s' % urlquote_plus(u'Paris & Orl\xe9ans')) -'locations/Paris+%26+Orl%C3%A9ans' - -iri_to_uri() is idempotent: ->>> iri_to_uri(iri_to_uri(u'red%09ros\xe9#red')) -'red%09ros%C3%A9#red' -""" +from django.test import TestCase + +from django.utils.text import * +from django.utils.http import urlquote, urlquote_plus, cookie_date, http_date +from django.utils.encoding import iri_to_uri + +class TextTests(TestCase): + """ + Tests for stuff in django.utils.text and other text munging util functions. + """ + + def test_smart_split(self): + + self.assertEquals(list(smart_split(r'''This is "a person" test.''')), + [u'This', u'is', u'"a person"', u'test.']) + + self.assertEquals(list(smart_split(r'''This is "a person's" test.'''))[2], + u'"a person\'s"') + + self.assertEquals(list(smart_split(r'''This is "a person\"s" test.'''))[2], + u'"a person\\"s"') + + self.assertEquals(list(smart_split('''"a 'one''')), [u'"a', u"'one"]) + + self.assertEquals(list(smart_split(r'''all friends' tests'''))[1], + "friends'") + + self.assertEquals(list(smart_split(u'url search_page words="something else"')), + [u'url', u'search_page', u'words="something else"']) + + self.assertEquals(list(smart_split(u"url search_page words='something else'")), + [u'url', u'search_page', u"words='something else'"]) + + self.assertEquals(list(smart_split(u'url search_page words "something else"')), + [u'url', u'search_page', u'words', u'"something else"']) + + self.assertEquals(list(smart_split(u'url search_page words-"something else"')), + [u'url', u'search_page', u'words-"something else"']) + + self.assertEquals(list(smart_split(u'url search_page words=hello')), + [u'url', u'search_page', u'words=hello']) + + self.assertEquals(list(smart_split(u'url search_page words="something else')), + [u'url', u'search_page', u'words="something', u'else']) + + self.assertEquals(list(smart_split("cut:','|cut:' '")), + [u"cut:','|cut:' '"]) + + def test_urlquote(self): + + self.assertEquals(urlquote(u'Paris & Orl\xe9ans'), + u'Paris%20%26%20Orl%C3%A9ans') + self.assertEquals(urlquote(u'Paris & Orl\xe9ans', safe="&"), + u'Paris%20&%20Orl%C3%A9ans') + self.assertEquals(urlquote_plus(u'Paris & Orl\xe9ans'), + u'Paris+%26+Orl%C3%A9ans') + self.assertEquals(urlquote_plus(u'Paris & Orl\xe9ans', safe="&"), + u'Paris+&+Orl%C3%A9ans') + + def test_cookie_date(self): + t = 1167616461.0 + self.assertEquals(cookie_date(t), 'Mon, 01-Jan-2007 01:54:21 GMT') + + def test_http_date(self): + t = 1167616461.0 + self.assertEquals(http_date(t), 'Mon, 01 Jan 2007 01:54:21 GMT') + + def test_iri_to_uri(self): + self.assertEquals(iri_to_uri(u'red%09ros\xe9#red'), + 'red%09ros%C3%A9#red') + + self.assertEquals(iri_to_uri(u'/blog/for/J\xfcrgen M\xfcnster/'), + '/blog/for/J%C3%BCrgen%20M%C3%BCnster/') + + self.assertEquals(iri_to_uri(u'locations/%s' % urlquote_plus(u'Paris & Orl\xe9ans')), + 'locations/Paris+%26+Orl%C3%A9ans') + + def test_iri_to_uri_idempotent(self): + self.assertEquals(iri_to_uri(iri_to_uri(u'red%09ros\xe9#red')), + 'red%09ros%C3%A9#red') From 025ee2c13e9e3c386c746c03ba7cd7603b4b7d10 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:37:19 +0000 Subject: [PATCH 210/902] [1.2.X] Migrated string_lookup doctests. Thanks to Stephan Jaekel. Backport of r13895 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13916 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/string_lookup/models.py | 64 --------------- tests/regressiontests/string_lookup/tests.py | 78 +++++++++++++++++++ 2 files changed, 78 insertions(+), 64 deletions(-) create mode 100644 tests/regressiontests/string_lookup/tests.py diff --git a/tests/regressiontests/string_lookup/models.py b/tests/regressiontests/string_lookup/models.py index 1bdb2d4452b2..037854dadd80 100644 --- a/tests/regressiontests/string_lookup/models.py +++ b/tests/regressiontests/string_lookup/models.py @@ -43,67 +43,3 @@ class Article(models.Model): def __str__(self): return "Article %s" % self.name - -__test__ = {'API_TESTS': ur""" -# Regression test for #1661 and #1662: Check that string form referencing of -# models works, both as pre and post reference, on all RelatedField types. - ->>> f1 = Foo(name="Foo1") ->>> f1.save() ->>> f2 = Foo(name="Foo2") ->>> f2.save() - ->>> w1 = Whiz(name="Whiz1") ->>> w1.save() - ->>> b1 = Bar(name="Bar1", normal=f1, fwd=w1, back=f2) ->>> b1.save() - ->>> b1.normal - - ->>> b1.fwd - - ->>> b1.back - - ->>> base1 = Base(name="Base1") ->>> base1.save() - ->>> child1 = Child(name="Child1", parent=base1) ->>> child1.save() - ->>> child1.parent - - -# Regression tests for #3937: make sure we can use unicode characters in -# queries. -# BUG: These tests fail on MySQL, but it's a problem with the test setup. A -# properly configured UTF-8 database can handle this. - ->>> fx = Foo(name='Bjorn', friend=u'François') ->>> fx.save() ->>> Foo.objects.get(friend__contains=u'\xe7') - - -# We can also do the above query using UTF-8 strings. ->>> Foo.objects.get(friend__contains='\xc3\xa7') - - -# Regression tests for #5087: make sure we can perform queries on TextFields. ->>> a = Article(name='Test', text='The quick brown fox jumps over the lazy dog.') ->>> a.save() ->>> Article.objects.get(text__exact='The quick brown fox jumps over the lazy dog.') - - ->>> Article.objects.get(text__contains='quick brown fox') - - -# Regression test for #708: "like" queries on IP address fields require casting -# to text (on PostgreSQL). ->>> Article(name='IP test', text='The body', submitted_from='192.0.2.100').save() ->>> Article.objects.filter(submitted_from__contains='192.0.2') -[] - -"""} diff --git a/tests/regressiontests/string_lookup/tests.py b/tests/regressiontests/string_lookup/tests.py new file mode 100644 index 000000000000..ddf7a8a76c31 --- /dev/null +++ b/tests/regressiontests/string_lookup/tests.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +from django.test import TestCase +from regressiontests.string_lookup.models import Foo, Whiz, Bar, Article, Base, Child + +class StringLookupTests(TestCase): + + def test_string_form_referencing(self): + """ + Regression test for #1661 and #1662 + + Check that string form referencing of + models works, both as pre and post reference, on all RelatedField types. + """ + + f1 = Foo(name="Foo1") + f1.save() + f2 = Foo(name="Foo2") + f2.save() + + w1 = Whiz(name="Whiz1") + w1.save() + + b1 = Bar(name="Bar1", normal=f1, fwd=w1, back=f2) + b1.save() + + self.assertEquals(b1.normal, f1) + + self.assertEquals(b1.fwd, w1) + + self.assertEquals(b1.back, f2) + + base1 = Base(name="Base1") + base1.save() + + child1 = Child(name="Child1", parent=base1) + child1.save() + + self.assertEquals(child1.parent, base1) + + def test_unicode_chars_in_queries(self): + """ + Regression tests for #3937 + + make sure we can use unicode characters in queries. + If these tests fail on MySQL, it's a problem with the test setup. + A properly configured UTF-8 database can handle this. + """ + + fx = Foo(name='Bjorn', friend=u'François') + fx.save() + self.assertEquals(Foo.objects.get(friend__contains=u'\xe7'), fx) + + # We can also do the above query using UTF-8 strings. + self.assertEquals(Foo.objects.get(friend__contains='\xc3\xa7'), fx) + + def test_queries_on_textfields(self): + """ + Regression tests for #5087 + + make sure we can perform queries on TextFields. + """ + + a = Article(name='Test', text='The quick brown fox jumps over the lazy dog.') + a.save() + self.assertEquals(Article.objects.get(text__exact='The quick brown fox jumps over the lazy dog.'), a) + + self.assertEquals(Article.objects.get(text__contains='quick brown fox'), a) + + def test_ipaddress_on_postgresql(self): + """ + Regression test for #708 + + "like" queries on IP address fields require casting to text (on PostgreSQL). + """ + a = Article(name='IP test', text='The body', submitted_from='192.0.2.100') + a.save() + self.assertEquals(repr(Article.objects.filter(submitted_from__contains='192.0.2')), + repr([a])) From 554be48d01d9aea9af9d27e7296488766f04f8c1 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:37:45 +0000 Subject: [PATCH 211/902] [1.2.X] Migrated signals_regress doctests. Thanks to Stephan Jaekel. Backport of r13896 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13917 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../regressiontests/signals_regress/models.py | 75 --------------- .../regressiontests/signals_regress/tests.py | 96 +++++++++++++++++++ 2 files changed, 96 insertions(+), 75 deletions(-) create mode 100644 tests/regressiontests/signals_regress/tests.py diff --git a/tests/regressiontests/signals_regress/models.py b/tests/regressiontests/signals_regress/models.py index 2b40ebc21ae5..e7879d8cccdc 100644 --- a/tests/regressiontests/signals_regress/models.py +++ b/tests/regressiontests/signals_regress/models.py @@ -1,7 +1,3 @@ -""" -Testing signals before/after saving and deleting. -""" - from django.db import models class Author(models.Model): @@ -16,74 +12,3 @@ class Book(models.Model): def __unicode__(self): return self.name - -def pre_save_test(signal, sender, instance, **kwargs): - print 'pre_save signal,', instance - if kwargs.get('raw'): - print 'Is raw' - -def post_save_test(signal, sender, instance, **kwargs): - print 'post_save signal,', instance - if 'created' in kwargs: - if kwargs['created']: - print 'Is created' - else: - print 'Is updated' - if kwargs.get('raw'): - print 'Is raw' - -def pre_delete_test(signal, sender, instance, **kwargs): - print 'pre_delete signal,', instance - print 'instance.id is not None: %s' % (instance.id != None) - -def post_delete_test(signal, sender, instance, **kwargs): - print 'post_delete signal,', instance - print 'instance.id is not None: %s' % (instance.id != None) - -__test__ = {'API_TESTS':""" - -# Save up the number of connected signals so that we can check at the end -# that all the signals we register get properly unregistered (#9989) ->>> pre_signals = (len(models.signals.pre_save.receivers), -... len(models.signals.post_save.receivers), -... len(models.signals.pre_delete.receivers), -... len(models.signals.post_delete.receivers)) - ->>> models.signals.pre_save.connect(pre_save_test) ->>> models.signals.post_save.connect(post_save_test) ->>> models.signals.pre_delete.connect(pre_delete_test) ->>> models.signals.post_delete.connect(post_delete_test) - ->>> a1 = Author(name='Neal Stephenson') ->>> a1.save() -pre_save signal, Neal Stephenson -post_save signal, Neal Stephenson -Is created - ->>> b1 = Book(name='Snow Crash') ->>> b1.save() -pre_save signal, Snow Crash -post_save signal, Snow Crash -Is created - -# Assigning to m2m shouldn't generate an m2m signal ->>> b1.authors = [a1] - -# Removing an author from an m2m shouldn't generate an m2m signal ->>> b1.authors = [] - ->>> models.signals.post_delete.disconnect(post_delete_test) ->>> models.signals.pre_delete.disconnect(pre_delete_test) ->>> models.signals.post_save.disconnect(post_save_test) ->>> models.signals.pre_save.disconnect(pre_save_test) - -# Check that all our signals got disconnected properly. ->>> post_signals = (len(models.signals.pre_save.receivers), -... len(models.signals.post_save.receivers), -... len(models.signals.pre_delete.receivers), -... len(models.signals.post_delete.receivers)) - ->>> pre_signals == post_signals -True - -"""} diff --git a/tests/regressiontests/signals_regress/tests.py b/tests/regressiontests/signals_regress/tests.py new file mode 100644 index 000000000000..234893fdfef7 --- /dev/null +++ b/tests/regressiontests/signals_regress/tests.py @@ -0,0 +1,96 @@ +import sys +from StringIO import StringIO +from django.test import TestCase + +from django.db import models +from regressiontests.signals_regress.models import Author, Book + +signal_output = [] + +def pre_save_test(signal, sender, instance, **kwargs): + signal_output.append('pre_save signal, %s' % instance) + if kwargs.get('raw'): + signal_output.append('Is raw') + +def post_save_test(signal, sender, instance, **kwargs): + signal_output.append('post_save signal, %s' % instance) + if 'created' in kwargs: + if kwargs['created']: + signal_output.append('Is created') + else: + signal_output.append('Is updated') + if kwargs.get('raw'): + signal_output.append('Is raw') + +def pre_delete_test(signal, sender, instance, **kwargs): + signal_output.append('pre_save signal, %s' % instance) + signal_output.append('instance.id is not None: %s' % (instance.id != None)) + +def post_delete_test(signal, sender, instance, **kwargs): + signal_output.append('post_delete signal, %s' % instance) + signal_output.append('instance.id is not None: %s' % (instance.id != None)) + +class SignalsRegressTests(TestCase): + """ + Testing signals before/after saving and deleting. + """ + + def get_signal_output(self, fn, *args, **kwargs): + # Flush any existing signal output + global signal_output + signal_output = [] + fn(*args, **kwargs) + return signal_output + + def setUp(self): + # Save up the number of connected signals so that we can check at the end + # that all the signals we register get properly unregistered (#9989) + self.pre_signals = (len(models.signals.pre_save.receivers), + len(models.signals.post_save.receivers), + len(models.signals.pre_delete.receivers), + len(models.signals.post_delete.receivers)) + + models.signals.pre_save.connect(pre_save_test) + models.signals.post_save.connect(post_save_test) + models.signals.pre_delete.connect(pre_delete_test) + models.signals.post_delete.connect(post_delete_test) + + def tearDown(self): + models.signals.post_delete.disconnect(post_delete_test) + models.signals.pre_delete.disconnect(pre_delete_test) + models.signals.post_save.disconnect(post_save_test) + models.signals.pre_save.disconnect(pre_save_test) + + # Check that all our signals got disconnected properly. + post_signals = (len(models.signals.pre_save.receivers), + len(models.signals.post_save.receivers), + len(models.signals.pre_delete.receivers), + len(models.signals.post_delete.receivers)) + + self.assertEquals(self.pre_signals, post_signals) + + def test_model_signals(self): + """ Model saves should throw some signals. """ + a1 = Author(name='Neal Stephenson') + self.assertEquals(self.get_signal_output(a1.save), [ + "pre_save signal, Neal Stephenson", + "post_save signal, Neal Stephenson", + "Is created" + ]) + + b1 = Book(name='Snow Crash') + self.assertEquals(self.get_signal_output(b1.save), [ + "pre_save signal, Snow Crash", + "post_save signal, Snow Crash", + "Is created" + ]) + + def test_m2m_signals(self): + """ Assigning and removing to/from m2m shouldn't generate an m2m signal """ + + b1 = Book(name='Snow Crash') + self.get_signal_output(b1.save) + a1 = Author(name='Neal Stephenson') + self.get_signal_output(a1.save) + self.assertEquals(self.get_signal_output(setattr, b1, 'authors', [a1]), []) + self.assertEquals(self.get_signal_output(setattr, b1, 'authors', []), []) From 25119398832db630322bfa845a381ea7f5b068d5 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 27 Sep 2010 15:38:08 +0000 Subject: [PATCH 212/902] [1.2.X] Added Stephan Jaekel to AUTHORS for his excellent work purging doctests. Backport of r13897 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13918 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 15e82a88cf8b..6e56b24dae52 100644 --- a/AUTHORS +++ b/AUTHORS @@ -231,6 +231,7 @@ answer newbie questions, and generally made Django that much better: Ibon Tom Insam Baurzhan Ismagulov + Stephan Jaekel james_027@yahoo.com jcrasta@gmail.com jdetaeye From 229c738a03b938fd81a4858ad51750c76d2d666c Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 27 Sep 2010 16:22:30 +0000 Subject: [PATCH 213/902] [1.2.X] Fixed #14053 -- Also localize long integers. Thanks, David Danier. Backport from trunk (r13920). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13921 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/formats.py | 4 ++-- tests/regressiontests/i18n/tests.py | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/django/utils/formats.py b/django/utils/formats.py index b26065c7c2be..bf48a4fbe0e4 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -93,7 +93,7 @@ def localize(value): Checks if value is a localizable type (date, number...) and returns it formatted as a string using current locale format """ - if isinstance(value, (decimal.Decimal, float, int)): + if isinstance(value, (decimal.Decimal, float, int, long)): return number_format(value) elif isinstance(value, datetime.datetime): return date_format(value, 'DATETIME_FORMAT') @@ -109,7 +109,7 @@ def localize_input(value, default=None): Checks if an input value is a localizable type and returns it formatted with the appropriate formatting string of the current locale. """ - if isinstance(value, (decimal.Decimal, float, int)): + if isinstance(value, (decimal.Decimal, float, int, long)): return number_format(value) if isinstance(value, datetime.datetime): value = datetime_safe.new_datetime(value) diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 75f8d2591700..77d588326538 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -122,12 +122,14 @@ def setUp(self): self.d = datetime.date(2009, 12, 31) self.dt = datetime.datetime(2009, 12, 31, 20, 50) self.t = datetime.time(10, 15, 48) + self.l = 10000L self.ctxt = Context({ 'n': self.n, 't': self.t, 'd': self.d, 'dt': self.dt, - 'f': self.f + 'f': self.f, + 'l': self.l, }) def tearDown(self): @@ -152,6 +154,7 @@ def test_locale_independent(self): self.assertEqual(u'6B6B6B6B6A6', nformat(self.n, decimal_sep='A', decimal_pos=1, grouping=1, thousand_sep='B')) self.assertEqual(u'-66666.6', nformat(-66666.666, decimal_sep='.', decimal_pos=1)) self.assertEqual(u'-66666.0', nformat(int('-66666'), decimal_sep='.', decimal_pos=1)) + self.assertEqual(u'10000.0', nformat(self.l, decimal_sep='.', decimal_pos=1)) # date filter self.assertEqual(u'31.12.2009 в 20:50', Template('{{ dt|date:"d.m.Y в H:i" }}').render(self.ctxt)) @@ -165,16 +168,17 @@ def test_l10n_disabled(self): settings.USE_L10N = False activate('ca') try: - self.assertEqual('N j, Y', get_format('DATE_FORMAT')) + self.assertEqual(u'N j, Y', get_format('DATE_FORMAT')) self.assertEqual(0, get_format('FIRST_DAY_OF_WEEK')) - self.assertEqual('.', get_format('DECIMAL_SEPARATOR')) + self.assertEqual(u'.', get_format('DECIMAL_SEPARATOR')) self.assertEqual(u'10:15 a.m.', time_format(self.t)) self.assertEqual(u'des. 31, 2009', date_format(self.d)) self.assertEqual(u'desembre 2009', date_format(self.d, 'YEAR_MONTH_FORMAT')) self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(self.dt, 'SHORT_DATETIME_FORMAT')) - self.assertEqual('No localizable', localize('No localizable')) - self.assertEqual('66666.666', localize(self.n)) - self.assertEqual('99999.999', localize(self.f)) + self.assertEqual(u'No localizable', localize('No localizable')) + self.assertEqual(u'66666.666', localize(self.n)) + self.assertEqual(u'99999.999', localize(self.f)) + self.assertEqual(u'10000', localize(self.l)) self.assertEqual(u'des. 31, 2009', localize(self.d)) self.assertEqual(u'des. 31, 2009, 8:50 p.m.', localize(self.dt)) self.assertEqual(u'66666.666', Template('{{ n }}').render(self.ctxt)) @@ -245,16 +249,19 @@ def test_l10n_enabled(self): settings.USE_THOUSAND_SEPARATOR = True self.assertEqual(u'66.666,666', localize(self.n)) self.assertEqual(u'99.999,999', localize(self.f)) + self.assertEqual(u'10.000', localize(self.l)) settings.USE_THOUSAND_SEPARATOR = False self.assertEqual(u'66666,666', localize(self.n)) self.assertEqual(u'99999,999', localize(self.f)) + self.assertEqual(u'10000', localize(self.l)) self.assertEqual(u'31 de desembre de 2009', localize(self.d)) self.assertEqual(u'31 de desembre de 2009 a les 20:50', localize(self.dt)) settings.USE_THOUSAND_SEPARATOR = True self.assertEqual(u'66.666,666', Template('{{ n }}').render(self.ctxt)) self.assertEqual(u'99.999,999', Template('{{ f }}').render(self.ctxt)) + self.assertEqual(u'10.000', Template('{{ l }}').render(self.ctxt)) form3 = I18nForm({ 'decimal_field': u'66.666,666', @@ -324,21 +331,24 @@ def test_l10n_enabled(self): self.assertEqual(u'Dec. 31, 2009', date_format(self.d)) self.assertEqual(u'December 2009', date_format(self.d, 'YEAR_MONTH_FORMAT')) self.assertEqual(u'12/31/2009 8:50 p.m.', date_format(self.dt, 'SHORT_DATETIME_FORMAT')) - self.assertEqual('No localizable', localize('No localizable')) + self.assertEqual(u'No localizable', localize('No localizable')) settings.USE_THOUSAND_SEPARATOR = True self.assertEqual(u'66,666.666', localize(self.n)) self.assertEqual(u'99,999.999', localize(self.f)) + self.assertEqual(u'10,000', localize(self.l)) settings.USE_THOUSAND_SEPARATOR = False self.assertEqual(u'66666.666', localize(self.n)) self.assertEqual(u'99999.999', localize(self.f)) + self.assertEqual(u'10000', localize(self.l)) self.assertEqual(u'Dec. 31, 2009', localize(self.d)) self.assertEqual(u'Dec. 31, 2009, 8:50 p.m.', localize(self.dt)) settings.USE_THOUSAND_SEPARATOR = True self.assertEqual(u'66,666.666', Template('{{ n }}').render(self.ctxt)) self.assertEqual(u'99,999.999', Template('{{ f }}').render(self.ctxt)) + self.assertEqual(u'10,000', Template('{{ l }}').render(self.ctxt)) settings.USE_THOUSAND_SEPARATOR = False self.assertEqual(u'66666.666', Template('{{ n }}').render(self.ctxt)) From cf0e0c47f0b68cb4cfe7d5711df44dd1c05c385e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 04:49:37 +0000 Subject: [PATCH 214/902] [1.2.X] Removed a test assertion that depended on primary key ordering. The test doesn't validate anything significant, and fails under Postgres. Thanks to Tobias McNulty and the magical Caktus buildbot for pointing out the problem. Backport of r13923 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13924 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/custom_columns_regress/tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/regressiontests/custom_columns_regress/tests.py b/tests/regressiontests/custom_columns_regress/tests.py index 6f6c4ecf948d..0587ab70704f 100644 --- a/tests/regressiontests/custom_columns_regress/tests.py +++ b/tests/regressiontests/custom_columns_regress/tests.py @@ -22,8 +22,6 @@ def setUp(self): self.authors = [self.a1, self.a2] def test_basic_creation(self): - self.assertEqual(self.a1.Author_ID, 1) - art = Article(headline='Django lets you build web apps easily', primary_author=self.a1) art.save() art.authors = [self.a1, self.a2] From e9f10deec0cc5c340d255f6f24df5d6889e94796 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 07:09:19 +0000 Subject: [PATCH 215/902] [1.2.X] Migrated select_related_regress doctests. Thanks to Stephan Jaekel. Backport of r13925 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13928 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../select_related_regress/models.py | 123 ---------------- .../select_related_regress/tests.py | 134 ++++++++++++++++++ 2 files changed, 134 insertions(+), 123 deletions(-) create mode 100644 tests/regressiontests/select_related_regress/tests.py diff --git a/tests/regressiontests/select_related_regress/models.py b/tests/regressiontests/select_related_regress/models.py index a673809369ca..3efb19e0acb9 100644 --- a/tests/regressiontests/select_related_regress/models.py +++ b/tests/regressiontests/select_related_regress/models.py @@ -84,126 +84,3 @@ class Item(models.Model): def __unicode__(self): return self.name - -__test__ = {'API_TESTS': """ -Regression test for bug #7110. When using select_related(), we must query the -Device and Building tables using two different aliases (each) in order to -differentiate the start and end Connection fields. The net result is that both -the "connections = ..." queries here should give the same results without -pulling in more than the absolute minimum number of tables (history has -shown that it's easy to make a mistake in the implementation and include some -unnecessary bonus joins). - ->>> b=Building.objects.create(name='101') ->>> dev1=Device.objects.create(name="router", building=b) ->>> dev2=Device.objects.create(name="switch", building=b) ->>> dev3=Device.objects.create(name="server", building=b) ->>> port1=Port.objects.create(port_number='4',device=dev1) ->>> port2=Port.objects.create(port_number='7',device=dev2) ->>> port3=Port.objects.create(port_number='1',device=dev3) ->>> c1=Connection.objects.create(start=port1, end=port2) ->>> c2=Connection.objects.create(start=port2, end=port3) - ->>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).order_by('id') ->>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections] -[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')] - ->>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).select_related().order_by('id') ->>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections] -[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')] - -# This final query should only join seven tables (port, device and building -# twice each, plus connection once). ->>> connections.query.count_active_tables() -7 - -Regression test for bug #8106. Same sort of problem as the previous test, but -this time there are more extra tables to pull in as part of the -select_related() and some of them could potentially clash (so need to be kept -separate). - ->>> us = TUser.objects.create(name="std") ->>> usp = Person.objects.create(user=us) ->>> uo = TUser.objects.create(name="org") ->>> uop = Person.objects.create(user=uo) ->>> s = Student.objects.create(person = usp) ->>> o = Organizer.objects.create(person = uop) ->>> c = Class.objects.create(org=o) ->>> e = Enrollment.objects.create(std=s, cls=c) - ->>> e_related = Enrollment.objects.all().select_related()[0] ->>> e_related.std.person.user.name -u"std" ->>> e_related.cls.org.person.user.name -u"org" - -Regression test for bug #8036: the first related model in the tests below -("state") is empty and we try to select the more remotely related -state__country. The regression here was not skipping the empty column results -for country before getting status. - ->>> australia = Country.objects.create(name='Australia') ->>> active = ClientStatus.objects.create(name='active') ->>> client = Client.objects.create(name='client', status=active) - ->>> client.status - ->>> Client.objects.select_related()[0].status - ->>> Client.objects.select_related('state')[0].status - ->>> Client.objects.select_related('state', 'status')[0].status - ->>> Client.objects.select_related('state__country')[0].status - ->>> Client.objects.select_related('state__country', 'status')[0].status - ->>> Client.objects.select_related('status')[0].status - - -Exercising select_related() with multi-table model inheritance. ->>> c1 = Child.objects.create(name="child1", value=42) ->>> _ = Item.objects.create(name="item1", child=c1) ->>> _ = Item.objects.create(name="item2") ->>> Item.objects.select_related("child").order_by("name") -[, ] - -# Regression for #12851 - Deferred fields are used correctly if you -# select_related a subset of fields. ->>> wa = State.objects.create(name="Western Australia", country=australia) ->>> _ = Client.objects.create(name='Brian Burke', state=wa, status=active) ->>> burke = Client.objects.select_related('state').defer('state__name').get(name='Brian Burke') ->>> burke.name -u'Brian Burke' ->>> burke.state.name -u'Western Australia' - -# Still works if we're dealing with an inherited class ->>> _ = SpecialClient.objects.create(name='Troy Buswell', state=wa, status=active, value=42) ->>> troy = SpecialClient.objects.select_related('state').defer('state__name').get(name='Troy Buswell') ->>> troy.name -u'Troy Buswell' ->>> troy.value -42 ->>> troy.state.name -u'Western Australia' - -# Still works if we defer an attribute on the inherited class ->>> troy = SpecialClient.objects.select_related('state').defer('value', 'state__name').get(name='Troy Buswell') ->>> troy.name -u'Troy Buswell' ->>> troy.value -42 ->>> troy.state.name -u'Western Australia' - -# Also works if you use only, rather than defer ->>> troy = SpecialClient.objects.select_related('state').only('name').get(name='Troy Buswell') ->>> troy.name -u'Troy Buswell' ->>> troy.value -42 ->>> troy.state.name -u'Western Australia' - -"""} diff --git a/tests/regressiontests/select_related_regress/tests.py b/tests/regressiontests/select_related_regress/tests.py new file mode 100644 index 000000000000..bfa1e2154b10 --- /dev/null +++ b/tests/regressiontests/select_related_regress/tests.py @@ -0,0 +1,134 @@ +from django.test import TestCase +from regressiontests.select_related_regress.models import * + +class SelectRelatedRegressTests(TestCase): + + def test_regression_7110(self): + """ + Regression test for bug #7110. + + When using select_related(), we must query the + Device and Building tables using two different aliases (each) in order to + differentiate the start and end Connection fields. The net result is that + both the "connections = ..." queries here should give the same results + without pulling in more than the absolute minimum number of tables + (history has shown that it's easy to make a mistake in the implementation + and include some unnecessary bonus joins). + """ + + b=Building.objects.create(name='101') + dev1=Device.objects.create(name="router", building=b) + dev2=Device.objects.create(name="switch", building=b) + dev3=Device.objects.create(name="server", building=b) + port1=Port.objects.create(port_number='4',device=dev1) + port2=Port.objects.create(port_number='7',device=dev2) + port3=Port.objects.create(port_number='1',device=dev3) + c1=Connection.objects.create(start=port1, end=port2) + c2=Connection.objects.create(start=port2, end=port3) + + connections=Connection.objects.filter(start__device__building=b, end__device__building=b).order_by('id') + self.assertEquals([(c.id, unicode(c.start), unicode(c.end)) for c in connections], + [(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]) + + connections=Connection.objects.filter(start__device__building=b, end__device__building=b).select_related().order_by('id') + self.assertEquals([(c.id, unicode(c.start), unicode(c.end)) for c in connections], + [(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]) + + # This final query should only join seven tables (port, device and building + # twice each, plus connection once). + self.assertEquals(connections.query.count_active_tables(), 7) + + + def test_regression_8106(self): + """ + Regression test for bug #8106. + + Same sort of problem as the previous test, but this time there are + more extra tables to pull in as part of the select_related() and some + of them could potentially clash (so need to be kept separate). + """ + + us = TUser.objects.create(name="std") + usp = Person.objects.create(user=us) + uo = TUser.objects.create(name="org") + uop = Person.objects.create(user=uo) + s = Student.objects.create(person = usp) + o = Organizer.objects.create(person = uop) + c = Class.objects.create(org=o) + e = Enrollment.objects.create(std=s, cls=c) + + e_related = Enrollment.objects.all().select_related()[0] + self.assertEquals(e_related.std.person.user.name, u"std") + self.assertEquals(e_related.cls.org.person.user.name, u"org") + + def test_regression_8036(self): + """ + Regression test for bug #8036 + + the first related model in the tests below + ("state") is empty and we try to select the more remotely related + state__country. The regression here was not skipping the empty column results + for country before getting status. + """ + + australia = Country.objects.create(name='Australia') + active = ClientStatus.objects.create(name='active') + client = Client.objects.create(name='client', status=active) + + self.assertEquals(client.status, active) + self.assertEquals(Client.objects.select_related()[0].status, active) + self.assertEquals(Client.objects.select_related('state')[0].status, active) + self.assertEquals(Client.objects.select_related('state', 'status')[0].status, active) + self.assertEquals(Client.objects.select_related('state__country')[0].status, active) + self.assertEquals(Client.objects.select_related('state__country', 'status')[0].status, active) + self.assertEquals(Client.objects.select_related('status')[0].status, active) + + def test_multi_table_inheritance(self): + """ Exercising select_related() with multi-table model inheritance. """ + c1 = Child.objects.create(name="child1", value=42) + i1 = Item.objects.create(name="item1", child=c1) + i2 = Item.objects.create(name="item2") + + self.assertQuerysetEqual( + Item.objects.select_related("child").order_by("name"), + ["", ""] + ) + + def test_regression_12851(self): + """ + Regression for #12851 + + Deferred fields are used correctly if you select_related a subset + of fields. + """ + australia = Country.objects.create(name='Australia') + active = ClientStatus.objects.create(name='active') + + wa = State.objects.create(name="Western Australia", country=australia) + c1 = Client.objects.create(name='Brian Burke', state=wa, status=active) + burke = Client.objects.select_related('state').defer('state__name').get(name='Brian Burke') + + self.assertEquals(burke.name, u'Brian Burke') + self.assertEquals(burke.state.name, u'Western Australia') + + # Still works if we're dealing with an inherited class + sc1 = SpecialClient.objects.create(name='Troy Buswell', state=wa, status=active, value=42) + troy = SpecialClient.objects.select_related('state').defer('state__name').get(name='Troy Buswell') + + self.assertEquals(troy.name, u'Troy Buswell') + self.assertEquals(troy.value, 42) + self.assertEquals(troy.state.name, u'Western Australia') + + # Still works if we defer an attribute on the inherited class + troy = SpecialClient.objects.select_related('state').defer('value', 'state__name').get(name='Troy Buswell') + + self.assertEquals(troy.name, u'Troy Buswell') + self.assertEquals(troy.value, 42) + self.assertEquals(troy.state.name, u'Western Australia') + + # Also works if you use only, rather than defer + troy = SpecialClient.objects.select_related('state').only('name').get(name='Troy Buswell') + + self.assertEquals(troy.name, u'Troy Buswell') + self.assertEquals(troy.value, 42) + self.assertEquals(troy.state.name, u'Western Australia') From 7230b609c3619cbcd503062fecf00871a6087e4d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 07:09:40 +0000 Subject: [PATCH 216/902] [1.2.X] Migrated reverse_single_related doctests. Thanks to Stephan Jaekel. Backport of r13926 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13929 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../reverse_single_related/models.py | 42 ------------------- .../reverse_single_related/tests.py | 36 ++++++++++++++++ 2 files changed, 36 insertions(+), 42 deletions(-) create mode 100644 tests/regressiontests/reverse_single_related/tests.py diff --git a/tests/regressiontests/reverse_single_related/models.py b/tests/regressiontests/reverse_single_related/models.py index b2b75392aa4e..a2d8fb0fac1a 100644 --- a/tests/regressiontests/reverse_single_related/models.py +++ b/tests/regressiontests/reverse_single_related/models.py @@ -1,11 +1,5 @@ -""" -Regression tests for an object that cannot access a single related object due -to a restrictive default manager. -""" - from django.db import models - class SourceManager(models.Manager): def get_query_set(self): return super(SourceManager, self).get_query_set().filter(is_public=True) @@ -16,39 +10,3 @@ class Source(models.Model): class Item(models.Model): source = models.ForeignKey(Source) - - -__test__ = {'API_TESTS':""" - ->>> public_source = Source.objects.create(is_public=True) ->>> public_item = Item.objects.create(source=public_source) - ->>> private_source = Source.objects.create(is_public=False) ->>> private_item = Item.objects.create(source=private_source) - -# Only one source is available via all() due to the custom default manager. - ->>> Source.objects.all() -[] - ->>> public_item.source - - -# Make sure that an item can still access its related source even if the default -# manager doesn't normally allow it. - ->>> private_item.source - - -# If the manager is marked "use_for_related_fields", it'll get used instead -# of the "bare" queryset. Usually you'd define this as a property on the class, -# but this approximates that in a way that's easier in tests. - ->>> Source.objects.use_for_related_fields = True ->>> private_item = Item.objects.get(pk=private_item.pk) ->>> private_item.source -Traceback (most recent call last): - ... -DoesNotExist: Source matching query does not exist. - -"""} diff --git a/tests/regressiontests/reverse_single_related/tests.py b/tests/regressiontests/reverse_single_related/tests.py new file mode 100644 index 000000000000..14f3a6665e37 --- /dev/null +++ b/tests/regressiontests/reverse_single_related/tests.py @@ -0,0 +1,36 @@ +from django.test import TestCase + +from regressiontests.reverse_single_related.models import * + +class ReverseSingleRelatedTests(TestCase): + """ + Regression tests for an object that cannot access a single related + object due to a restrictive default manager. + """ + + def test_reverse_single_related(self): + + public_source = Source.objects.create(is_public=True) + public_item = Item.objects.create(source=public_source) + + private_source = Source.objects.create(is_public=False) + private_item = Item.objects.create(source=private_source) + + # Only one source is available via all() due to the custom default manager. + self.assertQuerysetEqual( + Source.objects.all(), + [""] + ) + + self.assertEquals(public_item.source, public_source) + + # Make sure that an item can still access its related source even if the default + # manager doesn't normally allow it. + self.assertEquals(private_item.source, private_source) + + # If the manager is marked "use_for_related_fields", it'll get used instead + # of the "bare" queryset. Usually you'd define this as a property on the class, + # but this approximates that in a way that's easier in tests. + Source.objects.use_for_related_fields = True + private_item = Item.objects.get(pk=private_item.pk) + self.assertRaises(Source.DoesNotExist, lambda: private_item.source) From 66cb5d5aae6d0de993dd1ac972732aea2de8e605 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 07:10:05 +0000 Subject: [PATCH 217/902] [1.2.X] Migrated requests doctests. Thanks to Stephan Jaekel. Backport of r13927 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13930 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/requests/tests.py | 108 +++++++++++++----------- 1 file changed, 61 insertions(+), 47 deletions(-) diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index 1615a73406b9..2bf636c3a2ef 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -1,47 +1,61 @@ -""" ->>> from django.http import HttpRequest ->>> print repr(HttpRequest()) - - ->>> from django.core.handlers.wsgi import WSGIRequest ->>> print repr(WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus'})) -, -POST:, -COOKIES:{}, -META:{...}> - ->>> from django.core.handlers.modpython import ModPythonRequest ->>> class FakeModPythonRequest(ModPythonRequest): -... def __init__(self, *args, **kwargs): -... super(FakeModPythonRequest, self).__init__(*args, **kwargs) -... self._get = self._post = self._meta = self._cookies = {} ->>> class Dummy: -... def get_options(self): -... return {} ->>> req = Dummy() ->>> req.uri = 'bogus' ->>> print repr(FakeModPythonRequest(req)) - - ->>> from django.http import parse_cookie ->>> parse_cookie('invalid:key=true') -{} - ->>> request = HttpRequest() ->>> print request.build_absolute_uri(location="https://www.example.com/asdf") -https://www.example.com/asdf ->>> request.get_host = lambda: 'www.example.com' ->>> request.path = '' ->>> print request.build_absolute_uri(location="/path/with:colons") -http://www.example.com/path/with:colons -""" +from datetime import datetime, timedelta +import time +import unittest + +from django.http import HttpRequest, HttpResponse, parse_cookie +from django.core.handlers.wsgi import WSGIRequest +from django.core.handlers.modpython import ModPythonRequest +from django.utils.http import cookie_date + +class RequestsTests(unittest.TestCase): + + def test_httprequest(self): + self.assertEquals(repr(HttpRequest()), + "" + ) + + def test_wsgirequest(self): + self.assertEquals(repr(WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus'})), + ",\n"\ + "POST:,\n"\ + "COOKIES:{},\n"\ + "META:{'PATH_INFO': u'bogus', 'REQUEST_METHOD': 'bogus', 'SCRIPT_NAME': u''}>" + ) + + def test_modpythonrequest(self): + class FakeModPythonRequest(ModPythonRequest): + def __init__(self, *args, **kwargs): + super(FakeModPythonRequest, self).__init__(*args, **kwargs) + self._get = self._post = self._meta = self._cookies = {} + + class Dummy: + def get_options(self): + return {} + + req = Dummy() + req.uri = 'bogus' + self.assertEquals(repr(FakeModPythonRequest(req)), + "") + + def test_parse_cookie(self): + self.assertEquals(parse_cookie('invalid:key=true'), {}) + + def test_httprequest_location(self): + request = HttpRequest() + self.assertEquals(request.build_absolute_uri(location="https://www.example.com/asdf"), + 'https://www.example.com/asdf') + + request.get_host = lambda: 'www.example.com' + request.path = '' + self.assertEquals(request.build_absolute_uri(location="/path/with:colons"), + 'http://www.example.com/path/with:colons') From 45859fe9ffab2a5c28938d0722d6058076583dc2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 08:27:43 +0000 Subject: [PATCH 218/902] [1.2.X] Migrated one_to_one_regress doctests. Thanks to Stephan Jaekel. Backport of r13931 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13939 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../one_to_one_regress/models.py | 101 --------------- .../one_to_one_regress/tests.py | 120 +++++++++++++++++- 2 files changed, 114 insertions(+), 107 deletions(-) diff --git a/tests/regressiontests/one_to_one_regress/models.py b/tests/regressiontests/one_to_one_regress/models.py index 5c89c55f456e..a7edbc098e19 100644 --- a/tests/regressiontests/one_to_one_regress/models.py +++ b/tests/regressiontests/one_to_one_regress/models.py @@ -41,104 +41,3 @@ class Pointer(models.Model): class Pointer2(models.Model): other = models.OneToOneField(Target) - -__test__ = {'API_TESTS':""" -# Regression test for #1064 and #1506: Check that we create models via the m2m -# relation if the remote model has a OneToOneField. ->>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton') ->>> p1.save() ->>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False) ->>> r.save() ->>> f = Favorites(name = 'Fred') ->>> f.save() ->>> f.restaurants = [r] ->>> f.restaurants.all() -[] - -# Regression test for #7173: Check that the name of the cache for the -# reverse object is correct. ->>> b = Bar(place=p1, serves_cocktails=False) ->>> b.save() ->>> p1.restaurant - ->>> p1.bar - - -# -# Regression test for #6886 (the related-object cache) -# - -# Look up the objects again so that we get "fresh" objects ->>> p = Place.objects.get(name="Demon Dogs") ->>> r = p.restaurant - -# Accessing the related object again returns the exactly same object ->>> p.restaurant is r -True - -# But if we kill the cache, we get a new object ->>> del p._restaurant_cache ->>> p.restaurant is r -False - -# Reassigning the Restaurant object results in an immediate cache update -# We can't use a new Restaurant because that'll violate one-to-one, but -# with a new *instance* the is test below will fail if #6886 regresses. ->>> r2 = Restaurant.objects.get(pk=r.pk) ->>> p.restaurant = r2 ->>> p.restaurant is r2 -True - -# Assigning None succeeds if field is null=True. ->>> ug_bar = UndergroundBar.objects.create(place=p, serves_cocktails=False) ->>> ug_bar.place = None ->>> ug_bar.place is None -True - -# Assigning None fails: Place.restaurant is null=False ->>> p.restaurant = None -Traceback (most recent call last): - ... -ValueError: Cannot assign None: "Place.restaurant" does not allow null values. - -# You also can't assign an object of the wrong type here ->>> p.restaurant = p -Traceback (most recent call last): - ... -ValueError: Cannot assign "": "Place.restaurant" must be a "Restaurant" instance. - -# Creation using keyword argument should cache the related object. ->>> p = Place.objects.get(name="Demon Dogs") ->>> r = Restaurant(place=p) ->>> r.place is p -True - -# Creation using keyword argument and unsaved related instance (#8070). ->>> p = Place() ->>> r = Restaurant(place=p) ->>> r.place is p -True - -# Creation using attname keyword argument and an id will cause the related -# object to be fetched. ->>> p = Place.objects.get(name="Demon Dogs") ->>> r = Restaurant(place_id=p.id) ->>> r.place is p -False ->>> r.place == p -True - -# Regression test for #9968: filtering reverse one-to-one relations with -# primary_key=True was misbehaving. We test both (primary_key=True & False) -# cases here to prevent any reappearance of the problem. ->>> _ = Target.objects.create() ->>> Target.objects.filter(pointer=None) -[] ->>> Target.objects.exclude(pointer=None) -[] ->>> Target.objects.filter(pointer2=None) -[] ->>> Target.objects.exclude(pointer2=None) -[] - -"""} diff --git a/tests/regressiontests/one_to_one_regress/tests.py b/tests/regressiontests/one_to_one_regress/tests.py index a0a0c0584f1a..8787575a04f0 100644 --- a/tests/regressiontests/one_to_one_regress/tests.py +++ b/tests/regressiontests/one_to_one_regress/tests.py @@ -1,22 +1,130 @@ from django.test import TestCase -from regressiontests.one_to_one_regress.models import Place, UndergroundBar +from regressiontests.one_to_one_regress.models import * + +class OneToOneRegressionTests(TestCase): + + def setUp(self): + self.p1 = Place(name='Demon Dogs', address='944 W. Fullerton') + self.p1.save() + self.r1 = Restaurant(place=self.p1, serves_hot_dogs=True, serves_pizza=False) + self.r1.save() + self.b1 = Bar(place=self.p1, serves_cocktails=False) + self.b1.save() -class OneToOneDeletionTests(TestCase): def test_reverse_relationship_cache_cascade(self): """ Regression test for #9023: accessing the reverse relationship shouldn't result in a cascading delete(). """ - place = Place.objects.create(name="Dempsey's", address="623 Vermont St") - bar = UndergroundBar.objects.create(place=place, serves_cocktails=False) + bar = UndergroundBar.objects.create(place=self.p1, serves_cocktails=False) # The bug in #9023: if you access the one-to-one relation *before* # setting to None and deleting, the cascade happens anyway. - place.undergroundbar + self.p1.undergroundbar bar.place.name='foo' bar.place = None bar.save() - place.delete() + self.p1.delete() self.assertEqual(Place.objects.all().count(), 0) self.assertEqual(UndergroundBar.objects.all().count(), 1) + + def test_create_models_m2m(self): + """ + Regression test for #1064 and #1506 + + Check that we create models via the m2m relation if the remote model + has a OneToOneField. + """ + f = Favorites(name = 'Fred') + f.save() + f.restaurants = [self.r1] + self.assertQuerysetEqual( + f.restaurants.all(), + [''] + ) + + def test_reverse_object_cache(self): + """ + Regression test for #7173 + + Check that the name of the cache for the reverse object is correct. + """ + self.assertEquals(self.p1.restaurant, self.r1) + self.assertEquals(self.p1.bar, self.b1) + + def test_related_object_cache(self): + """ Regression test for #6886 (the related-object cache) """ + + # Look up the objects again so that we get "fresh" objects + p = Place.objects.get(name="Demon Dogs") + r = p.restaurant + + # Accessing the related object again returns the exactly same object + self.assertTrue(p.restaurant is r) + + # But if we kill the cache, we get a new object + del p._restaurant_cache + self.assertFalse(p.restaurant is r) + + # Reassigning the Restaurant object results in an immediate cache update + # We can't use a new Restaurant because that'll violate one-to-one, but + # with a new *instance* the is test below will fail if #6886 regresses. + r2 = Restaurant.objects.get(pk=r.pk) + p.restaurant = r2 + self.assertTrue(p.restaurant is r2) + + # Assigning None succeeds if field is null=True. + ug_bar = UndergroundBar.objects.create(place=p, serves_cocktails=False) + ug_bar.place = None + self.assertTrue(ug_bar.place is None) + + # Assigning None fails: Place.restaurant is null=False + self.assertRaises(ValueError, setattr, p, 'restaurant', None) + + # You also can't assign an object of the wrong type here + self.assertRaises(ValueError, setattr, p, 'restaurant', p) + + # Creation using keyword argument should cache the related object. + p = Place.objects.get(name="Demon Dogs") + r = Restaurant(place=p) + self.assertTrue(r.place is p) + + # Creation using keyword argument and unsaved related instance (#8070). + p = Place() + r = Restaurant(place=p) + self.assertTrue(r.place is p) + + # Creation using attname keyword argument and an id will cause the related + # object to be fetched. + p = Place.objects.get(name="Demon Dogs") + r = Restaurant(place_id=p.id) + self.assertFalse(r.place is p) + self.assertEqual(r.place, p) + + def test_filter_one_to_one_relations(self): + """ + Regression test for #9968 + + filtering reverse one-to-one relations with primary_key=True was + misbehaving. We test both (primary_key=True & False) cases here to + prevent any reappearance of the problem. + """ + t = Target.objects.create() + + self.assertQuerysetEqual( + Target.objects.filter(pointer=None), + [''] + ) + self.assertQuerysetEqual( + Target.objects.exclude(pointer=None), + [] + ) + self.assertQuerysetEqual( + Target.objects.filter(pointer2=None), + [''] + ) + self.assertQuerysetEqual( + Target.objects.exclude(pointer2=None), + [] + ) From a9e0305f2294012ffd6aad76fa7bab4d2c3ed4cd Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 08:28:07 +0000 Subject: [PATCH 219/902] [1.2.X] Migrated null_queries doctests. Thanks to Stephan Jaekel. Backport of r13932 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13940 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/null_queries/models.py | 57 ---------------- tests/regressiontests/null_queries/tests.py | 69 ++++++++++++++++++++ 2 files changed, 69 insertions(+), 57 deletions(-) create mode 100644 tests/regressiontests/null_queries/tests.py diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py index 7cde0aac6122..442535c11c63 100644 --- a/tests/regressiontests/null_queries/models.py +++ b/tests/regressiontests/null_queries/models.py @@ -23,60 +23,3 @@ class OuterB(models.Model): class Inner(models.Model): first = models.ForeignKey(OuterA) second = models.ForeignKey(OuterB, null=True) - -__test__ = {'API_TESTS':""" -# Regression test for the use of None as a query value. None is interpreted as -# an SQL NULL, but only in __exact queries. -# Set up some initial polls and choices ->>> p1 = Poll(question='Why?') ->>> p1.save() ->>> c1 = Choice(poll=p1, choice='Because.') ->>> c1.save() ->>> c2 = Choice(poll=p1, choice='Why Not?') ->>> c2.save() - -# Exact query with value None returns nothing ("is NULL" in sql, but every 'id' -# field has a value). ->>> Choice.objects.filter(choice__exact=None) -[] - -Excluding the previous result returns everything. ->>> Choice.objects.exclude(choice=None).order_by('id') -[, ] - -# Valid query, but fails because foo isn't a keyword ->>> Choice.objects.filter(foo__exact=None) -Traceback (most recent call last): -... -FieldError: Cannot resolve keyword 'foo' into field. Choices are: choice, id, poll - -# Can't use None on anything other than __exact ->>> Choice.objects.filter(id__gt=None) -Traceback (most recent call last): -... -ValueError: Cannot use None as a query value - -# Can't use None on anything other than __exact ->>> Choice.objects.filter(foo__gt=None) -Traceback (most recent call last): -... -ValueError: Cannot use None as a query value - -# Related managers use __exact=None implicitly if the object hasn't been saved. ->>> p2 = Poll(question="How?") ->>> p2.choice_set.all() -[] - -# Querying across reverse relations and then another relation should insert -# outer joins correctly so as not to exclude results. ->>> obj = OuterA.objects.create() ->>> OuterA.objects.filter(inner__second=None) -[] ->>> OuterA.objects.filter(inner__second__data=None) -[] ->>> _ = Inner.objects.create(first=obj) ->>> Inner.objects.filter(first__inner__second=None) -[] - - -"""} diff --git a/tests/regressiontests/null_queries/tests.py b/tests/regressiontests/null_queries/tests.py new file mode 100644 index 000000000000..72dcd5153cf1 --- /dev/null +++ b/tests/regressiontests/null_queries/tests.py @@ -0,0 +1,69 @@ +from django.test import TestCase +from django.core.exceptions import FieldError + +from regressiontests.null_queries.models import * + + +class NullQueriesTests(TestCase): + + def test_none_as_null(self): + """ + Regression test for the use of None as a query value. + + None is interpreted as an SQL NULL, but only in __exact queries. + Set up some initial polls and choices + """ + p1 = Poll(question='Why?') + p1.save() + c1 = Choice(poll=p1, choice='Because.') + c1.save() + c2 = Choice(poll=p1, choice='Why Not?') + c2.save() + + # Exact query with value None returns nothing ("is NULL" in sql, + # but every 'id' field has a value). + self.assertQuerysetEqual(Choice.objects.filter(choice__exact=None), []) + + # Excluding the previous result returns everything. + self.assertQuerysetEqual( + Choice.objects.exclude(choice=None).order_by('id'), + [ + '', + '' + ] + ) + + # Valid query, but fails because foo isn't a keyword + self.assertRaises(FieldError, Choice.objects.filter, foo__exact=None) + + # Can't use None on anything other than __exact + self.assertRaises(ValueError, Choice.objects.filter, id__gt=None) + + # Can't use None on anything other than __exact + self.assertRaises(ValueError, Choice.objects.filter, foo__gt=None) + + # Related managers use __exact=None implicitly if the object hasn't been saved. + p2 = Poll(question="How?") + self.assertEquals(repr(p2.choice_set.all()), '[]') + + def test_reverse_relations(self): + """ + Querying across reverse relations and then another relation should + insert outer joins correctly so as not to exclude results. + """ + obj = OuterA.objects.create() + self.assertQuerysetEqual( + OuterA.objects.filter(inner__second=None), + [''] + ) + self.assertQuerysetEqual( + OuterA.objects.filter(inner__second__data=None), + [''] + ) + + inner_obj = Inner.objects.create(first=obj) + self.assertQuerysetEqual( + Inner.objects.filter(first__inner__second=None), + [''] + ) + From ccd6a6783baef1980cbe2f04a038eaecf23742ef Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 08:28:32 +0000 Subject: [PATCH 220/902] [1.2.X] Migrated null_fk_ordering doctests. Thanks to Stephan Jaekel. Backport of r13933 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13941 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../null_fk_ordering/models.py | 37 ------------------ .../regressiontests/null_fk_ordering/tests.py | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 37 deletions(-) create mode 100644 tests/regressiontests/null_fk_ordering/tests.py diff --git a/tests/regressiontests/null_fk_ordering/models.py b/tests/regressiontests/null_fk_ordering/models.py index dd81706dcd23..d0635e8acc19 100644 --- a/tests/regressiontests/null_fk_ordering/models.py +++ b/tests/regressiontests/null_fk_ordering/models.py @@ -47,40 +47,3 @@ class Meta: def __unicode__(self): return self.comment_text - - -__test__ = {'API_TESTS': """ -# Regression test for #7512 -- ordering across nullable Foreign Keys shouldn't -# exclude results ->>> author_1 = Author.objects.create(name='Tom Jones') ->>> author_2 = Author.objects.create(name='Bob Smith') ->>> article_1 = Article.objects.create(title='No author on this article') ->>> article_2 = Article.objects.create(author=author_1, title='This article written by Tom Jones') ->>> article_3 = Article.objects.create(author=author_2, title='This article written by Bob Smith') - -# We can't compare results directly (since different databases sort NULLs to -# different ends of the ordering), but we can check that all results are -# returned. ->>> len(list(Article.objects.all())) == 3 -True - ->>> s = SystemInfo.objects.create(system_name='System Info') ->>> f = Forum.objects.create(system_info=s, forum_name='First forum') ->>> p = Post.objects.create(forum=f, title='First Post') ->>> c1 = Comment.objects.create(post=p, comment_text='My first comment') ->>> c2 = Comment.objects.create(comment_text='My second comment') ->>> s2 = SystemInfo.objects.create(system_name='More System Info') ->>> f2 = Forum.objects.create(system_info=s2, forum_name='Second forum') ->>> p2 = Post.objects.create(forum=f2, title='Second Post') ->>> c3 = Comment.objects.create(comment_text='Another first comment') ->>> c4 = Comment.objects.create(post=p2, comment_text='Another second comment') - -# We have to test this carefully. Some databases sort NULL values before -# everything else, some sort them afterwards. So we extract the ordered list -# and check the length. Before the fix, this list was too short (some values -# were omitted). ->>> len(list(Comment.objects.all())) == 4 -True - -""" -} diff --git a/tests/regressiontests/null_fk_ordering/tests.py b/tests/regressiontests/null_fk_ordering/tests.py new file mode 100644 index 000000000000..c9ee4f7b1ab9 --- /dev/null +++ b/tests/regressiontests/null_fk_ordering/tests.py @@ -0,0 +1,39 @@ +from django.test import TestCase + +from regressiontests.null_fk_ordering.models import * + +class NullFkOrderingTests(TestCase): + + def test_ordering_across_null_fk(self): + """ + Regression test for #7512 + + ordering across nullable Foreign Keys shouldn't exclude results + """ + author_1 = Author.objects.create(name='Tom Jones') + author_2 = Author.objects.create(name='Bob Smith') + article_1 = Article.objects.create(title='No author on this article') + article_2 = Article.objects.create(author=author_1, title='This article written by Tom Jones') + article_3 = Article.objects.create(author=author_2, title='This article written by Bob Smith') + + # We can't compare results directly (since different databases sort NULLs to + # different ends of the ordering), but we can check that all results are + # returned. + self.assertTrue(len(list(Article.objects.all())) == 3) + + s = SystemInfo.objects.create(system_name='System Info') + f = Forum.objects.create(system_info=s, forum_name='First forum') + p = Post.objects.create(forum=f, title='First Post') + c1 = Comment.objects.create(post=p, comment_text='My first comment') + c2 = Comment.objects.create(comment_text='My second comment') + s2 = SystemInfo.objects.create(system_name='More System Info') + f2 = Forum.objects.create(system_info=s2, forum_name='Second forum') + p2 = Post.objects.create(forum=f2, title='Second Post') + c3 = Comment.objects.create(comment_text='Another first comment') + c4 = Comment.objects.create(post=p2, comment_text='Another second comment') + + # We have to test this carefully. Some databases sort NULL values before + # everything else, some sort them afterwards. So we extract the ordered list + # and check the length. Before the fix, this list was too short (some values + # were omitted). + self.assertTrue(len(list(Comment.objects.all())) == 4) From 5277425ae454da282edfd4322b3a282458c410e2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 08:28:56 +0000 Subject: [PATCH 221/902] [1.2.X] Migrated null_fk doctests. Thanks to Stephan Jaekel. Backport of r13934 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13942 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/null_fk/models.py | 34 -------------------- tests/regressiontests/null_fk/tests.py | 42 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 34 deletions(-) create mode 100644 tests/regressiontests/null_fk/tests.py diff --git a/tests/regressiontests/null_fk/models.py b/tests/regressiontests/null_fk/models.py index 1f191daaad5a..3cce319496db 100644 --- a/tests/regressiontests/null_fk/models.py +++ b/tests/regressiontests/null_fk/models.py @@ -31,37 +31,3 @@ class Meta: def __unicode__(self): return self.comment_text - -__test__ = {'API_TESTS':""" - ->>> d = SystemDetails.objects.create(details='First details') ->>> s = SystemInfo.objects.create(system_name='First forum', system_details=d) ->>> f = Forum.objects.create(system_info=s, forum_name='First forum') ->>> p = Post.objects.create(forum=f, title='First Post') ->>> c1 = Comment.objects.create(post=p, comment_text='My first comment') ->>> c2 = Comment.objects.create(comment_text='My second comment') - -# Starting from comment, make sure that a .select_related(...) with a specified -# set of fields will properly LEFT JOIN multiple levels of NULLs (and the things -# that come after the NULLs, or else data that should exist won't). Regression -# test for #7369. ->>> c = Comment.objects.select_related().get(id=1) ->>> c.post - ->>> c = Comment.objects.select_related().get(id=2) ->>> print c.post -None - ->>> comments = Comment.objects.select_related('post__forum__system_info').all() ->>> [(c.id, c.comment_text, c.post) for c in comments] -[(1, u'My first comment', ), (2, u'My second comment', None)] - -# Regression test for #7530, #7716. ->>> Comment.objects.select_related('post').filter(post__isnull=True)[0].post is None -True - ->>> comments = Comment.objects.select_related('post__forum__system_info__system_details') ->>> [(c.id, c.comment_text, c.post) for c in comments] -[(1, u'My first comment', ), (2, u'My second comment', None)] - -"""} diff --git a/tests/regressiontests/null_fk/tests.py b/tests/regressiontests/null_fk/tests.py new file mode 100644 index 000000000000..449f3438a4f1 --- /dev/null +++ b/tests/regressiontests/null_fk/tests.py @@ -0,0 +1,42 @@ +from django.test import TestCase + +from regressiontests.null_fk.models import * + +class NullFkTests(TestCase): + + def test_null_fk(self): + d = SystemDetails.objects.create(details='First details') + s = SystemInfo.objects.create(system_name='First forum', system_details=d) + f = Forum.objects.create(system_info=s, forum_name='First forum') + p = Post.objects.create(forum=f, title='First Post') + c1 = Comment.objects.create(post=p, comment_text='My first comment') + c2 = Comment.objects.create(comment_text='My second comment') + + # Starting from comment, make sure that a .select_related(...) with a specified + # set of fields will properly LEFT JOIN multiple levels of NULLs (and the things + # that come after the NULLs, or else data that should exist won't). Regression + # test for #7369. + c = Comment.objects.select_related().get(id=1) + self.assertEquals(c.post, p) + self.assertEquals(Comment.objects.select_related().get(id=2).post, None) + + self.assertQuerysetEqual( + Comment.objects.select_related('post__forum__system_info').all(), + [ + (1, u'My first comment', ''), + (2, u'My second comment', 'None') + ], + transform = lambda c: (c.id, c.comment_text, repr(c.post)) + ) + + # Regression test for #7530, #7716. + self.assertTrue(Comment.objects.select_related('post').filter(post__isnull=True)[0].post is None) + + self.assertQuerysetEqual( + Comment.objects.select_related('post__forum__system_info__system_details'), + [ + (1, u'My first comment', ''), + (2, u'My second comment', 'None') + ], + transform = lambda c: (c.id, c.comment_text, repr(c.post)) + ) From efa27fc86cff473c5ca5c0d48bcd264c99d9fd47 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 08:29:30 +0000 Subject: [PATCH 222/902] [1.2.X] A few test optimizations; using native unittest where no Django-specific TestCase features are required. Backport of r13935 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13943 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/utils/checksums.py | 4 ++-- tests/regressiontests/utils/datastructures.py | 5 +++-- tests/regressiontests/utils/dateformat.py | 7 ++++--- tests/regressiontests/utils/datetime_safe.py | 4 ++-- tests/regressiontests/utils/feedgenerator.py | 4 ++-- tests/regressiontests/utils/functional.py | 4 ++-- tests/regressiontests/utils/html.py | 4 ++-- tests/regressiontests/utils/module_loading.py | 6 +++--- tests/regressiontests/utils/simplelazyobject.py | 6 +++--- tests/regressiontests/utils/termcolors.py | 4 ++-- tests/regressiontests/utils/text.py | 4 ++-- tests/regressiontests/utils/timesince.py | 5 ++--- tests/regressiontests/utils/tzinfo.py | 4 ++-- 13 files changed, 31 insertions(+), 30 deletions(-) diff --git a/tests/regressiontests/utils/checksums.py b/tests/regressiontests/utils/checksums.py index bb73576a8043..cee6dca2a84e 100644 --- a/tests/regressiontests/utils/checksums.py +++ b/tests/regressiontests/utils/checksums.py @@ -1,8 +1,8 @@ -from django.test import TestCase +import unittest from django.utils import checksums -class TestUtilsChecksums(TestCase): +class TestUtilsChecksums(unittest.TestCase): def check_output(self, function, value, output=None): """ diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index a353a324042b..e684694d7c6c 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -1,7 +1,8 @@ -from django.test import TestCase +import unittest + from django.utils.datastructures import SortedDict -class DatastructuresTests(TestCase): +class DatastructuresTests(unittest.TestCase): def setUp(self): self.d1 = SortedDict() self.d1[7] = 'seven' diff --git a/tests/regressiontests/utils/dateformat.py b/tests/regressiontests/utils/dateformat.py index b80010f8d814..d83e3e191c42 100644 --- a/tests/regressiontests/utils/dateformat.py +++ b/tests/regressiontests/utils/dateformat.py @@ -1,10 +1,11 @@ -import os -from unittest import TestCase from datetime import datetime, date +import os +import unittest + from django.utils.dateformat import format from django.utils.tzinfo import FixedOffset, LocalTimezone -class DateFormatTests(TestCase): +class DateFormatTests(unittest.TestCase): def test_date(self): d = date(2009, 5, 16) self.assertEquals(date.fromtimestamp(int(format(d, 'U'))), d) diff --git a/tests/regressiontests/utils/datetime_safe.py b/tests/regressiontests/utils/datetime_safe.py index 62b9c7481fe1..458a6b79f8af 100644 --- a/tests/regressiontests/utils/datetime_safe.py +++ b/tests/regressiontests/utils/datetime_safe.py @@ -1,9 +1,9 @@ -from django.test import TestCase +import unittest from datetime import date as original_date, datetime as original_datetime from django.utils.datetime_safe import date, datetime -class DatetimeTests(TestCase): +class DatetimeTests(unittest.TestCase): def setUp(self): self.just_safe = (1900, 1, 1) diff --git a/tests/regressiontests/utils/feedgenerator.py b/tests/regressiontests/utils/feedgenerator.py index 5a10de2d1bab..9085d41c0680 100644 --- a/tests/regressiontests/utils/feedgenerator.py +++ b/tests/regressiontests/utils/feedgenerator.py @@ -1,9 +1,9 @@ import datetime -from unittest import TestCase +import unittest from django.utils import feedgenerator, tzinfo -class FeedgeneratorTest(TestCase): +class FeedgeneratorTest(unittest.TestCase): """ Tests for the low-level syndication feed framework. """ diff --git a/tests/regressiontests/utils/functional.py b/tests/regressiontests/utils/functional.py index 72610154d87f..206a5838b595 100644 --- a/tests/regressiontests/utils/functional.py +++ b/tests/regressiontests/utils/functional.py @@ -1,9 +1,9 @@ -from unittest import TestCase +import unittest from django.utils.functional import lazy -class FunctionalTestCase(TestCase): +class FunctionalTestCase(unittest.TestCase): def test_lazy(self): t = lazy(lambda: tuple(range(3)), list, tuple) for a, b in zip(t(), range(3)): diff --git a/tests/regressiontests/utils/html.py b/tests/regressiontests/utils/html.py index 61ca6f4c53af..a9b0d337f2aa 100644 --- a/tests/regressiontests/utils/html.py +++ b/tests/regressiontests/utils/html.py @@ -1,8 +1,8 @@ -from django.test import TestCase +import unittest from django.utils import html -class TestUtilsHtml(TestCase): +class TestUtilsHtml(unittest.TestCase): def check_output(self, function, value, output=None): """ diff --git a/tests/regressiontests/utils/module_loading.py b/tests/regressiontests/utils/module_loading.py index b7df6d68b1f9..4422f8f5d85d 100644 --- a/tests/regressiontests/utils/module_loading.py +++ b/tests/regressiontests/utils/module_loading.py @@ -1,12 +1,12 @@ import os import sys -from unittest import TestCase +import unittest from zipimport import zipimporter from django.utils.importlib import import_module from django.utils.module_loading import module_has_submodule -class DefaultLoader(TestCase): +class DefaultLoader(unittest.TestCase): def test_loader(self): "Normal module existence can be tested" test_module = import_module('regressiontests.utils.test_module') @@ -24,7 +24,7 @@ def test_loader(self): self.assertFalse(module_has_submodule(test_module, 'no_such_module')) self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.no_such_module') -class EggLoader(TestCase): +class EggLoader(unittest.TestCase): def setUp(self): self.old_path = sys.path self.egg_dir = '%s/eggs' % os.path.dirname(__file__) diff --git a/tests/regressiontests/utils/simplelazyobject.py b/tests/regressiontests/utils/simplelazyobject.py index 1df55aa88549..4a930dd69b1c 100644 --- a/tests/regressiontests/utils/simplelazyobject.py +++ b/tests/regressiontests/utils/simplelazyobject.py @@ -1,5 +1,6 @@ -from django.test import TestCase +import unittest +import django.utils.copycompat as copy from django.utils.functional import SimpleLazyObject class _ComplexObject(object): @@ -23,7 +24,7 @@ def __repr__(self): complex_object = lambda: _ComplexObject("joe") -class TestUtilsSimpleLazyObject(TestCase): +class TestUtilsSimpleLazyObject(unittest.TestCase): """ Tests for SimpleLazyObject """ @@ -59,7 +60,6 @@ def test_class(self): self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__) def test_deepcopy(self): - import django.utils.copycompat as copy # Check that we *can* do deep copy, and that it returns the right # objects. diff --git a/tests/regressiontests/utils/termcolors.py b/tests/regressiontests/utils/termcolors.py index 7c8fe0d63bbf..ccae32cd1673 100644 --- a/tests/regressiontests/utils/termcolors.py +++ b/tests/regressiontests/utils/termcolors.py @@ -1,8 +1,8 @@ -from unittest import TestCase +import unittest from django.utils.termcolors import parse_color_setting, PALETTES, DEFAULT_PALETTE, LIGHT_PALETTE, DARK_PALETTE, NOCOLOR_PALETTE -class TermColorTests(TestCase): +class TermColorTests(unittest.TestCase): def test_empty_string(self): self.assertEquals(parse_color_setting(''), PALETTES[DEFAULT_PALETTE]) diff --git a/tests/regressiontests/utils/text.py b/tests/regressiontests/utils/text.py index 7cf52ca92c35..e7d2d38d3b21 100644 --- a/tests/regressiontests/utils/text.py +++ b/tests/regressiontests/utils/text.py @@ -1,8 +1,8 @@ -from django.test import TestCase +import unittest from django.utils import text -class TestUtilsText(TestCase): +class TestUtilsText(unittest.TestCase): def test_truncate_words(self): self.assertEqual(u'The quick brown fox jumped over the lazy dog.', text.truncate_words(u'The quick brown fox jumped over the lazy dog.', 10)) diff --git a/tests/regressiontests/utils/timesince.py b/tests/regressiontests/utils/timesince.py index d7511217303c..774aa3f7cd65 100644 --- a/tests/regressiontests/utils/timesince.py +++ b/tests/regressiontests/utils/timesince.py @@ -1,11 +1,10 @@ -from django.test import TestCase - import datetime +import unittest from django.utils.timesince import timesince, timeuntil from django.utils.tzinfo import LocalTimezone, FixedOffset -class TimesinceTests(TestCase): +class TimesinceTests(unittest.TestCase): def setUp(self): self.t = datetime.datetime(2007, 8, 14, 13, 46, 0) diff --git a/tests/regressiontests/utils/tzinfo.py b/tests/regressiontests/utils/tzinfo.py index 60fc0b942e31..edbb9a70da7b 100644 --- a/tests/regressiontests/utils/tzinfo.py +++ b/tests/regressiontests/utils/tzinfo.py @@ -1,8 +1,8 @@ -from django.test import TestCase +import unittest from django.utils.tzinfo import FixedOffset -class TzinfoTests(TestCase): +class TzinfoTests(unittest.TestCase): def test_fixedoffset(self): self.assertEquals(repr(FixedOffset(0)), '+0000') From 3b255e33e846459e217c1d9a02ef4b385a34c0a1 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 08:29:58 +0000 Subject: [PATCH 223/902] [1.2.X] Migrated regressiontest/datastructures doctest, and moved it into the existing utils datastructures package. Thanks to Stephan Jaekel. Backport of r13937 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13944 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../datastructures/__init__.py | 0 .../regressiontests/datastructures/models.py | 0 tests/regressiontests/datastructures/tests.py | 173 ---------------- tests/regressiontests/utils/datastructures.py | 192 +++++++++++++++++- 4 files changed, 190 insertions(+), 175 deletions(-) delete mode 100644 tests/regressiontests/datastructures/__init__.py delete mode 100644 tests/regressiontests/datastructures/models.py delete mode 100644 tests/regressiontests/datastructures/tests.py diff --git a/tests/regressiontests/datastructures/__init__.py b/tests/regressiontests/datastructures/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tests/regressiontests/datastructures/models.py b/tests/regressiontests/datastructures/models.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py deleted file mode 100644 index a858e2469fe5..000000000000 --- a/tests/regressiontests/datastructures/tests.py +++ /dev/null @@ -1,173 +0,0 @@ -""" -# Tests for stuff in django.utils.datastructures. - ->>> import pickle ->>> from django.utils.datastructures import * - -### MergeDict ################################################################# - ->>> d1 = {'chris':'cool','camri':'cute','cotton':'adorable','tulip':'snuggable', 'twoofme':'firstone'} ->>> d2 = {'chris2':'cool2','camri2':'cute2','cotton2':'adorable2','tulip2':'snuggable2'} ->>> d3 = {'chris3':'cool3','camri3':'cute3','cotton3':'adorable3','tulip3':'snuggable3'} ->>> d4 = {'twoofme':'secondone'} ->>> md = MergeDict( d1,d2,d3 ) ->>> md['chris'] -'cool' ->>> md['camri'] -'cute' ->>> md['twoofme'] -'firstone' ->>> md2 = md.copy() ->>> md2['chris'] -'cool' - -MergeDict can merge MultiValueDicts ->>> multi1 = MultiValueDict({'key1': ['value1'], 'key2': ['value2', 'value3']}) ->>> multi2 = MultiValueDict({'key2': ['value4'], 'key4': ['value5', 'value6']}) ->>> mm = MergeDict(multi1, multi2) - -# Although 'key2' appears in both dictionaries, only the first value is used. ->>> mm.getlist('key2') -['value2', 'value3'] ->>> mm.getlist('key4') -['value5', 'value6'] ->>> mm.getlist('undefined') -[] - ->>> sorted(mm.keys()) -['key1', 'key2', 'key4'] ->>> len(mm.values()) -3 ->>> "value1" in mm.values() -True ->>> sorted(mm.items(), key=lambda k: k[0]) -[('key1', 'value1'), ('key2', 'value3'), ('key4', 'value6')] ->>> [(k,mm.getlist(k)) for k in sorted(mm)] -[('key1', ['value1']), ('key2', ['value2', 'value3']), ('key4', ['value5', 'value6'])] - -### MultiValueDict ########################################################## - ->>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']}) ->>> d['name'] -'Simon' ->>> d.get('name') -'Simon' ->>> d.getlist('name') -['Adrian', 'Simon'] ->>> list(d.iteritems()) -[('position', 'Developer'), ('name', 'Simon')] ->>> list(d.iterlists()) -[('position', ['Developer']), ('name', ['Adrian', 'Simon'])] ->>> d['lastname'] -Traceback (most recent call last): -... -MultiValueDictKeyError: "Key 'lastname' not found in " ->>> d.get('lastname') - ->>> d.get('lastname', 'nonexistent') -'nonexistent' ->>> d.getlist('lastname') -[] ->>> d.setlist('lastname', ['Holovaty', 'Willison']) ->>> d.getlist('lastname') -['Holovaty', 'Willison'] ->>> d.values() -['Developer', 'Simon', 'Willison'] ->>> list(d.itervalues()) -['Developer', 'Simon', 'Willison'] - -### SortedDict ################################################################# - ->>> d = SortedDict() ->>> d['one'] = 'one' ->>> d['two'] = 'two' ->>> d['three'] = 'three' ->>> d['one'] -'one' ->>> d['two'] -'two' ->>> d['three'] -'three' ->>> d.keys() -['one', 'two', 'three'] ->>> d.values() -['one', 'two', 'three'] ->>> d['one'] = 'not one' ->>> d['one'] -'not one' ->>> d.keys() == d.copy().keys() -True ->>> d2 = d.copy() ->>> d2['four'] = 'four' ->>> print repr(d) -{'one': 'not one', 'two': 'two', 'three': 'three'} ->>> d.pop('one', 'missing') -'not one' ->>> d.pop('one', 'missing') -'missing' - ->>> SortedDict((i, i) for i in xrange(3)) -{0: 0, 1: 1, 2: 2} - -We don't know which item will be popped in popitem(), so we'll just check that -the number of keys has decreased. ->>> l = len(d) ->>> _ = d.popitem() ->>> l - len(d) -1 - -Init from sequence of tuples ->>> d = SortedDict(( -... (1, "one"), -... (0, "zero"), -... (2, "two"))) ->>> print repr(d) -{1: 'one', 0: 'zero', 2: 'two'} - ->>> pickle.loads(pickle.dumps(d, 2)) -{1: 'one', 0: 'zero', 2: 'two'} - ->>> d.clear() ->>> d -{} ->>> d.keyOrder -[] - -### DotExpandedDict ########################################################## - ->>> d = DotExpandedDict({'person.1.firstname': ['Simon'], 'person.1.lastname': ['Willison'], 'person.2.firstname': ['Adrian'], 'person.2.lastname': ['Holovaty']}) ->>> d['person']['1']['lastname'] -['Willison'] ->>> d['person']['2']['lastname'] -['Holovaty'] ->>> d['person']['2']['firstname'] -['Adrian'] - -### ImmutableList ############################################################ ->>> d = ImmutableList(range(10)) ->>> d.sort() -Traceback (most recent call last): - File "", line 1, in - File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain - raise AttributeError, self.warning -AttributeError: ImmutableList object is immutable. ->>> repr(d) -'(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)' ->>> d = ImmutableList(range(10), warning="Object is immutable!") ->>> d[1] -1 ->>> d[1] = 'test' -Traceback (most recent call last): - File "", line 1, in - File "/var/lib/python-support/python2.5/django/utils/datastructures.py", line 359, in complain - raise AttributeError, self.warning -AttributeError: Object is immutable! - -### DictWrapper ############################################################# - ->>> f = lambda x: "*%s" % x ->>> d = DictWrapper({'a': 'a'}, f, 'xx_') ->>> "Normal: %(a)s. Modified: %(xx_a)s" % d -'Normal: a. Modified: *a' - -""" diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py index e684694d7c6c..a41281cd3746 100644 --- a/tests/regressiontests/utils/datastructures.py +++ b/tests/regressiontests/utils/datastructures.py @@ -1,8 +1,23 @@ +""" +Tests for stuff in django.utils.datastructures. +""" +import pickle import unittest -from django.utils.datastructures import SortedDict +from django.utils.datastructures import * -class DatastructuresTests(unittest.TestCase): + +class DatastructuresTestCase(unittest.TestCase): + def assertRaisesErrorWithMessage(self, error, message, callable, + *args, **kwargs): + self.assertRaises(error, callable, *args, **kwargs) + try: + callable(*args, **kwargs) + except error, e: + self.assertEqual(message, str(e)) + + +class SortedDictTests(DatastructuresTestCase): def setUp(self): self.d1 = SortedDict() self.d1[7] = 'seven' @@ -66,3 +81,176 @@ def test_init_keys(self): # Here the order of SortedDict values *is* what we are testing self.assertEquals(d.values(), ['second-two', 'one']) + + def test_overwrite(self): + self.d1[1] = 'not one' + self.assertEqual(self.d1[1], 'not one') + self.assertEqual(self.d1.keys(), self.d1.copy().keys()) + + def test_append(self): + self.d1[13] = 'thirteen' + self.assertEquals( + repr(self.d1), + "{7: 'seven', 1: 'one', 9: 'nine', 13: 'thirteen'}" + ) + + def test_pop(self): + self.assertEquals(self.d1.pop(1, 'missing'), 'one') + self.assertEquals(self.d1.pop(1, 'missing'), 'missing') + + # We don't know which item will be popped in popitem(), so we'll + # just check that the number of keys has decreased. + l = len(self.d1) + self.d1.popitem() + self.assertEquals(l - len(self.d1), 1) + + def test_dict_equality(self): + d = SortedDict((i, i) for i in xrange(3)) + self.assertEquals(d, {0: 0, 1: 1, 2: 2}) + + def test_tuple_init(self): + d = SortedDict(((1, "one"), (0, "zero"), (2, "two"))) + self.assertEquals(repr(d), "{1: 'one', 0: 'zero', 2: 'two'}") + + def test_pickle(self): + self.assertEquals( + pickle.loads(pickle.dumps(self.d1, 2)), + {7: 'seven', 1: 'one', 9: 'nine'} + ) + + def test_clear(self): + self.d1.clear() + self.assertEquals(self.d1, {}) + self.assertEquals(self.d1.keyOrder, []) + +class MergeDictTests(DatastructuresTestCase): + + def test_simple_mergedict(self): + d1 = {'chris':'cool', 'camri':'cute', 'cotton':'adorable', + 'tulip':'snuggable', 'twoofme':'firstone'} + + d2 = {'chris2':'cool2', 'camri2':'cute2', 'cotton2':'adorable2', + 'tulip2':'snuggable2'} + + d3 = {'chris3':'cool3', 'camri3':'cute3', 'cotton3':'adorable3', + 'tulip3':'snuggable3'} + + d4 = {'twoofme': 'secondone'} + + md = MergeDict(d1, d2, d3) + + self.assertEquals(md['chris'], 'cool') + self.assertEquals(md['camri'], 'cute') + self.assertEquals(md['twoofme'], 'firstone') + + md2 = md.copy() + self.assertEquals(md2['chris'], 'cool') + + def test_mergedict_merges_multivaluedict(self): + """ MergeDict can merge MultiValueDicts """ + + multi1 = MultiValueDict({'key1': ['value1'], + 'key2': ['value2', 'value3']}) + + multi2 = MultiValueDict({'key2': ['value4'], + 'key4': ['value5', 'value6']}) + + mm = MergeDict(multi1, multi2) + + # Although 'key2' appears in both dictionaries, + # only the first value is used. + self.assertEquals(mm.getlist('key2'), ['value2', 'value3']) + self.assertEquals(mm.getlist('key4'), ['value5', 'value6']) + self.assertEquals(mm.getlist('undefined'), []) + + self.assertEquals(sorted(mm.keys()), ['key1', 'key2', 'key4']) + self.assertEquals(len(mm.values()), 3) + + self.assertTrue('value1' in mm.values()) + + self.assertEquals(sorted(mm.items(), key=lambda k: k[0]), + [('key1', 'value1'), ('key2', 'value3'), + ('key4', 'value6')]) + + self.assertEquals([(k,mm.getlist(k)) for k in sorted(mm)], + [('key1', ['value1']), + ('key2', ['value2', 'value3']), + ('key4', ['value5', 'value6'])]) + +class MultiValueDictTests(DatastructuresTestCase): + + def test_multivaluedict(self): + d = MultiValueDict({'name': ['Adrian', 'Simon'], + 'position': ['Developer']}) + + self.assertEquals(d['name'], 'Simon') + self.assertEquals(d.get('name'), 'Simon') + self.assertEquals(d.getlist('name'), ['Adrian', 'Simon']) + self.assertEquals(list(d.iteritems()), + [('position', 'Developer'), ('name', 'Simon')]) + + self.assertEquals(list(d.iterlists()), + [('position', ['Developer']), + ('name', ['Adrian', 'Simon'])]) + + # MultiValueDictKeyError: "Key 'lastname' not found in + # " + self.assertRaisesErrorWithMessage(MultiValueDictKeyError, + '"Key \'lastname\' not found in "', + d.__getitem__, 'lastname') + + self.assertEquals(d.get('lastname'), None) + self.assertEquals(d.get('lastname', 'nonexistent'), 'nonexistent') + self.assertEquals(d.getlist('lastname'), []) + + d.setlist('lastname', ['Holovaty', 'Willison']) + self.assertEquals(d.getlist('lastname'), ['Holovaty', 'Willison']) + self.assertEquals(d.values(), ['Developer', 'Simon', 'Willison']) + self.assertEquals(list(d.itervalues()), + ['Developer', 'Simon', 'Willison']) + + +class DotExpandedDictTests(DatastructuresTestCase): + + def test_dotexpandeddict(self): + + d = DotExpandedDict({'person.1.firstname': ['Simon'], + 'person.1.lastname': ['Willison'], + 'person.2.firstname': ['Adrian'], + 'person.2.lastname': ['Holovaty']}) + + self.assertEquals(d['person']['1']['lastname'], ['Willison']) + self.assertEquals(d['person']['2']['lastname'], ['Holovaty']) + self.assertEquals(d['person']['2']['firstname'], ['Adrian']) + + +class ImmutableListTests(DatastructuresTestCase): + + def test_sort(self): + d = ImmutableList(range(10)) + + # AttributeError: ImmutableList object is immutable. + self.assertRaisesErrorWithMessage(AttributeError, + 'ImmutableList object is immutable.', d.sort) + + self.assertEquals(repr(d), '(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)') + + def test_custom_warning(self): + d = ImmutableList(range(10), warning="Object is immutable!") + + self.assertEquals(d[1], 1) + + # AttributeError: Object is immutable! + self.assertRaisesErrorWithMessage(AttributeError, + 'Object is immutable!', d.__setitem__, 1, 'test') + + +class DictWrapperTests(DatastructuresTestCase): + + def test_dictwrapper(self): + f = lambda x: "*%s" % x + d = DictWrapper({'a': 'a'}, f, 'xx_') + self.assertEquals("Normal: %(a)s. Modified: %(xx_a)s" % d, + 'Normal: a. Modified: *a') From a36a8bbb39615cde73b5fbeec2621360f69869c7 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 08:30:22 +0000 Subject: [PATCH 224/902] [1.2.X] Migrated defaultfilters doctests. Thanks to Stephan Jaekel Backport of r13937 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13945 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/defaultfilters/tests.py | 1073 ++++++++--------- 1 file changed, 481 insertions(+), 592 deletions(-) diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index ff49b65d6861..0866bfcb0ef1 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -1,596 +1,485 @@ # -*- coding: utf-8 -*- - -r""" ->>> floatformat(7.7) -u'7.7' ->>> floatformat(7.0) -u'7' ->>> floatformat(0.7) -u'0.7' ->>> floatformat(0.07) -u'0.1' ->>> floatformat(0.007) -u'0.0' ->>> floatformat(0.0) -u'0' ->>> floatformat(7.7, 3) -u'7.700' ->>> floatformat(6.000000, 3) -u'6.000' ->>> floatformat(6.200000, 3) -u'6.200' ->>> floatformat(6.200000, -3) -u'6.200' ->>> floatformat(13.1031, -3) -u'13.103' ->>> floatformat(11.1197, -2) -u'11.12' ->>> floatformat(11.0000, -2) -u'11' ->>> floatformat(11.000001, -2) -u'11.00' ->>> floatformat(8.2798, 3) -u'8.280' ->>> floatformat(u'foo') -u'' ->>> floatformat(13.1031, u'bar') -u'13.1031' ->>> floatformat(18.125, 2) -u'18.13' ->>> floatformat(u'foo', u'bar') -u'' ->>> floatformat(u'¿Cómo esta usted?') -u'' ->>> floatformat(None) -u'' ->>> pos_inf = float(1e30000) ->>> floatformat(pos_inf) == unicode(pos_inf) -True ->>> neg_inf = float(-1e30000) ->>> floatformat(neg_inf) == unicode(neg_inf) -True ->>> nan = pos_inf / pos_inf ->>> floatformat(nan) == unicode(nan) -True - ->>> class FloatWrapper(object): -... def __init__(self, value): -... self.value = value -... def __float__(self): -... return self.value - ->>> floatformat(FloatWrapper(11.000001), -2) -u'11.00' - ->>> addslashes(u'"double quotes" and \'single quotes\'') -u'\\"double quotes\\" and \\\'single quotes\\\'' - ->>> addslashes(ur'\ : backslashes, too') -u'\\\\ : backslashes, too' - ->>> capfirst(u'hello world') -u'Hello world' - ->>> escapejs(u'"double quotes" and \'single quotes\'') -u'\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027' - ->>> escapejs(ur'\ : backslashes, too') -u'\\u005C : backslashes, too' - ->>> escapejs(u'and lots of whitespace: \r\n\t\v\f\b') -u'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008' - ->>> escapejs(ur'') -u'\\u003Cscript\\u003Eand this\\u003C/script\\u003E' - ->>> escapejs(u'paragraph separator:\u2029and line separator:\u2028') -u'paragraph separator:\\u2029and line separator:\\u2028' - ->>> fix_ampersands(u'Jack & Jill & Jeroboam') -u'Jack & Jill & Jeroboam' - ->>> linenumbers(u'line 1\nline 2') -u'1. line 1\n2. line 2' - ->>> linenumbers(u'\n'.join([u'x'] * 10)) -u'01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. x\n08. x\n09. x\n10. x' - ->>> lower('TEST') -u'test' - ->>> lower(u'\xcb') # uppercase E umlaut -u'\xeb' - ->>> make_list('abc') -[u'a', u'b', u'c'] - ->>> make_list(1234) -[u'1', u'2', u'3', u'4'] - ->>> slugify(' Jack & Jill like numbers 1,2,3 and 4 and silly characters ?%.$!/') -u'jack-jill-like-numbers-123-and-4-and-silly-characters' - ->>> slugify(u"Un \xe9l\xe9phant \xe0 l'or\xe9e du bois") -u'un-elephant-a-loree-du-bois' - ->>> stringformat(1, u'03d') -u'001' - ->>> stringformat(1, u'z') -u'' - ->>> title('a nice title, isn\'t it?') -u"A Nice Title, Isn't It?" - ->>> title(u'discoth\xe8que') -u'Discoth\xe8que' - ->>> truncatewords(u'A sentence with a few words in it', 1) -u'A ...' - ->>> truncatewords(u'A sentence with a few words in it', 5) -u'A sentence with a few ...' - ->>> truncatewords(u'A sentence with a few words in it', 100) -u'A sentence with a few words in it' - ->>> truncatewords(u'A sentence with a few words in it', 'not a number') -u'A sentence with a few words in it' - ->>> truncatewords_html(u'

        one two - three
        four
        five

        ', 0) -u'' - ->>> truncatewords_html(u'

        one two - three
        four
        five

        ', 2) -u'

        one two ...

        ' - ->>> truncatewords_html(u'

        one two - three
        four
        five

        ', 4) -u'

        one two - three
        four ...

        ' - ->>> truncatewords_html(u'

        one two - three
        four
        five

        ', 5) -u'

        one two - three
        four
        five

        ' - ->>> truncatewords_html(u'

        one two - three
        four
        five

        ', 100) -u'

        one two - three
        four
        five

        ' - ->>> truncatewords_html(u'\xc5ngstr\xf6m was here', 1) -u'\xc5ngstr\xf6m ...' - ->>> upper(u'Mixed case input') -u'MIXED CASE INPUT' - ->>> upper(u'\xeb') # lowercase e umlaut -u'\xcb' - - ->>> urlencode(u'fran\xe7ois & jill') -u'fran%C3%A7ois%20%26%20jill' ->>> urlencode(1) -u'1' ->>> iriencode(u'S\xf8r-Tr\xf8ndelag') -u'S%C3%B8r-Tr%C3%B8ndelag' ->>> iriencode(urlencode(u'fran\xe7ois & jill')) -u'fran%C3%A7ois%20%26%20jill' - ->>> urlizetrunc(u'http://short.com/', 20) -u'http://short.com/' - ->>> urlizetrunc(u'http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20) -u'http://www.google...' - ->>> urlizetrunc('http://www.google.co.uk/search?hl=en&q=some+long+url&btnG=Search&meta=', 20) -u'http://www.google...' - -# Check truncating of URIs which are the exact length ->>> uri = 'http://31characteruri.com/test/' ->>> len(uri) -31 ->>> urlizetrunc(uri, 31) -u'http://31characteruri.com/test/' ->>> urlizetrunc(uri, 30) -u'http://31characteruri.com/t...' ->>> urlizetrunc(uri, 2) -u'...' - -# Check normal urlize ->>> urlize('http://google.com') -u'http://google.com' - ->>> urlize('http://google.com/') -u'http://google.com/' - ->>> urlize('www.google.com') -u'www.google.com' - ->>> urlize('djangoproject.org') -u'djangoproject.org' - ->>> urlize('info@djangoproject.org') -u'info@djangoproject.org' - -# Check urlize with https addresses ->>> urlize('https://google.com') -u'https://google.com' - - ->>> wordcount('') -0 - ->>> wordcount(u'oneword') -1 - ->>> wordcount(u'lots of words') -3 - ->>> wordwrap(u'this is a long paragraph of text that really needs to be wrapped I\'m afraid', 14) -u"this is a long\nparagraph of\ntext that\nreally needs\nto be wrapped\nI'm afraid" - ->>> wordwrap(u'this is a short paragraph of text.\n But this line should be indented',14) -u'this is a\nshort\nparagraph of\ntext.\n But this\nline should be\nindented' - ->>> wordwrap(u'this is a short paragraph of text.\n But this line should be indented',15) -u'this is a short\nparagraph of\ntext.\n But this line\nshould be\nindented' - ->>> ljust(u'test', 10) -u'test ' - ->>> ljust(u'test', 3) -u'test' - ->>> rjust(u'test', 10) -u' test' - ->>> rjust(u'test', 3) -u'test' - ->>> center(u'test', 6) -u' test ' - ->>> cut(u'a string to be mangled', 'a') -u' string to be mngled' - ->>> cut(u'a string to be mangled', 'ng') -u'a stri to be maled' - ->>> cut(u'a string to be mangled', 'strings') -u'a string to be mangled' - ->>> force_escape(u' here') -u'<some html & special characters > here' - ->>> force_escape(u' here ĐÅ€£') -u'<some html & special characters > here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3' - ->>> linebreaks(u'line 1') -u'

        line 1

        ' - ->>> linebreaks(u'line 1\nline 2') -u'

        line 1
        line 2

        ' - ->>> removetags(u'some html with disallowed tags', 'script img') -u'some html with alert("You smell") disallowed tags' - ->>> striptags(u'some html with disallowed tags') -u'some html with alert("You smell") disallowed tags' - ->>> sorted_dicts = dictsort([{'age': 23, 'name': 'Barbara-Ann'}, -... {'age': 63, 'name': 'Ra Ra Rasputin'}, -... {'name': 'Jonny B Goode', 'age': 18}], 'age') ->>> [sorted(dict.items()) for dict in sorted_dicts] -[[('age', 18), ('name', 'Jonny B Goode')], [('age', 23), ('name', 'Barbara-Ann')], [('age', 63), ('name', 'Ra Ra Rasputin')]] - ->>> sorted_dicts = dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'}, -... {'age': 63, 'name': 'Ra Ra Rasputin'}, -... {'name': 'Jonny B Goode', 'age': 18}], 'age') ->>> [sorted(dict.items()) for dict in sorted_dicts] -[[('age', 63), ('name', 'Ra Ra Rasputin')], [('age', 23), ('name', 'Barbara-Ann')], [('age', 18), ('name', 'Jonny B Goode')]] - ->>> first([0,1,2]) -0 - ->>> first(u'') -u'' - ->>> first(u'test') -u't' - ->>> join([0,1,2], u'glue') -u'0glue1glue2' - ->>> length(u'1234') -4 - ->>> length([1,2,3,4]) -4 - ->>> length_is([], 0) -True - ->>> length_is([], 1) -False - ->>> length_is('a', 1) -True - ->>> length_is(u'a', 10) -False - ->>> slice_(u'abcdefg', u'0') -u'' - ->>> slice_(u'abcdefg', u'1') -u'a' - ->>> slice_(u'abcdefg', u'-1') -u'abcdef' - ->>> slice_(u'abcdefg', u'1:2') -u'b' - ->>> slice_(u'abcdefg', u'1:3') -u'bc' - ->>> slice_(u'abcdefg', u'0::2') -u'aceg' - ->>> unordered_list([u'item 1', u'item 2']) -u'\t
      • item 1
      • \n\t
      • item 2
      • ' - ->>> unordered_list([u'item 1', [u'item 1.1']]) -u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t
        \n\t
      • ' - ->>> unordered_list([u'item 1', [u'item 1.1', u'item1.2'], u'item 2']) -u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t\t
        • item1.2
        • \n\t
        \n\t
      • \n\t
      • item 2
      • ' - ->>> unordered_list([u'item 1', [u'item 1.1', [u'item 1.1.1', [u'item 1.1.1.1']]]]) -u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1\n\t\t
            \n\t\t\t
          • item 1.1.1\n\t\t\t
              \n\t\t\t\t
            • item 1.1.1.1
            • \n\t\t\t
            \n\t\t\t
          • \n\t\t
          \n\t\t
        • \n\t
        \n\t
      • ' - ->>> unordered_list(['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]) -u'\t
      • States\n\t
          \n\t\t
        • Kansas\n\t\t
            \n\t\t\t
          • Lawrence
          • \n\t\t\t
          • Topeka
          • \n\t\t
          \n\t\t
        • \n\t\t
        • Illinois
        • \n\t
        \n\t
      • ' - ->>> class ULItem(object): -... def __init__(self, title): -... self.title = title -... def __unicode__(self): -... return u'ulitem-%s' % str(self.title) - ->>> a = ULItem('a') ->>> b = ULItem('b') ->>> unordered_list([a,b]) -u'\t
      • ulitem-a
      • \n\t
      • ulitem-b
      • ' - -# Old format for unordered lists should still work ->>> unordered_list([u'item 1', []]) -u'\t
      • item 1
      • ' - ->>> unordered_list([u'item 1', [[u'item 1.1', []]]]) -u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t
        \n\t
      • ' - ->>> unordered_list([u'item 1', [[u'item 1.1', []], [u'item 1.2', []]]]) -u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t\t
        • item 1.2
        • \n\t
        \n\t
      • ' - ->>> unordered_list(['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]) -u'\t
      • States\n\t
          \n\t\t
        • Kansas\n\t\t
            \n\t\t\t
          • Lawrence
          • \n\t\t\t
          • Topeka
          • \n\t\t
          \n\t\t
        • \n\t\t
        • Illinois
        • \n\t
        \n\t
      • ' - ->>> add(u'1', u'2') -3 - ->>> get_digit(123, 1) -3 - ->>> get_digit(123, 2) -2 - ->>> get_digit(123, 3) -1 - ->>> get_digit(123, 4) -0 - ->>> get_digit(123, 0) -123 - ->>> get_digit(u'xyz', 0) -u'xyz' - -# real testing of date() is in dateformat.py ->>> date(datetime.datetime(2005, 12, 29), u"d F Y") -u'29 December 2005' ->>> date(datetime.datetime(2005, 12, 29), ur'jS o\f F') -u'29th of December' - -# real testing of time() is done in dateformat.py ->>> time(datetime.time(13), u"h") -u'01' - ->>> time(datetime.time(0), u"h") -u'12' - -# real testing is done in timesince.py, where we can provide our own 'now' ->>> timesince(datetime.datetime.now() - datetime.timedelta(1)) -u'1 day' - ->>> timesince(datetime.datetime(2005, 12, 29), datetime.datetime(2005, 12, 30)) -u'1 day' - ->>> timeuntil(datetime.datetime.now() + datetime.timedelta(1)) -u'1 day' - ->>> timeuntil(datetime.datetime(2005, 12, 30), datetime.datetime(2005, 12, 29)) -u'1 day' - ->>> default(u"val", u"default") -u'val' - ->>> default(None, u"default") -u'default' - ->>> default(u'', u"default") -u'default' - ->>> default_if_none(u"val", u"default") -u'val' - ->>> default_if_none(None, u"default") -u'default' - ->>> default_if_none(u'', u"default") -u'' - ->>> divisibleby(4, 2) -True - ->>> divisibleby(4, 3) -False - ->>> yesno(True) -u'yes' - ->>> yesno(False) -u'no' - ->>> yesno(None) -u'maybe' - ->>> yesno(True, u'certainly,get out of town,perhaps') -u'certainly' - ->>> yesno(False, u'certainly,get out of town,perhaps') -u'get out of town' - ->>> yesno(None, u'certainly,get out of town,perhaps') -u'perhaps' - ->>> yesno(None, u'certainly,get out of town') -u'get out of town' - ->>> filesizeformat(1023) -u'1023 bytes' - ->>> filesizeformat(1024) -u'1.0 KB' - ->>> filesizeformat(10*1024) -u'10.0 KB' - ->>> filesizeformat(1024*1024-1) -u'1024.0 KB' - ->>> filesizeformat(1024*1024) -u'1.0 MB' - ->>> filesizeformat(1024*1024*50) -u'50.0 MB' - ->>> filesizeformat(1024*1024*1024-1) -u'1024.0 MB' - ->>> filesizeformat(1024*1024*1024) -u'1.0 GB' - ->>> filesizeformat(complex(1,-1)) -u'0 bytes' - ->>> filesizeformat("") -u'0 bytes' - ->>> filesizeformat(u"\N{GREEK SMALL LETTER ALPHA}") -u'0 bytes' - ->>> pluralize(1) -u'' - ->>> pluralize(0) -u's' - ->>> pluralize(2) -u's' - ->>> pluralize([1]) -u'' - ->>> pluralize([]) -u's' - ->>> pluralize([1,2,3]) -u's' - ->>> pluralize(1,u'es') -u'' - ->>> pluralize(0,u'es') -u'es' - ->>> pluralize(2,u'es') -u'es' - ->>> pluralize(1,u'y,ies') -u'y' - ->>> pluralize(0,u'y,ies') -u'ies' - ->>> pluralize(2,u'y,ies') -u'ies' - ->>> pluralize(0,u'y,ies,error') -u'' - ->>> phone2numeric(u'0800 flowers') -u'0800 3569377' - -# Filters shouldn't break if passed non-strings ->>> addslashes(123) -u'123' ->>> linenumbers(123) -u'1. 123' ->>> lower(123) -u'123' ->>> make_list(123) -[u'1', u'2', u'3'] ->>> slugify(123) -u'123' ->>> title(123) -u'123' ->>> truncatewords(123, 2) -u'123' ->>> upper(123) -u'123' ->>> urlencode(123) -u'123' ->>> urlize(123) -u'123' ->>> urlizetrunc(123, 1) -u'123' ->>> wordcount(123) -1 ->>> wordwrap(123, 2) -u'123' ->>> ljust('123', 4) -u'123 ' ->>> rjust('123', 4) -u' 123' ->>> center('123', 5) -u' 123 ' ->>> center('123', 6) -u' 123 ' ->>> cut(123, '2') -u'13' ->>> escape(123) -u'123' ->>> linebreaks(123) -u'

        123

        ' ->>> linebreaksbr(123) -u'123' ->>> removetags(123, 'a') -u'123' ->>> striptags(123) -u'123' - -""" +import datetime +import unittest from django.template.defaultfilters import * -import datetime -if __name__ == '__main__': - import doctest - doctest.testmod() +class DefaultFiltersTests(unittest.TestCase): + + def test_floatformat(self): + self.assertEqual(floatformat(7.7), u'7.7') + self.assertEqual(floatformat(7.0), u'7') + self.assertEqual(floatformat(0.7), u'0.7') + self.assertEqual(floatformat(0.07), u'0.1') + self.assertEqual(floatformat(0.007), u'0.0') + self.assertEqual(floatformat(0.0), u'0') + self.assertEqual(floatformat(7.7, 3), u'7.700') + self.assertEqual(floatformat(6.000000, 3), u'6.000') + self.assertEqual(floatformat(6.200000, 3), u'6.200') + self.assertEqual(floatformat(6.200000, -3), u'6.200') + self.assertEqual(floatformat(13.1031, -3), u'13.103') + self.assertEqual(floatformat(11.1197, -2), u'11.12') + self.assertEqual(floatformat(11.0000, -2), u'11') + self.assertEqual(floatformat(11.000001, -2), u'11.00') + self.assertEqual(floatformat(8.2798, 3), u'8.280') + self.assertEqual(floatformat(u'foo'), u'') + self.assertEqual(floatformat(13.1031, u'bar'), u'13.1031') + self.assertEqual(floatformat(18.125, 2), u'18.13') + self.assertEqual(floatformat(u'foo', u'bar'), u'') + self.assertEqual(floatformat(u'¿Cómo esta usted?'), u'') + self.assertEqual(floatformat(None), u'') + + pos_inf = float(1e30000) + self.assertEqual(floatformat(pos_inf), unicode(pos_inf)) + + neg_inf = float(-1e30000) + self.assertEqual(floatformat(neg_inf), unicode(neg_inf)) + + nan = pos_inf / pos_inf + self.assertEqual(floatformat(nan), unicode(nan)) + + class FloatWrapper(object): + def __init__(self, value): + self.value = value + def __float__(self): + return self.value + + self.assertEqual(floatformat(FloatWrapper(11.000001), -2), u'11.00') + + def test_addslashes(self): + self.assertEqual(addslashes(u'"double quotes" and \'single quotes\''), + u'\\"double quotes\\" and \\\'single quotes\\\'') + + self.assertEqual(addslashes(ur'\ : backslashes, too'), + u'\\\\ : backslashes, too') + + def test_capfirst(self): + self.assertEqual(capfirst(u'hello world'), u'Hello world') + + def test_escapejs(self): + self.assertEqual(escapejs(u'"double quotes" and \'single quotes\''), + u'\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027') + self.assertEqual(escapejs(ur'\ : backslashes, too'), + u'\\u005C : backslashes, too') + self.assertEqual(escapejs(u'and lots of whitespace: \r\n\t\v\f\b'), + u'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008') + self.assertEqual(escapejs(ur''), + u'\\u003Cscript\\u003Eand this\\u003C/script\\u003E') + self.assertEqual( + escapejs(u'paragraph separator:\u2029and line separator:\u2028'), + u'paragraph separator:\\u2029and line separator:\\u2028') + + def test_fix_ampersands(self): + self.assertEqual(fix_ampersands(u'Jack & Jill & Jeroboam'), + u'Jack & Jill & Jeroboam') + + def test_linenumbers(self): + self.assertEqual(linenumbers(u'line 1\nline 2'), + u'1. line 1\n2. line 2') + self.assertEqual(linenumbers(u'\n'.join([u'x'] * 10)), + u'01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. '\ + u'x\n08. x\n09. x\n10. x') + + def test_lower(self): + self.assertEqual(lower('TEST'), u'test') + + # uppercase E umlaut + self.assertEqual(lower(u'\xcb'), u'\xeb') + + def test_make_list(self): + self.assertEqual(make_list('abc'), [u'a', u'b', u'c']) + self.assertEqual(make_list(1234), [u'1', u'2', u'3', u'4']) + + def test_slugify(self): + self.assertEqual(slugify(' Jack & Jill like numbers 1,2,3 and 4 and'\ + ' silly characters ?%.$!/'), + u'jack-jill-like-numbers-123-and-4-and-silly-characters') + + self.assertEqual(slugify(u"Un \xe9l\xe9phant \xe0 l'or\xe9e du bois"), + u'un-elephant-a-loree-du-bois') + + def test_stringformat(self): + self.assertEqual(stringformat(1, u'03d'), u'001') + self.assertEqual(stringformat(1, u'z'), u'') + + def test_title(self): + self.assertEqual(title('a nice title, isn\'t it?'), + u"A Nice Title, Isn't It?") + self.assertEqual(title(u'discoth\xe8que'), u'Discoth\xe8que') + + def test_truncatewords(self): + self.assertEqual( + truncatewords(u'A sentence with a few words in it', 1), u'A ...') + self.assertEqual( + truncatewords(u'A sentence with a few words in it', 5), + u'A sentence with a few ...') + self.assertEqual( + truncatewords(u'A sentence with a few words in it', 100), + u'A sentence with a few words in it') + self.assertEqual( + truncatewords(u'A sentence with a few words in it', + 'not a number'), u'A sentence with a few words in it') + + def test_truncatewords_html(self): + self.assertEqual(truncatewords_html( + u'

        one two - three
        four
        five

        ', 0), u'') + self.assertEqual(truncatewords_html(u'

        one two - '\ + u'three
        four
        five

        ', 2), + u'

        one two ...

        ') + self.assertEqual(truncatewords_html( + u'

        one two - three
        four
        five

        ', 4), + u'

        one two - three
        four ...

        ') + self.assertEqual(truncatewords_html( + u'

        one two - three
        four
        five

        ', 5), + u'

        one two - three
        four
        five

        ') + self.assertEqual(truncatewords_html( + u'

        one two - three
        four
        five

        ', 100), + u'

        one two - three
        four
        five

        ') + self.assertEqual(truncatewords_html( + u'\xc5ngstr\xf6m was here', 1), u'\xc5ngstr\xf6m ...') + + def test_upper(self): + self.assertEqual(upper(u'Mixed case input'), u'MIXED CASE INPUT') + # lowercase e umlaut + self.assertEqual(upper(u'\xeb'), u'\xcb') + + def test_urlencode(self): + self.assertEqual(urlencode(u'fran\xe7ois & jill'), + u'fran%C3%A7ois%20%26%20jill') + self.assertEqual(urlencode(1), u'1') + + def test_iriencode(self): + self.assertEqual(iriencode(u'S\xf8r-Tr\xf8ndelag'), + u'S%C3%B8r-Tr%C3%B8ndelag') + self.assertEqual(iriencode(urlencode(u'fran\xe7ois & jill')), + u'fran%C3%A7ois%20%26%20jill') + + def test_urlizetrunc(self): + self.assertEqual(urlizetrunc(u'http://short.com/', 20), u'http://short.com/') + + self.assertEqual(urlizetrunc(u'http://www.google.co.uk/search?hl=en'\ + u'&q=some+long+url&btnG=Search&meta=', 20), u'http://www.google...') + + self.assertEqual(urlizetrunc('http://www.google.co.uk/search?hl=en'\ + u'&q=some+long+url&btnG=Search&meta=', 20), u'http://www.google...') + + # Check truncating of URIs which are the exact length + uri = 'http://31characteruri.com/test/' + self.assertEqual(len(uri), 31) + + self.assertEqual(urlizetrunc(uri, 31), + u''\ + u'http://31characteruri.com/test/') + + self.assertEqual(urlizetrunc(uri, 30), + u''\ + u'http://31characteruri.com/t...') + + self.assertEqual(urlizetrunc(uri, 2), + u'...') + + def test_urlize(self): + # Check normal urlize + self.assertEqual(urlize('http://google.com'), + u'http://google.com') + self.assertEqual(urlize('http://google.com/'), + u'http://google.com/') + self.assertEqual(urlize('www.google.com'), + u'www.google.com') + self.assertEqual(urlize('djangoproject.org'), + u'djangoproject.org') + self.assertEqual(urlize('info@djangoproject.org'), + u'info@djangoproject.org') + + # Check urlize with https addresses + self.assertEqual(urlize('https://google.com'), + u'https://google.com') + + def test_wordcount(self): + self.assertEqual(wordcount(''), 0) + self.assertEqual(wordcount(u'oneword'), 1) + self.assertEqual(wordcount(u'lots of words'), 3) + + self.assertEqual(wordwrap(u'this is a long paragraph of text that '\ + u'really needs to be wrapped I\'m afraid', 14), + u"this is a long\nparagraph of\ntext that\nreally needs\nto be "\ + u"wrapped\nI'm afraid") + + self.assertEqual(wordwrap(u'this is a short paragraph of text.\n '\ + u'But this line should be indented', 14), + u'this is a\nshort\nparagraph of\ntext.\n But this\nline '\ + u'should be\nindented') + + self.assertEqual(wordwrap(u'this is a short paragraph of text.\n '\ + u'But this line should be indented',15), u'this is a short\n'\ + u'paragraph of\ntext.\n But this line\nshould be\nindented') + + def test_rjust(self): + self.assertEqual(ljust(u'test', 10), u'test ') + self.assertEqual(ljust(u'test', 3), u'test') + self.assertEqual(rjust(u'test', 10), u' test') + self.assertEqual(rjust(u'test', 3), u'test') + + def test_center(self): + self.assertEqual(center(u'test', 6), u' test ') + + def test_cut(self): + self.assertEqual(cut(u'a string to be mangled', 'a'), + u' string to be mngled') + self.assertEqual(cut(u'a string to be mangled', 'ng'), + u'a stri to be maled') + self.assertEqual(cut(u'a string to be mangled', 'strings'), + u'a string to be mangled') + + def test_force_escape(self): + self.assertEqual( + force_escape(u' here'), + u'<some html & special characters > here') + self.assertEqual( + force_escape(u' here ĐÅ€£'), + u'<some html & special characters > here'\ + u' \u0110\xc5\u20ac\xa3') + + def test_linebreaks(self): + self.assertEqual(linebreaks(u'line 1'), u'

        line 1

        ') + self.assertEqual(linebreaks(u'line 1\nline 2'), + u'

        line 1
        line 2

        ') + + def test_removetags(self): + self.assertEqual(removetags(u'some html with disallowed tags', 'script img'), + u'some html with alert("You smell") disallowed tags') + self.assertEqual(striptags(u'some html with disallowed tags'), + u'some html with alert("You smell") disallowed tags') + + def test_dictsort(self): + sorted_dicts = dictsort([{'age': 23, 'name': 'Barbara-Ann'}, + {'age': 63, 'name': 'Ra Ra Rasputin'}, + {'name': 'Jonny B Goode', 'age': 18}], 'age') + + self.assertEqual([sorted(dict.items()) for dict in sorted_dicts], + [[('age', 18), ('name', 'Jonny B Goode')], + [('age', 23), ('name', 'Barbara-Ann')], + [('age', 63), ('name', 'Ra Ra Rasputin')]]) + + def test_dictsortreversed(self): + sorted_dicts = dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'}, + {'age': 63, 'name': 'Ra Ra Rasputin'}, + {'name': 'Jonny B Goode', 'age': 18}], + 'age') + + self.assertEqual([sorted(dict.items()) for dict in sorted_dicts], + [[('age', 63), ('name', 'Ra Ra Rasputin')], + [('age', 23), ('name', 'Barbara-Ann')], + [('age', 18), ('name', 'Jonny B Goode')]]) + + def test_first(self): + self.assertEqual(first([0,1,2]), 0) + self.assertEqual(first(u''), u'') + self.assertEqual(first(u'test'), u't') + + def test_join(self): + self.assertEqual(join([0,1,2], u'glue'), u'0glue1glue2') + + def test_length(self): + self.assertEqual(length(u'1234'), 4) + self.assertEqual(length([1,2,3,4]), 4) + self.assertEqual(length_is([], 0), True) + self.assertEqual(length_is([], 1), False) + self.assertEqual(length_is('a', 1), True) + self.assertEqual(length_is(u'a', 10), False) + + def test_slice(self): + self.assertEqual(slice_(u'abcdefg', u'0'), u'') + self.assertEqual(slice_(u'abcdefg', u'1'), u'a') + self.assertEqual(slice_(u'abcdefg', u'-1'), u'abcdef') + self.assertEqual(slice_(u'abcdefg', u'1:2'), u'b') + self.assertEqual(slice_(u'abcdefg', u'1:3'), u'bc') + self.assertEqual(slice_(u'abcdefg', u'0::2'), u'aceg') + + def test_unordered_list(self): + self.assertEqual(unordered_list([u'item 1', u'item 2']), + u'\t
      • item 1
      • \n\t
      • item 2
      • ') + self.assertEqual(unordered_list([u'item 1', [u'item 1.1']]), + u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t
        \n\t
      • ') + + self.assertEqual( + unordered_list([u'item 1', [u'item 1.1', u'item1.2'], u'item 2']), + u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t\t
        • item1.2'\ + u'
        • \n\t
        \n\t
      • \n\t
      • item 2
      • ') + + self.assertEqual( + unordered_list([u'item 1', [u'item 1.1', [u'item 1.1.1', + [u'item 1.1.1.1']]]]), + u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1\n\t\t
            \n\t\t\t
          • '\ + u'item 1.1.1\n\t\t\t
              \n\t\t\t\t
            • item 1.1.1.1
            • \n\t\t\t'\ + u'
            \n\t\t\t
          • \n\t\t
          \n\t\t
        • \n\t
        \n\t
      • ') + + self.assertEqual(unordered_list( + ['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]), + u'\t
      • States\n\t
          \n\t\t
        • Kansas\n\t\t
            \n\t\t\t
          • '\ + u'Lawrence
          • \n\t\t\t
          • Topeka
          • \n\t\t
          \n\t\t
        • '\ + u'\n\t\t
        • Illinois
        • \n\t
        \n\t
      • ') + + class ULItem(object): + def __init__(self, title): + self.title = title + def __unicode__(self): + return u'ulitem-%s' % str(self.title) + + a = ULItem('a') + b = ULItem('b') + self.assertEqual(unordered_list([a,b]), + u'\t
      • ulitem-a
      • \n\t
      • ulitem-b
      • ') + + # Old format for unordered lists should still work + self.assertEqual(unordered_list([u'item 1', []]), u'\t
      • item 1
      • ') + + self.assertEqual(unordered_list([u'item 1', [[u'item 1.1', []]]]), + u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1
        • \n\t
        \n\t
      • ') + + self.assertEqual(unordered_list([u'item 1', [[u'item 1.1', []], + [u'item 1.2', []]]]), u'\t
      • item 1\n\t
          \n\t\t
        • item 1.1'\ + u'
        • \n\t\t
        • item 1.2
        • \n\t
        \n\t
      • ') + + self.assertEqual(unordered_list(['States', [['Kansas', [['Lawrence', + []], ['Topeka', []]]], ['Illinois', []]]]), u'\t
      • States\n\t'\ + u'
          \n\t\t
        • Kansas\n\t\t
            \n\t\t\t
          • Lawrence
          • '\ + u'\n\t\t\t
          • Topeka
          • \n\t\t
          \n\t\t
        • \n\t\t
        • '\ + u'Illinois
        • \n\t
        \n\t
      • ') + + def test_add(self): + self.assertEqual(add(u'1', u'2'), 3) + + def test_get_digit(self): + self.assertEqual(get_digit(123, 1), 3) + self.assertEqual(get_digit(123, 2), 2) + self.assertEqual(get_digit(123, 3), 1) + self.assertEqual(get_digit(123, 4), 0) + self.assertEqual(get_digit(123, 0), 123) + self.assertEqual(get_digit(u'xyz', 0), u'xyz') + + def test_date(self): + # real testing of date() is in dateformat.py + self.assertEqual(date(datetime.datetime(2005, 12, 29), u"d F Y"), + u'29 December 2005') + self.assertEqual(date(datetime.datetime(2005, 12, 29), ur'jS o\f F'), + u'29th of December') + + def test_time(self): + # real testing of time() is done in dateformat.py + self.assertEqual(time(datetime.time(13), u"h"), u'01') + self.assertEqual(time(datetime.time(0), u"h"), u'12') + + def test_timesince(self): + # real testing is done in timesince.py, where we can provide our own 'now' + self.assertEqual( + timesince(datetime.datetime.now() - datetime.timedelta(1)), + u'1 day') + + self.assertEqual( + timesince(datetime.datetime(2005, 12, 29), + datetime.datetime(2005, 12, 30)), + u'1 day') + + def test_timeuntil(self): + self.assertEqual( + timeuntil(datetime.datetime.now() + datetime.timedelta(1)), + u'1 day') + + self.assertEqual(timeuntil(datetime.datetime(2005, 12, 30), + datetime.datetime(2005, 12, 29)), + u'1 day') + + def test_default(self): + self.assertEqual(default(u"val", u"default"), u'val') + self.assertEqual(default(None, u"default"), u'default') + self.assertEqual(default(u'', u"default"), u'default') + + def test_if_none(self): + self.assertEqual(default_if_none(u"val", u"default"), u'val') + self.assertEqual(default_if_none(None, u"default"), u'default') + self.assertEqual(default_if_none(u'', u"default"), u'') + + def test_divisibleby(self): + self.assertEqual(divisibleby(4, 2), True) + self.assertEqual(divisibleby(4, 3), False) + + def test_yesno(self): + self.assertEqual(yesno(True), u'yes') + self.assertEqual(yesno(False), u'no') + self.assertEqual(yesno(None), u'maybe') + self.assertEqual(yesno(True, u'certainly,get out of town,perhaps'), + u'certainly') + self.assertEqual(yesno(False, u'certainly,get out of town,perhaps'), + u'get out of town') + self.assertEqual(yesno(None, u'certainly,get out of town,perhaps'), + u'perhaps') + self.assertEqual(yesno(None, u'certainly,get out of town'), + u'get out of town') + + def test_filesizeformat(self): + self.assertEqual(filesizeformat(1023), u'1023 bytes') + self.assertEqual(filesizeformat(1024), u'1.0 KB') + self.assertEqual(filesizeformat(10*1024), u'10.0 KB') + self.assertEqual(filesizeformat(1024*1024-1), u'1024.0 KB') + self.assertEqual(filesizeformat(1024*1024), u'1.0 MB') + self.assertEqual(filesizeformat(1024*1024*50), u'50.0 MB') + self.assertEqual(filesizeformat(1024*1024*1024-1), u'1024.0 MB') + self.assertEqual(filesizeformat(1024*1024*1024), u'1.0 GB') + self.assertEqual(filesizeformat(complex(1,-1)), u'0 bytes') + self.assertEqual(filesizeformat(""), u'0 bytes') + self.assertEqual(filesizeformat(u"\N{GREEK SMALL LETTER ALPHA}"), + u'0 bytes') + + def test_pluralize(self): + self.assertEqual(pluralize(1), u'') + self.assertEqual(pluralize(0), u's') + self.assertEqual(pluralize(2), u's') + self.assertEqual(pluralize([1]), u'') + self.assertEqual(pluralize([]), u's') + self.assertEqual(pluralize([1,2,3]), u's') + self.assertEqual(pluralize(1,u'es'), u'') + self.assertEqual(pluralize(0,u'es'), u'es') + self.assertEqual(pluralize(2,u'es'), u'es') + self.assertEqual(pluralize(1,u'y,ies'), u'y') + self.assertEqual(pluralize(0,u'y,ies'), u'ies') + self.assertEqual(pluralize(2,u'y,ies'), u'ies') + self.assertEqual(pluralize(0,u'y,ies,error'), u'') + + def test_phone2numeric(self): + self.assertEqual(phone2numeric(u'0800 flowers'), u'0800 3569377') + + def test_non_string_input(self): + # Filters shouldn't break if passed non-strings + self.assertEqual(addslashes(123), u'123') + self.assertEqual(linenumbers(123), u'1. 123') + self.assertEqual(lower(123), u'123') + self.assertEqual(make_list(123), [u'1', u'2', u'3']) + self.assertEqual(slugify(123), u'123') + self.assertEqual(title(123), u'123') + self.assertEqual(truncatewords(123, 2), u'123') + self.assertEqual(upper(123), u'123') + self.assertEqual(urlencode(123), u'123') + self.assertEqual(urlize(123), u'123') + self.assertEqual(urlizetrunc(123, 1), u'123') + self.assertEqual(wordcount(123), 1) + self.assertEqual(wordwrap(123, 2), u'123') + self.assertEqual(ljust('123', 4), u'123 ') + self.assertEqual(rjust('123', 4), u' 123') + self.assertEqual(center('123', 5), u' 123 ') + self.assertEqual(center('123', 6), u' 123 ') + self.assertEqual(cut(123, '2'), u'13') + self.assertEqual(escape(123), u'123') + self.assertEqual(linebreaks(123), u'

        123

        ') + self.assertEqual(linebreaksbr(123), u'123') + self.assertEqual(removetags(123, 'a'), u'123') + self.assertEqual(striptags(123), u'123') + From 0f41f91bc2fea5a4e11936ff9011148e91366a83 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 08:31:01 +0000 Subject: [PATCH 225/902] [1.2.X] Unified the regressiontests/dateformat tests with the regressiontests/utils/dateformat tests. Backport of r13938 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13946 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/dateformat/__init__.py | 0 tests/regressiontests/dateformat/models.py | 0 tests/regressiontests/dateformat/tests.py | 96 -------------------- tests/regressiontests/utils/dateformat.py | 93 +++++++++++++++++++ 4 files changed, 93 insertions(+), 96 deletions(-) delete mode 100644 tests/regressiontests/dateformat/__init__.py delete mode 100644 tests/regressiontests/dateformat/models.py delete mode 100644 tests/regressiontests/dateformat/tests.py diff --git a/tests/regressiontests/dateformat/__init__.py b/tests/regressiontests/dateformat/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tests/regressiontests/dateformat/models.py b/tests/regressiontests/dateformat/models.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tests/regressiontests/dateformat/tests.py b/tests/regressiontests/dateformat/tests.py deleted file mode 100644 index 545b17d37709..000000000000 --- a/tests/regressiontests/dateformat/tests.py +++ /dev/null @@ -1,96 +0,0 @@ - -from django.utils import dateformat, translation -from unittest import TestCase -import datetime, os, time - -class DateFormatTests(TestCase): - def setUp(self): - self.old_TZ = os.environ.get('TZ') - os.environ['TZ'] = 'Europe/Copenhagen' - translation.activate('en-us') - - try: - # Check if a timezone has been set - time.tzset() - self.tz_tests = True - except AttributeError: - # No timezone available. Don't run the tests that require a TZ - self.tz_tests = False - - def tearDown(self): - if self.old_TZ is None: - del os.environ['TZ'] - else: - os.environ['TZ'] = self.old_TZ - - # Cleanup - force re-evaluation of TZ environment variable. - if self.tz_tests: - time.tzset() - - def test_empty_format(self): - my_birthday = datetime.datetime(1979, 7, 8, 22, 00) - - self.assertEquals(dateformat.format(my_birthday, ''), u'') - - def test_am_pm(self): - my_birthday = datetime.datetime(1979, 7, 8, 22, 00) - - self.assertEquals(dateformat.format(my_birthday, 'a'), u'p.m.') - - def test_date_formats(self): - my_birthday = datetime.datetime(1979, 7, 8, 22, 00) - timestamp = datetime.datetime(2008, 5, 19, 11, 45, 23, 123456) - - self.assertEquals(dateformat.format(my_birthday, 'A'), u'PM') - self.assertEquals(dateformat.format(timestamp, 'c'), u'2008-05-19T11:45:23.123456') - self.assertEquals(dateformat.format(my_birthday, 'd'), u'08') - self.assertEquals(dateformat.format(my_birthday, 'j'), u'8') - self.assertEquals(dateformat.format(my_birthday, 'l'), u'Sunday') - self.assertEquals(dateformat.format(my_birthday, 'L'), u'False') - self.assertEquals(dateformat.format(my_birthday, 'm'), u'07') - self.assertEquals(dateformat.format(my_birthday, 'M'), u'Jul') - self.assertEquals(dateformat.format(my_birthday, 'b'), u'jul') - self.assertEquals(dateformat.format(my_birthday, 'n'), u'7') - self.assertEquals(dateformat.format(my_birthday, 'N'), u'July') - - def test_time_formats(self): - my_birthday = datetime.datetime(1979, 7, 8, 22, 00) - - self.assertEquals(dateformat.format(my_birthday, 'P'), u'10 p.m.') - self.assertEquals(dateformat.format(my_birthday, 's'), u'00') - self.assertEquals(dateformat.format(my_birthday, 'S'), u'th') - self.assertEquals(dateformat.format(my_birthday, 't'), u'31') - self.assertEquals(dateformat.format(my_birthday, 'w'), u'0') - self.assertEquals(dateformat.format(my_birthday, 'W'), u'27') - self.assertEquals(dateformat.format(my_birthday, 'y'), u'79') - self.assertEquals(dateformat.format(my_birthday, 'Y'), u'1979') - self.assertEquals(dateformat.format(my_birthday, 'z'), u'189') - - def test_dateformat(self): - my_birthday = datetime.datetime(1979, 7, 8, 22, 00) - - self.assertEquals(dateformat.format(my_birthday, r'Y z \C\E\T'), u'1979 189 CET') - - self.assertEquals(dateformat.format(my_birthday, r'jS o\f F'), u'8th of July') - - def test_futuredates(self): - the_future = datetime.datetime(2100, 10, 25, 0, 00) - self.assertEquals(dateformat.format(the_future, r'Y'), u'2100') - - def test_timezones(self): - my_birthday = datetime.datetime(1979, 7, 8, 22, 00) - summertime = datetime.datetime(2005, 10, 30, 1, 00) - wintertime = datetime.datetime(2005, 10, 30, 4, 00) - timestamp = datetime.datetime(2008, 5, 19, 11, 45, 23, 123456) - - if self.tz_tests: - self.assertEquals(dateformat.format(my_birthday, 'O'), u'+0100') - self.assertEquals(dateformat.format(my_birthday, 'r'), u'Sun, 8 Jul 1979 22:00:00 +0100') - self.assertEquals(dateformat.format(my_birthday, 'T'), u'CET') - self.assertEquals(dateformat.format(my_birthday, 'U'), u'300315600') - self.assertEquals(dateformat.format(timestamp, 'u'), u'123456') - self.assertEquals(dateformat.format(my_birthday, 'Z'), u'3600') - self.assertEquals(dateformat.format(summertime, 'I'), u'1') - self.assertEquals(dateformat.format(summertime, 'O'), u'+0200') - self.assertEquals(dateformat.format(wintertime, 'I'), u'0') - self.assertEquals(dateformat.format(wintertime, 'O'), u'+0100') diff --git a/tests/regressiontests/utils/dateformat.py b/tests/regressiontests/utils/dateformat.py index d83e3e191c42..b312c8d2aab8 100644 --- a/tests/regressiontests/utils/dateformat.py +++ b/tests/regressiontests/utils/dateformat.py @@ -1,11 +1,36 @@ from datetime import datetime, date import os +import time import unittest from django.utils.dateformat import format +from django.utils import dateformat, translation from django.utils.tzinfo import FixedOffset, LocalTimezone class DateFormatTests(unittest.TestCase): + def setUp(self): + self.old_TZ = os.environ.get('TZ') + os.environ['TZ'] = 'Europe/Copenhagen' + translation.activate('en-us') + + try: + # Check if a timezone has been set + time.tzset() + self.tz_tests = True + except AttributeError: + # No timezone available. Don't run the tests that require a TZ + self.tz_tests = False + + def tearDown(self): + if self.old_TZ is None: + del os.environ['TZ'] + else: + os.environ['TZ'] = self.old_TZ + + # Cleanup - force re-evaluation of TZ environment variable. + if self.tz_tests: + time.tzset() + def test_date(self): d = date(2009, 5, 16) self.assertEquals(date.fromtimestamp(int(format(d, 'U'))), d) @@ -34,3 +59,71 @@ def test_epoch(self): utc = FixedOffset(0) udt = datetime(1970, 1, 1, tzinfo=utc) self.assertEquals(format(udt, 'U'), u'0') + + def test_empty_format(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEquals(dateformat.format(my_birthday, ''), u'') + + def test_am_pm(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEquals(dateformat.format(my_birthday, 'a'), u'p.m.') + + def test_date_formats(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456) + + self.assertEquals(dateformat.format(my_birthday, 'A'), u'PM') + self.assertEquals(dateformat.format(timestamp, 'c'), u'2008-05-19T11:45:23.123456') + self.assertEquals(dateformat.format(my_birthday, 'd'), u'08') + self.assertEquals(dateformat.format(my_birthday, 'j'), u'8') + self.assertEquals(dateformat.format(my_birthday, 'l'), u'Sunday') + self.assertEquals(dateformat.format(my_birthday, 'L'), u'False') + self.assertEquals(dateformat.format(my_birthday, 'm'), u'07') + self.assertEquals(dateformat.format(my_birthday, 'M'), u'Jul') + self.assertEquals(dateformat.format(my_birthday, 'b'), u'jul') + self.assertEquals(dateformat.format(my_birthday, 'n'), u'7') + self.assertEquals(dateformat.format(my_birthday, 'N'), u'July') + + def test_time_formats(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEquals(dateformat.format(my_birthday, 'P'), u'10 p.m.') + self.assertEquals(dateformat.format(my_birthday, 's'), u'00') + self.assertEquals(dateformat.format(my_birthday, 'S'), u'th') + self.assertEquals(dateformat.format(my_birthday, 't'), u'31') + self.assertEquals(dateformat.format(my_birthday, 'w'), u'0') + self.assertEquals(dateformat.format(my_birthday, 'W'), u'27') + self.assertEquals(dateformat.format(my_birthday, 'y'), u'79') + self.assertEquals(dateformat.format(my_birthday, 'Y'), u'1979') + self.assertEquals(dateformat.format(my_birthday, 'z'), u'189') + + def test_dateformat(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEquals(dateformat.format(my_birthday, r'Y z \C\E\T'), u'1979 189 CET') + + self.assertEquals(dateformat.format(my_birthday, r'jS o\f F'), u'8th of July') + + def test_futuredates(self): + the_future = datetime(2100, 10, 25, 0, 00) + self.assertEquals(dateformat.format(the_future, r'Y'), u'2100') + + def test_timezones(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + summertime = datetime(2005, 10, 30, 1, 00) + wintertime = datetime(2005, 10, 30, 4, 00) + timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456) + + if self.tz_tests: + self.assertEquals(dateformat.format(my_birthday, 'O'), u'+0100') + self.assertEquals(dateformat.format(my_birthday, 'r'), u'Sun, 8 Jul 1979 22:00:00 +0100') + self.assertEquals(dateformat.format(my_birthday, 'T'), u'CET') + self.assertEquals(dateformat.format(my_birthday, 'U'), u'300315600') + self.assertEquals(dateformat.format(timestamp, 'u'), u'123456') + self.assertEquals(dateformat.format(my_birthday, 'Z'), u'3600') + self.assertEquals(dateformat.format(summertime, 'I'), u'1') + self.assertEquals(dateformat.format(summertime, 'O'), u'+0200') + self.assertEquals(dateformat.format(wintertime, 'I'), u'0') + self.assertEquals(dateformat.format(wintertime, 'O'), u'+0100') From aec5f084a381062c2d98d17583b997beda31447a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 12:10:52 +0000 Subject: [PATCH 226/902] [1.2.X] Modified the requests unit tests so that they aren't dependent on dictionary ordering. Backport of r13848 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13949 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/requests/tests.py | 46 ++++++++++++------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py index 2bf636c3a2ef..556d61e2ced1 100644 --- a/tests/regressiontests/requests/tests.py +++ b/tests/regressiontests/requests/tests.py @@ -10,22 +10,21 @@ class RequestsTests(unittest.TestCase): def test_httprequest(self): - self.assertEquals(repr(HttpRequest()), - "" - ) + request = HttpRequest() + self.assertEqual(request.GET.keys(), []) + self.assertEqual(request.POST.keys(), []) + self.assertEqual(request.COOKIES.keys(), []) + self.assertEqual(request.META.keys(), []) def test_wsgirequest(self): - self.assertEquals(repr(WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus'})), - ",\n"\ - "POST:,\n"\ - "COOKIES:{},\n"\ - "META:{'PATH_INFO': u'bogus', 'REQUEST_METHOD': 'bogus', 'SCRIPT_NAME': u''}>" - ) + request = WSGIRequest({'PATH_INFO': 'bogus', 'REQUEST_METHOD': 'bogus'}) + self.assertEqual(request.GET.keys(), []) + self.assertEqual(request.POST.keys(), []) + self.assertEqual(request.COOKIES.keys(), []) + self.assertEqual(set(request.META.keys()), set(['PATH_INFO', 'REQUEST_METHOD', 'SCRIPT_NAME'])) + self.assertEqual(request.META['PATH_INFO'], 'bogus') + self.assertEqual(request.META['REQUEST_METHOD'], 'bogus') + self.assertEqual(request.META['SCRIPT_NAME'], '') def test_modpythonrequest(self): class FakeModPythonRequest(ModPythonRequest): @@ -39,23 +38,22 @@ def get_options(self): req = Dummy() req.uri = 'bogus' - self.assertEquals(repr(FakeModPythonRequest(req)), - "") + request = FakeModPythonRequest(req) + self.assertEqual(request.path, 'bogus') + self.assertEqual(request.GET.keys(), []) + self.assertEqual(request.POST.keys(), []) + self.assertEqual(request.COOKIES.keys(), []) + self.assertEqual(request.META.keys(), []) def test_parse_cookie(self): - self.assertEquals(parse_cookie('invalid:key=true'), {}) + self.assertEqual(parse_cookie('invalid:key=true'), {}) def test_httprequest_location(self): request = HttpRequest() - self.assertEquals(request.build_absolute_uri(location="https://www.example.com/asdf"), + self.assertEqual(request.build_absolute_uri(location="https://www.example.com/asdf"), 'https://www.example.com/asdf') request.get_host = lambda: 'www.example.com' request.path = '' - self.assertEquals(request.build_absolute_uri(location="/path/with:colons"), + self.assertEqual(request.build_absolute_uri(location="/path/with:colons"), 'http://www.example.com/path/with:colons') From 51ded1500fe8466743cd47f901a71504ad545406 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 13:44:49 +0000 Subject: [PATCH 227/902] [1.2.X] Migrated expressions_regress doctests. Thanks to Stephan Jaekel. Backport of r13950 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13952 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../expressions_regress/models.py | 129 +----------- .../expressions_regress/tests.py | 195 ++++++++++++++++++ 2 files changed, 197 insertions(+), 127 deletions(-) create mode 100644 tests/regressiontests/expressions_regress/tests.py diff --git a/tests/regressiontests/expressions_regress/models.py b/tests/regressiontests/expressions_regress/models.py index a5177803233e..f2997ee5be94 100644 --- a/tests/regressiontests/expressions_regress/models.py +++ b/tests/regressiontests/expressions_regress/models.py @@ -1,12 +1,7 @@ """ -Spanning tests for all the operations that F() expressions can perform. +Model for testing arithmetic expressions. """ -from django.conf import settings -from django.db import models, DEFAULT_DB_ALIAS - -# -# Model for testing arithmetic expressions. -# +from django.db import models class Number(models.Model): integer = models.IntegerField(db_column='the_integer') @@ -15,123 +10,3 @@ class Number(models.Model): def __unicode__(self): return u'%i, %.3f' % (self.integer, self.float) - -__test__ = {'API_TESTS': """ ->>> from django.db.models import F - ->>> Number(integer=-1).save() ->>> Number(integer=42).save() ->>> Number(integer=1337).save() - -We can fill a value in all objects with an other value of the same object. - ->>> Number.objects.update(float=F('integer')) -3 ->>> Number.objects.all() -[, , ] - -We can increment a value of all objects in a query set. - ->>> Number.objects.filter(integer__gt=0).update(integer=F('integer') + 1) -2 ->>> Number.objects.all() -[, , ] - -We can filter for objects, where a value is not equals the value of an other field. - ->>> Number.objects.exclude(float=F('integer')) -[, ] - -Complex expressions of different connection types are possible. - ->>> n = Number.objects.create(integer=10, float=123.45) - ->>> Number.objects.filter(pk=n.pk).update(float=F('integer') + F('float') * 2) -1 ->>> Number.objects.get(pk=n.pk) - - -# All supported operators work as expected. - ->>> n = Number.objects.create(integer=42, float=15.5) - -# Left hand operators - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') + 15, float=F('float') + 42.7) ->>> Number.objects.get(pk=n.pk) # LH Addition of floats and integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') - 15, float=F('float') - 42.7) ->>> Number.objects.get(pk=n.pk) # LH Subtraction of floats and integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') * 15, float=F('float') * 42.7) ->>> Number.objects.get(pk=n.pk) # Multiplication of floats and integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') / 2, float=F('float') / 42.7) ->>> Number.objects.get(pk=n.pk) # LH Division of floats and integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') % 20) ->>> Number.objects.get(pk=n.pk) # LH Modulo arithmetic on integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') & 56) ->>> Number.objects.get(pk=n.pk) # LH Bitwise ands on integers - - -# Right hand operators - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=15 + F('integer'), float=42.7 + F('float')) ->>> Number.objects.get(pk=n.pk) # RH Addition of floats and integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=15 - F('integer'), float=42.7 - F('float')) ->>> Number.objects.get(pk=n.pk) # RH Subtraction of floats and integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=15 * F('integer'), float=42.7 * F('float')) ->>> Number.objects.get(pk=n.pk) # RH Multiplication of floats and integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=640 / F('integer'), float=42.7 / F('float')) ->>> Number.objects.get(pk=n.pk) # RH Division of floats and integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=69 % F('integer')) ->>> Number.objects.get(pk=n.pk) # RH Modulo arithmetic on integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=15 & F('integer')) ->>> Number.objects.get(pk=n.pk) # RH Bitwise ands on integers - -"""} - -# Oracle doesn't support the Bitwise OR operator. -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle': - __test__['API_TESTS'] += """ - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') | 48) ->>> Number.objects.get(pk=n.pk) # LH Bitwise or on integers - - ->>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5) ->>> _ = Number.objects.filter(pk=n.pk).update(integer=15 | F('integer')) ->>> Number.objects.get(pk=n.pk) # RH Bitwise or on integers - - -""" diff --git a/tests/regressiontests/expressions_regress/tests.py b/tests/regressiontests/expressions_regress/tests.py new file mode 100644 index 000000000000..a66272828db0 --- /dev/null +++ b/tests/regressiontests/expressions_regress/tests.py @@ -0,0 +1,195 @@ +""" +Spanning tests for all the operations that F() expressions can perform. +""" +from django.test import TestCase, Approximate + +from django.conf import settings +from django.db import models, DEFAULT_DB_ALIAS +from django.db.models import F + +from regressiontests.expressions_regress.models import Number + +class ExpressionsRegressTests(TestCase): + + def setUp(self): + Number(integer=-1).save() + Number(integer=42).save() + Number(integer=1337).save() + self.assertEqual(Number.objects.update(float=F('integer')), 3) + + def test_fill_with_value_from_same_object(self): + """ + We can fill a value in all objects with an other value of the + same object. + """ + self.assertQuerysetEqual( + Number.objects.all(), + [ + '', + '', + '' + ] + ) + + def test_increment_value(self): + """ + We can increment a value of all objects in a query set. + """ + self.assertEqual( + Number.objects.filter(integer__gt=0) + .update(integer=F('integer') + 1), + 2) + + self.assertQuerysetEqual( + Number.objects.all(), + [ + '', + '', + '' + ] + ) + + def test_filter_not_equals_other_field(self): + """ + We can filter for objects, where a value is not equals the value + of an other field. + """ + self.assertEqual( + Number.objects.filter(integer__gt=0) + .update(integer=F('integer') + 1), + 2) + self.assertQuerysetEqual( + Number.objects.exclude(float=F('integer')), + [ + '', + '' + ] + ) + + def test_complex_expressions(self): + """ + Complex expressions of different connection types are possible. + """ + n = Number.objects.create(integer=10, float=123.45) + self.assertEqual(Number.objects.filter(pk=n.pk) + .update(float=F('integer') + F('float') * 2), + 1) + + self.assertEqual(Number.objects.get(pk=n.pk).integer, 10) + self.assertEqual(Number.objects.get(pk=n.pk).float, Approximate(256.900, places=3)) + +class ExpressionOperatorTests(TestCase): + def setUp(self): + self.n = Number.objects.create(integer=42, float=15.5) + + def test_lefthand_addition(self): + # LH Addition of floats and integers + Number.objects.filter(pk=self.n.pk).update( + integer=F('integer') + 15, + float=F('float') + 42.7 + ) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 57) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(58.200, places=3)) + + def test_lefthand_subtraction(self): + # LH Subtraction of floats and integers + Number.objects.filter(pk=self.n.pk).update(integer=F('integer') - 15, + float=F('float') - 42.7) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 27) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(-27.200, places=3)) + + def test_lefthand_multiplication(self): + # Multiplication of floats and integers + Number.objects.filter(pk=self.n.pk).update(integer=F('integer') * 15, + float=F('float') * 42.7) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 630) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(661.850, places=3)) + + def test_lefthand_division(self): + # LH Division of floats and integers + Number.objects.filter(pk=self.n.pk).update(integer=F('integer') / 2, + float=F('float') / 42.7) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 21) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(0.363, places=3)) + + def test_lefthand_modulo(self): + # LH Modulo arithmetic on integers + Number.objects.filter(pk=self.n.pk).update(integer=F('integer') % 20) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 2) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3)) + + def test_lefthand_bitwise_and(self): + # LH Bitwise ands on integers + Number.objects.filter(pk=self.n.pk).update(integer=F('integer') & 56) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 40) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3)) + + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle': + def test_lefthand_bitwise_or(self): + # LH Bitwise or on integers + Number.objects.filter(pk=self.n.pk).update(integer=F('integer') | 48) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 58) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3)) + + def test_right_hand_addition(self): + # Right hand operators + Number.objects.filter(pk=self.n.pk).update(integer=15 + F('integer'), + float=42.7 + F('float')) + + # RH Addition of floats and integers + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 57) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(58.200, places=3)) + + def test_right_hand_subtraction(self): + Number.objects.filter(pk=self.n.pk).update(integer=15 - F('integer'), + float=42.7 - F('float')) + + # RH Subtraction of floats and integers + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, -27) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(27.200, places=3)) + + def test_right_hand_multiplication(self): + # RH Multiplication of floats and integers + Number.objects.filter(pk=self.n.pk).update(integer=15 * F('integer'), + float=42.7 * F('float')) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 630) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(661.850, places=3)) + + def test_right_hand_division(self): + # RH Division of floats and integers + Number.objects.filter(pk=self.n.pk).update(integer=640 / F('integer'), + float=42.7 / F('float')) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 15) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(2.755, places=3)) + + def test_right_hand_modulo(self): + # RH Modulo arithmetic on integers + Number.objects.filter(pk=self.n.pk).update(integer=69 % F('integer')) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 27) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3)) + + def test_right_hand_bitwise_and(self): + # RH Bitwise ands on integers + Number.objects.filter(pk=self.n.pk).update(integer=15 & F('integer')) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 10) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3)) + + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle': + def test_right_hand_bitwise_or(self): + # RH Bitwise or on integers + Number.objects.filter(pk=self.n.pk).update(integer=15 | F('integer')) + + self.assertEqual(Number.objects.get(pk=self.n.pk).integer, 47) + self.assertEqual(Number.objects.get(pk=self.n.pk).float, Approximate(15.500, places=3)) + From 49544ffb3616eb3c65d3ecef0ba28f836ab57050 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 13:45:22 +0000 Subject: [PATCH 228/902] [1.2.X] Migrated extra_regress doctests. Thanks to Stephan Jaekel. Backport of r13951 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13953 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/extra_regress/models.py | 177 ---------- tests/regressiontests/extra_regress/tests.py | 314 ++++++++++++++++++ 2 files changed, 314 insertions(+), 177 deletions(-) create mode 100644 tests/regressiontests/extra_regress/tests.py diff --git a/tests/regressiontests/extra_regress/models.py b/tests/regressiontests/extra_regress/models.py index b68a37348cba..073157a38a7f 100644 --- a/tests/regressiontests/extra_regress/models.py +++ b/tests/regressiontests/extra_regress/models.py @@ -4,9 +4,6 @@ from django.contrib.auth.models import User from django.db import models -from django.db.models.query import Q -from django.utils.datastructures import SortedDict - class RevisionableModel(models.Model): base = models.ForeignKey('self', null=True) @@ -41,177 +38,3 @@ class TestObject(models.Model): def __unicode__(self): return u'TestObject: %s,%s,%s' % (self.first,self.second,self.third) -__test__ = {"API_TESTS": """ -# Regression tests for #7314 and #7372 - ->>> rm = RevisionableModel.objects.create(title='First Revision', when=datetime.datetime(2008, 9, 28, 10, 30, 0)) ->>> rm.pk, rm.base.pk -(1, 1) - ->>> rm2 = rm.new_revision() ->>> rm2.title = "Second Revision" ->>> rm.when = datetime.datetime(2008, 9, 28, 14, 25, 0) ->>> rm2.save() ->>> print u"%s of %s" % (rm2.title, rm2.base.title) -Second Revision of First Revision - ->>> rm2.pk, rm2.base.pk -(2, 1) - -Queryset to match most recent revision: ->>> qs = RevisionableModel.objects.extra(where=["%(table)s.id IN (SELECT MAX(rev.id) FROM %(table)s rev GROUP BY rev.base_id)" % {'table': RevisionableModel._meta.db_table,}],) ->>> qs -[] - -Queryset to search for string in title: ->>> qs2 = RevisionableModel.objects.filter(title__contains="Revision") ->>> qs2 -[, ] - -Following queryset should return the most recent revision: ->>> qs & qs2 -[] - ->>> u = User.objects.create_user(username="fred", password="secret", email="fred@example.com") - -# General regression tests: extra select parameters should stay tied to their -# corresponding select portions. Applies when portions are updated or otherwise -# moved around. ->>> qs = User.objects.extra(select=SortedDict((("alpha", "%s"), ("beta", "2"), ("gamma", "%s"))), select_params=(1, 3)) ->>> qs = qs.extra(select={"beta": 4}) ->>> qs = qs.extra(select={"alpha": "%s"}, select_params=[5]) ->>> result = {'alpha': 5, 'beta': 4, 'gamma': 3} ->>> list(qs.filter(id=u.id).values('alpha', 'beta', 'gamma')) == [result] -True - -# Regression test for #7957: Combining extra() calls should leave the -# corresponding parameters associated with the right extra() bit. I.e. internal -# dictionary must remain sorted. ->>> User.objects.extra(select={"alpha": "%s"}, select_params=(1,)).extra(select={"beta": "%s"}, select_params=(2,))[0].alpha -1 ->>> User.objects.extra(select={"beta": "%s"}, select_params=(1,)).extra(select={"alpha": "%s"}, select_params=(2,))[0].alpha -2 - -# Regression test for #7961: When not using a portion of an extra(...) in a -# query, remove any corresponding parameters from the query as well. ->>> list(User.objects.extra(select={"alpha": "%s"}, select_params=(-6,)).filter(id=u.id).values_list('id', flat=True)) == [u.id] -True - -# Regression test for #8063: limiting a query shouldn't discard any extra() -# bits. ->>> qs = User.objects.all().extra(where=['id=%s'], params=[u.id]) ->>> qs -[] ->>> qs[:1] -[] - -# Regression test for #8039: Ordering sometimes removed relevant tables from -# extra(). This test is the critical case: ordering uses a table, but then -# removes the reference because of an optimisation. The table should still be -# present because of the extra() call. ->>> Order.objects.extra(where=["username=%s"], params=["fred"], tables=["auth_user"]).order_by('created_by') -[] - -# Regression test for #8819: Fields in the extra(select=...) list should be -# available to extra(order_by=...). ->>> User.objects.filter(pk=u.id).extra(select={'extra_field': 1}).distinct() -[] ->>> User.objects.filter(pk=u.id).extra(select={'extra_field': 1}, order_by=['extra_field']) -[] ->>> User.objects.filter(pk=u.id).extra(select={'extra_field': 1}, order_by=['extra_field']).distinct() -[] - -# When calling the dates() method on a queryset with extra selection columns, -# we can (and should) ignore those columns. They don't change the result and -# cause incorrect SQL to be produced otherwise. ->>> RevisionableModel.objects.extra(select={"the_answer": 'id'}).dates('when', 'month') -[datetime.datetime(2008, 9, 1, 0, 0)] - -# Regression test for #10256... If there is a values() clause, Extra columns are -# only returned if they are explicitly mentioned. ->>> TestObject(first='first', second='second', third='third').save() - ->>> list(TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values()) == [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': 1, 'first': u'first'}] -True - -# Extra clauses after an empty values clause are still included ->>> list(TestObject.objects.values().extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third'))))) == [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': 1, 'first': u'first'}] -True - -# Extra columns are ignored if not mentioned in the values() clause ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values('first', 'second') -[{'second': u'second', 'first': u'first'}] - -# Extra columns after a non-empty values() clause are ignored ->>> TestObject.objects.values('first', 'second').extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))) -[{'second': u'second', 'first': u'first'}] - -# Extra columns can be partially returned ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values('first', 'second', 'foo') -[{'second': u'second', 'foo': u'first', 'first': u'first'}] - -# Also works if only extra columns are included ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values('foo', 'whiz') -[{'foo': u'first', 'whiz': u'third'}] - -# Values list works the same way -# All columns are returned for an empty values_list() ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list() -[(u'first', u'second', u'third', 1, u'first', u'second', u'third')] - -# Extra columns after an empty values_list() are still included ->>> TestObject.objects.values_list().extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))) -[(u'first', u'second', u'third', 1, u'first', u'second', u'third')] - -# Extra columns ignored completely if not mentioned in values_list() ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('first', 'second') -[(u'first', u'second')] - -# Extra columns after a non-empty values_list() clause are ignored completely ->>> TestObject.objects.values_list('first', 'second').extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))) -[(u'first', u'second')] - ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('second', flat=True) -[u'second'] - -# Only the extra columns specified in the values_list() are returned ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('first', 'second', 'whiz') -[(u'first', u'second', u'third')] - -# ...also works if only extra columns are included ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('foo','whiz') -[(u'first', u'third')] - ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('whiz', flat=True) -[u'third'] - -# ... and values are returned in the order they are specified ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('whiz','foo') -[(u'third', u'first')] - ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('first','id') -[(u'first', 1)] - ->>> TestObject.objects.extra(select=SortedDict((('foo','first'),('bar','second'),('whiz','third')))).values_list('whiz', 'first', 'bar', 'id') -[(u'third', u'first', u'second', 1)] - -# Regression for #10847: the list of extra columns can always be accurately evaluated. -# Using an inner query ensures that as_sql() is producing correct output -# without requiring full evaluation and execution of the inner query. ->>> TestObject.objects.extra(select={'extra': 1}).values('pk') -[{'pk': 1}] - ->>> TestObject.objects.filter(pk__in=TestObject.objects.extra(select={'extra': 1}).values('pk')) -[] - ->>> TestObject.objects.values('pk').extra(select={'extra': 1}) -[{'pk': 1}] - ->>> TestObject.objects.filter(pk__in=TestObject.objects.values('pk').extra(select={'extra': 1})) -[] - ->>> pk = TestObject.objects.get().pk ->>> TestObject.objects.filter(pk=pk) | TestObject.objects.extra(where=["id > %s"], params=[pk]) -[] - -"""} diff --git a/tests/regressiontests/extra_regress/tests.py b/tests/regressiontests/extra_regress/tests.py new file mode 100644 index 000000000000..ef7cbb8b9ba0 --- /dev/null +++ b/tests/regressiontests/extra_regress/tests.py @@ -0,0 +1,314 @@ +from django.test import TestCase + +from django.utils.datastructures import SortedDict + +from django.contrib.auth.models import User +from regressiontests.extra_regress.models import TestObject, Order, \ + RevisionableModel + +import datetime + +class ExtraRegressTests(TestCase): + + def setUp(self): + self.u = User.objects.create_user( + username="fred", + password="secret", + email="fred@example.com" + ) + + def test_regression_7314_7372(self): + """ + Regression tests for #7314 and #7372 + """ + rm = RevisionableModel.objects.create( + title='First Revision', + when=datetime.datetime(2008, 9, 28, 10, 30, 0) + ) + self.assertEqual(rm.pk, rm.base.pk) + + rm2 = rm.new_revision() + rm2.title = "Second Revision" + rm.when = datetime.datetime(2008, 9, 28, 14, 25, 0) + rm2.save() + + self.assertEqual(rm2.title, 'Second Revision') + self.assertEqual(rm2.base.title, 'First Revision') + + self.assertNotEqual(rm2.pk, rm.pk) + self.assertEqual(rm2.base.pk, rm.pk) + + # Queryset to match most recent revision: + qs = RevisionableModel.objects.extra( + where=["%(table)s.id IN (SELECT MAX(rev.id) FROM %(table)s rev GROUP BY rev.base_id)" % { + 'table': RevisionableModel._meta.db_table, + }] + ) + + self.assertQuerysetEqual(qs, + [('Second Revision', 'First Revision')], + transform=lambda r: (r.title, r.base.title) + ) + + # Queryset to search for string in title: + qs2 = RevisionableModel.objects.filter(title__contains="Revision") + self.assertQuerysetEqual(qs2, + [ + ('First Revision', 'First Revision'), + ('Second Revision', 'First Revision'), + ], + transform=lambda r: (r.title, r.base.title) + ) + + # Following queryset should return the most recent revision: + self.assertQuerysetEqual(qs & qs2, + [('Second Revision', 'First Revision')], + transform=lambda r: (r.title, r.base.title) + ) + + def test_extra_stay_tied(self): + # Extra select parameters should stay tied to their corresponding + # select portions. Applies when portions are updated or otherwise + # moved around. + qs = User.objects.extra( + select=SortedDict((("alpha", "%s"), ("beta", "2"), ("gamma", "%s"))), + select_params=(1, 3) + ) + qs = qs.extra(select={"beta": 4}) + qs = qs.extra(select={"alpha": "%s"}, select_params=[5]) + self.assertEqual( + list(qs.filter(id=self.u.id).values('alpha', 'beta', 'gamma')), + [{'alpha': 5, 'beta': 4, 'gamma': 3}] + ) + + def test_regression_7957(self): + """ + Regression test for #7957: Combining extra() calls should leave the + corresponding parameters associated with the right extra() bit. I.e. + internal dictionary must remain sorted. + """ + self.assertEqual( + User.objects.extra(select={"alpha": "%s"}, select_params=(1,) + ).extra(select={"beta": "%s"}, select_params=(2,))[0].alpha, + 1) + + self.assertEqual( + User.objects.extra(select={"beta": "%s"}, select_params=(1,) + ).extra(select={"alpha": "%s"}, select_params=(2,))[0].alpha, + 2) + + def test_regression_7961(self): + """ + Regression test for #7961: When not using a portion of an + extra(...) in a query, remove any corresponding parameters from the + query as well. + """ + self.assertEqual( + list(User.objects.extra(select={"alpha": "%s"}, select_params=(-6,) + ).filter(id=self.u.id).values_list('id', flat=True)), + [self.u.id] + ) + + def test_regression_8063(self): + """ + Regression test for #8063: limiting a query shouldn't discard any + extra() bits. + """ + qs = User.objects.all().extra(where=['id=%s'], params=[self.u.id]) + self.assertQuerysetEqual(qs, ['']) + self.assertQuerysetEqual(qs[:1], ['']) + + def test_regression_8039(self): + """ + Regression test for #8039: Ordering sometimes removed relevant tables + from extra(). This test is the critical case: ordering uses a table, + but then removes the reference because of an optimisation. The table + should still be present because of the extra() call. + """ + self.assertQuerysetEqual( + Order.objects.extra(where=["username=%s"], + params=["fred"], + tables=["auth_user"] + ).order_by('created_by'), + [] + ) + + def test_regression_8819(self): + """ + Regression test for #8819: Fields in the extra(select=...) list + should be available to extra(order_by=...). + """ + self.assertQuerysetEqual( + User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}).distinct(), + [''] + ) + self.assertQuerysetEqual( + User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']), + [''] + ) + self.assertQuerysetEqual( + User.objects.filter(pk=self.u.id).extra(select={'extra_field': 1}, order_by=['extra_field']).distinct(), + [''] + ) + + def test_dates_query(self): + """ + When calling the dates() method on a queryset with extra selection + columns, we can (and should) ignore those columns. They don't change + the result and cause incorrect SQL to be produced otherwise. + """ + rm = RevisionableModel.objects.create( + title='First Revision', + when=datetime.datetime(2008, 9, 28, 10, 30, 0) + ) + + self.assertQuerysetEqual( + RevisionableModel.objects.extra(select={"the_answer": 'id'}).dates('when', 'month'), + ['datetime.datetime(2008, 9, 1, 0, 0)'] + ) + + def test_values_with_extra(self): + """ + Regression test for #10256... If there is a values() clause, Extra + columns are only returned if they are explicitly mentioned. + """ + obj = TestObject(first='first', second='second', third='third') + obj.save() + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values()), + [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': obj.pk, 'first': u'first'}] + ) + + # Extra clauses after an empty values clause are still included + self.assertEqual( + list(TestObject.objects.values().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + [{'bar': u'second', 'third': u'third', 'second': u'second', 'whiz': u'third', 'foo': u'first', 'id': obj.pk, 'first': u'first'}] + ) + + # Extra columns are ignored if not mentioned in the values() clause + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second')), + [{'second': u'second', 'first': u'first'}] + ) + + # Extra columns after a non-empty values() clause are ignored + self.assertEqual( + list(TestObject.objects.values('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + [{'second': u'second', 'first': u'first'}] + ) + + # Extra columns can be partially returned + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('first', 'second', 'foo')), + [{'second': u'second', 'foo': u'first', 'first': u'first'}] + ) + + # Also works if only extra columns are included + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values('foo', 'whiz')), + [{'foo': u'first', 'whiz': u'third'}] + ) + + # Values list works the same way + # All columns are returned for an empty values_list() + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list()), + [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')] + ) + + # Extra columns after an empty values_list() are still included + self.assertEqual( + list(TestObject.objects.values_list().extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + [(u'first', u'second', u'third', obj.pk, u'first', u'second', u'third')] + ) + + # Extra columns ignored completely if not mentioned in values_list() + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second')), + [(u'first', u'second')] + ) + + # Extra columns after a non-empty values_list() clause are ignored completely + self.assertEqual( + list(TestObject.objects.values_list('first', 'second').extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third'))))), + [(u'first', u'second')] + ) + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('second', flat=True)), + [u'second'] + ) + + # Only the extra columns specified in the values_list() are returned + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first', 'second', 'whiz')), + [(u'first', u'second', u'third')] + ) + + # ...also works if only extra columns are included + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('foo','whiz')), + [(u'first', u'third')] + ) + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', flat=True)), + [u'third'] + ) + + # ... and values are returned in the order they are specified + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz','foo')), + [(u'third', u'first')] + ) + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('first','id')), + [(u'first', obj.pk)] + ) + + self.assertEqual( + list(TestObject.objects.extra(select=SortedDict((('foo','first'), ('bar','second'), ('whiz','third')))).values_list('whiz', 'first', 'bar', 'id')), + [(u'third', u'first', u'second', obj.pk)] + ) + + def test_regression_10847(self): + """ + Regression for #10847: the list of extra columns can always be + accurately evaluated. Using an inner query ensures that as_sql() is + producing correct output without requiring full evaluation and + execution of the inner query. + """ + obj = TestObject(first='first', second='second', third='third') + obj.save() + + self.assertEqual( + list(TestObject.objects.extra(select={'extra': 1}).values('pk')), + [{'pk': obj.pk}] + ) + + self.assertQuerysetEqual( + TestObject.objects.filter( + pk__in=TestObject.objects.extra(select={'extra': 1}).values('pk') + ), + [''] + ) + + self.assertEqual( + list(TestObject.objects.values('pk').extra(select={'extra': 1})), + [{'pk': obj.pk}] + ) + + self.assertQuerysetEqual( + TestObject.objects.filter( + pk__in=TestObject.objects.values('pk').extra(select={'extra': 1}) + ), + [''] + ) + + self.assertQuerysetEqual( + TestObject.objects.filter(pk=obj.pk) | + TestObject.objects.extra(where=["id > %s"], params=[obj.pk]), + [''] + ) From cd7e271e5424bd2f32f8b064072a4e040fca5a9a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 14:53:21 +0000 Subject: [PATCH 229/902] [1.2.X] Migrated fixtures_regress doctests. Thanks to David Brenneman for the patch. Backport of r13954 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13955 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + .../fixtures_regress/models.py | 249 +------- .../regressiontests/fixtures_regress/tests.py | 564 ++++++++++++++++++ 3 files changed, 594 insertions(+), 220 deletions(-) create mode 100644 tests/regressiontests/fixtures_regress/tests.py diff --git a/AUTHORS b/AUTHORS index 6e56b24dae52..e8584324bc34 100644 --- a/AUTHORS +++ b/AUTHORS @@ -82,6 +82,7 @@ answer newbie questions, and generally made Django that much better: Matías Bordese Sean Brant Andrew Brehaut + David Brennenman brut.alll@gmail.com bthomas btoll@bestweb.net diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py index 0e727c49353b..9e09a1688bce 100644 --- a/tests/regressiontests/fixtures_regress/models.py +++ b/tests/regressiontests/fixtures_regress/models.py @@ -1,7 +1,7 @@ from django.db import models, DEFAULT_DB_ALIAS from django.contrib.auth.models import User from django.conf import settings -import os + class Animal(models.Model): name = models.CharField(max_length=150) @@ -15,10 +15,6 @@ class Animal(models.Model): def __unicode__(self): return self.name -def animal_pre_save_check(signal, sender, instance, **kwargs): - "A signal that is used to check the type of data loaded from fixtures" - print 'Count = %s (%s)' % (instance.count, type(instance.count)) - print 'Weight = %s (%s)' % (instance.weight, type(instance.weight)) class Plant(models.Model): name = models.CharField(max_length=150) @@ -40,6 +36,7 @@ def __unicode__(self): name = None return unicode(name) + u' is owned by ' + unicode(self.owner) + class Absolute(models.Model): name = models.CharField(max_length=40) @@ -49,19 +46,23 @@ def __init__(self, *args, **kwargs): super(Absolute, self).__init__(*args, **kwargs) Absolute.load_count += 1 + class Parent(models.Model): name = models.CharField(max_length=10) class Meta: ordering = ('id',) + class Child(Parent): data = models.CharField(max_length=10) + # Models to regression test #7572 class Channel(models.Model): name = models.CharField(max_length=255) + class Article(models.Model): title = models.CharField(max_length=255) channels = models.ManyToManyField(Channel) @@ -69,6 +70,7 @@ class Article(models.Model): class Meta: ordering = ('id',) + # Models to regression test #11428 class Widget(models.Model): name = models.CharField(max_length=255) @@ -79,16 +81,18 @@ class Meta: def __unicode__(self): return self.name + class WidgetProxy(Widget): class Meta: proxy = True -# Check for forward references in FKs and M2Ms with natural keys +# Check for forward references in FKs and M2Ms with natural keys class TestManager(models.Manager): def get_by_natural_key(self, key): return self.get(name=key) + class Store(models.Model): objects = TestManager() name = models.CharField(max_length=255) @@ -102,6 +106,7 @@ def __unicode__(self): def natural_key(self): return (self.name,) + class Person(models.Model): objects = TestManager() name = models.CharField(max_length=255) @@ -118,6 +123,7 @@ def natural_key(self): return (self.name,) natural_key.dependencies = ['fixtures_regress.store'] + class Book(models.Model): name = models.CharField(max_length=255) author = models.ForeignKey(Person) @@ -133,10 +139,12 @@ def __unicode__(self): ', '.join(s.name for s in self.stores.all()) ) + class NKManager(models.Manager): def get_by_natural_key(self, data): return self.get(data=data) + class NKChild(Parent): data = models.CharField(max_length=10, unique=True) objects = NKManager() @@ -147,6 +155,7 @@ def natural_key(self): def __unicode__(self): return u'NKChild %s:%s' % (self.name, self.data) + class RefToNKChild(models.Model): text = models.CharField(max_length=10) nk_fk = models.ForeignKey(NKChild, related_name='ref_fks') @@ -159,260 +168,60 @@ def __unicode__(self): ', '.join(str(o) for o in self.nk_m2m.all()) ) + # ome models with pathological circular dependencies class Circle1(models.Model): name = models.CharField(max_length=255) + def natural_key(self): return self.name natural_key.dependencies = ['fixtures_regress.circle2'] + class Circle2(models.Model): name = models.CharField(max_length=255) + def natural_key(self): return self.name natural_key.dependencies = ['fixtures_regress.circle1'] + class Circle3(models.Model): name = models.CharField(max_length=255) + def natural_key(self): return self.name natural_key.dependencies = ['fixtures_regress.circle3'] + class Circle4(models.Model): name = models.CharField(max_length=255) + def natural_key(self): return self.name natural_key.dependencies = ['fixtures_regress.circle5'] + class Circle5(models.Model): name = models.CharField(max_length=255) + def natural_key(self): return self.name natural_key.dependencies = ['fixtures_regress.circle6'] + class Circle6(models.Model): name = models.CharField(max_length=255) + def natural_key(self): return self.name natural_key.dependencies = ['fixtures_regress.circle4'] + class ExternalDependency(models.Model): name = models.CharField(max_length=255) + def natural_key(self): return self.name natural_key.dependencies = ['fixtures_regress.book'] -__test__ = {'API_TESTS':""" ->>> from django.core import management - -# Load a fixture that uses PK=1 ->>> management.call_command('loaddata', 'sequence', verbosity=0) - -# Create a new animal. Without a sequence reset, this new object -# will take a PK of 1 (on Postgres), and the save will fail. -# This is a regression test for ticket #3790. ->>> animal = Animal(name='Platypus', latin_name='Ornithorhynchus anatinus', count=2, weight=2.2) ->>> animal.save() - -############################################### -# Regression test for ticket #4558 -- pretty printing of XML fixtures -# doesn't affect parsing of None values. - -# Load a pretty-printed XML fixture with Nulls. ->>> management.call_command('loaddata', 'pretty.xml', verbosity=0) ->>> Stuff.objects.all() -[] - -############################################### -# Regression test for ticket #6436 -- -# os.path.join will throw away the initial parts of a path if it encounters -# an absolute path. This means that if a fixture is specified as an absolute path, -# we need to make sure we don't discover the absolute path in every fixture directory. - ->>> load_absolute_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'absolute.json') ->>> management.call_command('loaddata', load_absolute_path, verbosity=0) ->>> Absolute.load_count -1 - -############################################### -# Test for ticket #4371 -- fixture loading fails silently in testcases -# Validate that error conditions are caught correctly - -# redirect stderr for the next few tests... ->>> import sys ->>> savestderr = sys.stderr ->>> sys.stderr = sys.stdout - -# Loading data of an unknown format should fail ->>> management.call_command('loaddata', 'bad_fixture1.unkn', verbosity=0) -Problem installing fixture 'bad_fixture1': unkn is not a known serialization format. - -# Loading a fixture file with invalid data using explicit filename ->>> management.call_command('loaddata', 'bad_fixture2.xml', verbosity=0) -No fixture data found for 'bad_fixture2'. (File format may be invalid.) - -# Loading a fixture file with invalid data without file extension ->>> management.call_command('loaddata', 'bad_fixture2', verbosity=0) -No fixture data found for 'bad_fixture2'. (File format may be invalid.) - -# Loading a fixture file with no data returns an error ->>> management.call_command('loaddata', 'empty', verbosity=0) -No fixture data found for 'empty'. (File format may be invalid.) - -# If any of the fixtures contain an error, loading is aborted -# (Regression for #9011 - error message is correct) ->>> management.call_command('loaddata', 'bad_fixture2', 'animal', verbosity=0) -No fixture data found for 'bad_fixture2'. (File format may be invalid.) - ->>> sys.stderr = savestderr - -############################################### -# Test for ticket #7565 -- PostgreSQL sequence resetting checks shouldn't -# ascend to parent models when inheritance is used (since they are treated -# individually). - ->>> management.call_command('loaddata', 'model-inheritance.json', verbosity=0) - -############################################### -# Test for ticket #13030 -- natural keys deserialize with fk to inheriting model - -# load data with natural keys ->>> management.call_command('loaddata', 'nk-inheritance.json', verbosity=0) - ->>> NKChild.objects.get(pk=1) - - ->>> RefToNKChild.objects.get(pk=1) - - -# ... and again in XML ->>> management.call_command('loaddata', 'nk-inheritance2.xml', verbosity=0) - ->>> NKChild.objects.get(pk=2) - - ->>> RefToNKChild.objects.get(pk=2) - - -############################################### -# Test for ticket #7572 -- MySQL has a problem if the same connection is -# used to create tables, load data, and then query over that data. -# To compensate, we close the connection after running loaddata. -# This ensures that a new connection is opened when test queries are issued. - ->>> management.call_command('loaddata', 'big-fixture.json', verbosity=0) - ->>> articles = Article.objects.exclude(id=9) ->>> articles.values_list('id', flat=True) -[1, 2, 3, 4, 5, 6, 7, 8] - -# Just for good measure, run the same query again. Under the influence of -# ticket #7572, this will give a different result to the previous call. ->>> articles.values_list('id', flat=True) -[1, 2, 3, 4, 5, 6, 7, 8] - -############################################### -# Test for tickets #8298, #9942 - Field values should be coerced into the -# correct type by the deserializer, not as part of the database write. - ->>> models.signals.pre_save.connect(animal_pre_save_check) ->>> management.call_command('loaddata', 'animal.xml', verbosity=0) -Count = 42 () -Weight = 1.2 () - ->>> models.signals.pre_save.disconnect(animal_pre_save_check) - -############################################### -# Regression for #11286 -- Ensure that dumpdata honors the default manager -# Dump the current contents of the database as a JSON fixture ->>> management.call_command('dumpdata', 'fixtures_regress.animal', format='json') -[{"pk": 1, "model": "fixtures_regress.animal", "fields": {"count": 3, "weight": 1.2, "name": "Lion", "latin_name": "Panthera leo"}}, {"pk": 2, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.2, "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}, {"pk": 10, "model": "fixtures_regress.animal", "fields": {"count": 42, "weight": 1.2, "name": "Emu", "latin_name": "Dromaius novaehollandiae"}}] - -############################################### -# Regression for #11428 - Proxy models aren't included when you dumpdata - -# Flush out the database first ->>> management.call_command('reset', 'fixtures_regress', interactive=False, verbosity=0) - -# Create an instance of the concrete class ->>> Widget(name='grommet').save() - -# Dump data for the entire app. The proxy class shouldn't be included ->>> management.call_command('dumpdata', 'fixtures_regress.widget', 'fixtures_regress.widgetproxy', format='json') -[{"pk": 1, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}] - -############################################### -# Check that natural key requirements are taken into account -# when serializing models ->>> management.call_command('loaddata', 'forward_ref_lookup.json', verbosity=0) - ->>> management.call_command('dumpdata', 'fixtures_regress.book', 'fixtures_regress.person', 'fixtures_regress.store', verbosity=0, use_natural_keys=True) -[{"pk": 2, "model": "fixtures_regress.store", "fields": {"name": "Amazon"}}, {"pk": 3, "model": "fixtures_regress.store", "fields": {"name": "Borders"}}, {"pk": 4, "model": "fixtures_regress.person", "fields": {"name": "Neal Stephenson"}}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}] - -# Now lets check the dependency sorting explicitly - -# It doesn't matter what order you mention the models -# Store *must* be serialized before then Person, and both -# must be serialized before Book. ->>> from django.core.management.commands.dumpdata import sort_dependencies ->>> sort_dependencies([('fixtures_regress', [Book, Person, Store])]) -[, , ] - ->>> sort_dependencies([('fixtures_regress', [Book, Store, Person])]) -[, , ] - ->>> sort_dependencies([('fixtures_regress', [Store, Book, Person])]) -[, , ] - ->>> sort_dependencies([('fixtures_regress', [Store, Person, Book])]) -[, , ] - ->>> sort_dependencies([('fixtures_regress', [Person, Book, Store])]) -[, , ] - ->>> sort_dependencies([('fixtures_regress', [Person, Store, Book])]) -[, , ] - -# A dangling dependency - assume the user knows what they are doing. ->>> sort_dependencies([('fixtures_regress', [Person, Circle1, Store, Book])]) -[, , , ] - -# A tight circular dependency ->>> sort_dependencies([('fixtures_regress', [Person, Circle2, Circle1, Store, Book])]) -Traceback (most recent call last): -... -CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list. - ->>> sort_dependencies([('fixtures_regress', [Circle1, Book, Circle2])]) -Traceback (most recent call last): -... -CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list. - -# A self referential dependency ->>> sort_dependencies([('fixtures_regress', [Book, Circle3])]) -Traceback (most recent call last): -... -CommandError: Can't resolve dependencies for fixtures_regress.Circle3 in serialized app list. - -# A long circular dependency ->>> sort_dependencies([('fixtures_regress', [Person, Circle2, Circle1, Circle3, Store, Book])]) -Traceback (most recent call last): -... -CommandError: Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2, fixtures_regress.Circle3 in serialized app list. - -# A dependency on a normal, non-natural-key model ->>> sort_dependencies([('fixtures_regress', [Person, ExternalDependency, Book])]) -[, , ] - -############################################### -# Check that normal primary keys still work -# on a model with natural key capabilities - ->>> management.call_command('loaddata', 'non_natural_1.json', verbosity=0) ->>> management.call_command('loaddata', 'non_natural_2.xml', verbosity=0) - ->>> Book.objects.all() -[, , ] - -"""} - diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py new file mode 100644 index 000000000000..4b5475e7301c --- /dev/null +++ b/tests/regressiontests/fixtures_regress/tests.py @@ -0,0 +1,564 @@ +# -*- coding: utf-8 -*- +# Unittests for fixtures. +import os +import sys +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +from django.core import management +from django.core.management.commands.dumpdata import sort_dependencies +from django.core.management.base import CommandError +from django.db.models import signals +from django.test import TestCase + +from models import Animal, Stuff +from models import Absolute, Parent, Child +from models import Article, Widget +from models import Store, Person, Book +from models import NKChild, RefToNKChild +from models import Circle1, Circle2, Circle3 +from models import ExternalDependency + + +pre_save_checks = [] +def animal_pre_save_check(signal, sender, instance, **kwargs): + "A signal that is used to check the type of data loaded from fixtures" + pre_save_checks.append( + ( + 'Count = %s (%s)' % (instance.count, type(instance.count)), + 'Weight = %s (%s)' % (instance.weight, type(instance.weight)), + ) + ) + + +class TestFixtures(TestCase): + def test_duplicate_pk(self): + """ + This is a regression test for ticket #3790. + """ + # Load a fixture that uses PK=1 + management.call_command( + 'loaddata', + 'sequence', + verbosity=0, + commit=False + ) + + # Create a new animal. Without a sequence reset, this new object + # will take a PK of 1 (on Postgres), and the save will fail. + + animal = Animal( + name='Platypus', + latin_name='Ornithorhynchus anatinus', + count=2, + weight=2.2 + ) + animal.save() + self.assertEqual(animal.id, 2) + + def test_pretty_print_xml(self): + """ + Regression test for ticket #4558 -- pretty printing of XML fixtures + doesn't affect parsing of None values. + """ + # Load a pretty-printed XML fixture with Nulls. + management.call_command( + 'loaddata', + 'pretty.xml', + verbosity=0, + commit=False + ) + self.assertEqual(Stuff.objects.all()[0].name, None) + self.assertEqual(Stuff.objects.all()[0].owner, None) + + def test_absolute_path(self): + """ + Regression test for ticket #6436 -- + os.path.join will throw away the initial parts of a path if it + encounters an absolute path. + This means that if a fixture is specified as an absolute path, + we need to make sure we don't discover the absolute path in every + fixture directory. + """ + load_absolute_path = os.path.join( + os.path.dirname(__file__), + 'fixtures', + 'absolute.json' + ) + management.call_command( + 'loaddata', + load_absolute_path, + verbosity=0, + commit=False + ) + self.assertEqual(Absolute.load_count, 1) + + + def test_unknown_format(self): + """ + Test for ticket #4371 -- Loading data of an unknown format should fail + Validate that error conditions are caught correctly + """ + stderr = StringIO() + management.call_command( + 'loaddata', + 'bad_fixture1.unkn', + verbosity=0, + commit=False, + stderr=stderr, + ) + self.assertEqual( + stderr.getvalue(), + "Problem installing fixture 'bad_fixture1': unkn is not a known serialization format.\n" + ) + + def test_invalid_data(self): + """ + Test for ticket #4371 -- Loading a fixture file with invalid data + using explicit filename. + Validate that error conditions are caught correctly + """ + stderr = StringIO() + management.call_command( + 'loaddata', + 'bad_fixture2.xml', + verbosity=0, + commit=False, + stderr=stderr, + ) + self.assertEqual( + stderr.getvalue(), + "No fixture data found for 'bad_fixture2'. (File format may be invalid.)\n" + ) + + def test_invalid_data_no_ext(self): + """ + Test for ticket #4371 -- Loading a fixture file with invalid data + without file extension. + Validate that error conditions are caught correctly + """ + stderr = StringIO() + management.call_command( + 'loaddata', + 'bad_fixture2', + verbosity=0, + commit=False, + stderr=stderr, + ) + self.assertEqual( + stderr.getvalue(), + "No fixture data found for 'bad_fixture2'. (File format may be invalid.)\n" + ) + + def test_empty(self): + """ + Test for ticket #4371 -- Loading a fixture file with no data returns an error. + Validate that error conditions are caught correctly + """ + stderr = StringIO() + management.call_command( + 'loaddata', + 'empty', + verbosity=0, + commit=False, + stderr=stderr, + ) + self.assertEqual( + stderr.getvalue(), + "No fixture data found for 'empty'. (File format may be invalid.)\n" + ) + + def test_abort_loaddata_on_error(self): + """ + Test for ticket #4371 -- If any of the fixtures contain an error, + loading is aborted. + Validate that error conditions are caught correctly + """ + stderr = StringIO() + management.call_command( + 'loaddata', + 'empty', + verbosity=0, + commit=False, + stderr=stderr, + ) + self.assertEqual( + stderr.getvalue(), + "No fixture data found for 'empty'. (File format may be invalid.)\n" + ) + + def test_error_message(self): + """ + (Regression for #9011 - error message is correct) + """ + stderr = StringIO() + management.call_command( + 'loaddata', + 'bad_fixture2', + 'animal', + verbosity=0, + commit=False, + stderr=stderr, + ) + self.assertEqual( + stderr.getvalue(), + "No fixture data found for 'bad_fixture2'. (File format may be invalid.)\n" + ) + + def test_pg_sequence_resetting_checks(self): + """ + Test for ticket #7565 -- PostgreSQL sequence resetting checks shouldn't + ascend to parent models when inheritance is used + (since they are treated individually). + """ + management.call_command( + 'loaddata', + 'model-inheritance.json', + verbosity=0, + commit=False + ) + self.assertEqual(Parent.objects.all()[0].id, 1) + self.assertEqual(Child.objects.all()[0].id, 1) + + def test_close_connection_after_loaddata(self): + """ + Test for ticket #7572 -- MySQL has a problem if the same connection is + used to create tables, load data, and then query over that data. + To compensate, we close the connection after running loaddata. + This ensures that a new connection is opened when test queries are + issued. + """ + management.call_command( + 'loaddata', + 'big-fixture.json', + verbosity=0, + commit=False + ) + articles = Article.objects.exclude(id=9) + self.assertEqual( + articles.values_list('id', flat=True).__repr__(), + "[1, 2, 3, 4, 5, 6, 7, 8]" + ) + # Just for good measure, run the same query again. + # Under the influence of ticket #7572, this will + # give a different result to the previous call. + self.assertEqual( + articles.values_list('id', flat=True).__repr__(), + "[1, 2, 3, 4, 5, 6, 7, 8]" + ) + + def test_field_value_coerce(self): + """ + Test for tickets #8298, #9942 - Field values should be coerced into the + correct type by the deserializer, not as part of the database write. + """ + global pre_save_checks + pre_save_checks = [] + signals.pre_save.connect(animal_pre_save_check) + management.call_command( + 'loaddata', + 'animal.xml', + verbosity=0, + commit=False, + ) + self.assertEqual( + pre_save_checks, + [ + ("Count = 42 ()", "Weight = 1.2 ()") + ] + ) + signals.pre_save.disconnect(animal_pre_save_check) + + def test_dumpdata_uses_default_manager(self): + """ + Regression for #11286 + Ensure that dumpdata honors the default manager + Dump the current contents of the database as a JSON fixture + """ + management.call_command( + 'loaddata', + 'animal.xml', + verbosity=0, + commit=False, + ) + management.call_command( + 'loaddata', + 'sequence.json', + verbosity=0, + commit=False, + ) + animal = Animal( + name='Platypus', + latin_name='Ornithorhynchus anatinus', + count=2, + weight=2.2 + ) + animal.save() + + stdout = StringIO() + management.call_command( + 'dumpdata', + 'fixtures_regress.animal', + format='json', + stdout=stdout + ) + self.assertEqual( + stdout.getvalue(), + """[{"pk": 1, "model": "fixtures_regress.animal", "fields": {"count": 3, "weight": 1.2, "name": "Lion", "latin_name": "Panthera leo"}}, {"pk": 10, "model": "fixtures_regress.animal", "fields": {"count": 42, "weight": 1.2, "name": "Emu", "latin_name": "Dromaius novaehollandiae"}}, {"pk": 11, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.2000000000000002, "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}]""" + ) + + def test_proxy_model_included(self): + """ + Regression for #11428 - Proxy models aren't included when you dumpdata + """ + stdout = StringIO() + # Create an instance of the concrete class + Widget(name='grommet').save() + management.call_command( + 'dumpdata', + 'fixtures_regress.widget', + 'fixtures_regress.widgetproxy', + format='json', + stdout=stdout + ) + self.assertEqual( + stdout.getvalue(), + """[{"pk": 1, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]""" + ) + + +class NaturalKeyFixtureTests(TestCase): + def assertRaisesMessage(self, exc, msg, func, *args, **kwargs): + try: + func(*args, **kwargs) + except Exception, e: + self.assertEqual(msg, str(e)) + self.assertTrue(isinstance(e, exc), "Expected %s, got %s" % (exc, type(e))) + + def test_nk_deserialize(self): + """ + Test for ticket #13030 - Python based parser version + natural keys deserialize with fk to inheriting model + """ + management.call_command( + 'loaddata', + 'model-inheritance.json', + verbosity=0, + commit=False + ) + management.call_command( + 'loaddata', + 'nk-inheritance.json', + verbosity=0, + commit=False + ) + self.assertEqual( + NKChild.objects.get(pk=1).data, + 'apple' + ) + + self.assertEqual( + RefToNKChild.objects.get(pk=1).nk_fk.data, + 'apple' + ) + + def test_nk_deserialize_xml(self): + """ + Test for ticket #13030 - XML version + natural keys deserialize with fk to inheriting model + """ + management.call_command( + 'loaddata', + 'model-inheritance.json', + verbosity=0, + commit=False + ) + management.call_command( + 'loaddata', + 'nk-inheritance.json', + verbosity=0, + commit=False + ) + management.call_command( + 'loaddata', + 'nk-inheritance2.xml', + verbosity=0, + commit=False + ) + self.assertEqual( + NKChild.objects.get(pk=2).data, + 'banana' + ) + self.assertEqual( + RefToNKChild.objects.get(pk=2).nk_fk.data, + 'apple' + ) + + def test_nk_on_serialize(self): + """ + Check that natural key requirements are taken into account + when serializing models + """ + management.call_command( + 'loaddata', + 'forward_ref_lookup.json', + verbosity=0, + commit=False + ) + + stdout = StringIO() + management.call_command( + 'dumpdata', + 'fixtures_regress.book', + 'fixtures_regress.person', + 'fixtures_regress.store', + verbosity=0, + format='json', + use_natural_keys=True, + stdout=stdout, + ) + self.assertEqual( + stdout.getvalue(), + """[{"pk": 2, "model": "fixtures_regress.store", "fields": {"name": "Amazon"}}, {"pk": 3, "model": "fixtures_regress.store", "fields": {"name": "Borders"}}, {"pk": 4, "model": "fixtures_regress.person", "fields": {"name": "Neal Stephenson"}}, {"pk": 1, "model": "fixtures_regress.book", "fields": {"stores": [["Amazon"], ["Borders"]], "name": "Cryptonomicon", "author": ["Neal Stephenson"]}}]""" + ) + + def test_dependency_sorting(self): + """ + Now lets check the dependency sorting explicitly + It doesn't matter what order you mention the models + Store *must* be serialized before then Person, and both + must be serialized before Book. + """ + sorted_deps = sort_dependencies( + [('fixtures_regress', [Book, Person, Store])] + ) + self.assertEqual( + sorted_deps, + [Store, Person, Book] + ) + + def test_dependency_sorting_2(self): + sorted_deps = sort_dependencies( + [('fixtures_regress', [Book, Store, Person])] + ) + self.assertEqual( + sorted_deps, + [Store, Person, Book] + ) + + def test_dependency_sorting_3(self): + sorted_deps = sort_dependencies( + [('fixtures_regress', [Store, Book, Person])] + ) + self.assertEqual( + sorted_deps, + [Store, Person, Book] + ) + + def test_dependency_sorting_4(self): + sorted_deps = sort_dependencies( + [('fixtures_regress', [Store, Person, Book])] + ) + self.assertEqual( + sorted_deps, + [Store, Person, Book] + ) + + def test_dependency_sorting_5(self): + sorted_deps = sort_dependencies( + [('fixtures_regress', [Person, Book, Store])] + ) + self.assertEqual( + sorted_deps, + [Store, Person, Book] + ) + + def test_dependency_sorting_6(self): + sorted_deps = sort_dependencies( + [('fixtures_regress', [Person, Store, Book])] + ) + self.assertEqual( + sorted_deps, + [Store, Person, Book] + ) + + def test_dependency_sorting_dangling(self): + sorted_deps = sort_dependencies( + [('fixtures_regress', [Person, Circle1, Store, Book])] + ) + self.assertEqual( + sorted_deps, + [Circle1, Store, Person, Book] + ) + + def test_dependency_sorting_tight_circular(self): + self.assertRaisesMessage( + CommandError, + """Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list.""", + sort_dependencies, + [('fixtures_regress', [Person, Circle2, Circle1, Store, Book])], + ) + + def test_dependency_sorting_tight_circular_2(self): + self.assertRaisesMessage( + CommandError, + """Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2 in serialized app list.""", + sort_dependencies, + [('fixtures_regress', [Circle1, Book, Circle2])], + ) + + def test_dependency_self_referential(self): + self.assertRaisesMessage( + CommandError, + """Can't resolve dependencies for fixtures_regress.Circle3 in serialized app list.""", + sort_dependencies, + [('fixtures_regress', [Book, Circle3])], + ) + + def test_dependency_sorting_long(self): + self.assertRaisesMessage( + CommandError, + """Can't resolve dependencies for fixtures_regress.Circle1, fixtures_regress.Circle2, fixtures_regress.Circle3 in serialized app list.""", + sort_dependencies, + [('fixtures_regress', [Person, Circle2, Circle1, Circle3, Store, Book])], + ) + + def test_dependency_sorting_normal(self): + sorted_deps = sort_dependencies( + [('fixtures_regress', [Person, ExternalDependency, Book])] + ) + self.assertEqual( + sorted_deps, + [Person, Book, ExternalDependency] + ) + + def test_normal_pk(self): + """ + Check that normal primary keys still work + on a model with natural key capabilities + """ + management.call_command( + 'loaddata', + 'non_natural_1.json', + verbosity=0, + commit=False + ) + management.call_command( + 'loaddata', + 'forward_ref_lookup.json', + verbosity=0, + commit=False + ) + management.call_command( + 'loaddata', + 'non_natural_2.xml', + verbosity=0, + commit=False + ) + books = Book.objects.all() + self.assertEqual( + books.__repr__(), + """[, , ]""" + ) From a40380f37aab87a2bbde205356c7f3f63efe8e6c Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 28 Sep 2010 15:09:06 +0000 Subject: [PATCH 230/902] [1.2.X] Corrected the spelling of David Brenneman's name in the AUTHORS file. Apologies, David. Backport of r13956 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13957 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index e8584324bc34..52d3d3ad26dd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -82,7 +82,7 @@ answer newbie questions, and generally made Django that much better: Matías Bordese Sean Brant Andrew Brehaut - David Brennenman + David Brenneman brut.alll@gmail.com bthomas btoll@bestweb.net From f27d85b8f4fa16f0d98d4dd56a6a77575ac0e952 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 29 Sep 2010 01:29:10 +0000 Subject: [PATCH 231/902] [1.2.X] Modified a fixtures_regress test case to make it more robust to database ordering. Backport of r13958 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13959 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/fixtures_regress/tests.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py index 4b5475e7301c..7f9890a2ccdb 100644 --- a/tests/regressiontests/fixtures_regress/tests.py +++ b/tests/regressiontests/fixtures_regress/tests.py @@ -304,10 +304,17 @@ def test_dumpdata_uses_default_manager(self): format='json', stdout=stdout ) - self.assertEqual( - stdout.getvalue(), - """[{"pk": 1, "model": "fixtures_regress.animal", "fields": {"count": 3, "weight": 1.2, "name": "Lion", "latin_name": "Panthera leo"}}, {"pk": 10, "model": "fixtures_regress.animal", "fields": {"count": 42, "weight": 1.2, "name": "Emu", "latin_name": "Dromaius novaehollandiae"}}, {"pk": 11, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.2000000000000002, "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}]""" - ) + + # Output order isn't guaranteed, so check for parts + data = stdout.getvalue() + lion_json = '{"pk": 1, "model": "fixtures_regress.animal", "fields": {"count": 3, "weight": 1.2, "name": "Lion", "latin_name": "Panthera leo"}}' + emu_json = '{"pk": 10, "model": "fixtures_regress.animal", "fields": {"count": 42, "weight": 1.2, "name": "Emu", "latin_name": "Dromaius novaehollandiae"}}' + platypus_json = '{"pk": 11, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.2000000000000002, "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}' + + self.assertEqual(len(data), len('[%s]' % ', '.join([lion_json, emu_json, platypus_json]))) + self.assertTrue(lion_json in data) + self.assertTrue(emu_json in data) + self.assertTrue(platypus_json in data) def test_proxy_model_included(self): """ From ed1aa807e22c8c76fd7885153d23544061a2f2ee Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 29 Sep 2010 17:34:26 +0000 Subject: [PATCH 232/902] [1.2.X] Fixed #14182 - documented how to modify upload handlers when using CsrfViewMiddleware Thanks to dc for the report. Backport of [13960] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13961 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/http/file-uploads.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt index 6b0a4d57228d..c505cac7ee4f 100644 --- a/docs/topics/http/file-uploads.txt +++ b/docs/topics/http/file-uploads.txt @@ -270,6 +270,30 @@ list:: Thus, you should always modify uploading handlers as early in your view as possible. + Also, ``request.POST`` is accessed by + :class:`~django.middleware.csrf.CsrfViewMiddleware` which is enabled by + default. This means you will probably need to use + :func:`~django.views.decorators.csrf.csrf_exempt` on your view to allow you + to change the upload handlers. Assuming you do need CSRF protection, you + will then need to use :func:`~django.views.decorators.csrf.csrf_protect` on + the function that actually processes the request. Note that this means that + the handlers may start receiving the file upload before the CSRF checks have + been done. Example code: + + .. code-block:: python + + from django.views.decorators.csrf import csrf_exempt, csrf_protect + + @csrf_exempt + def upload_file_view(request): + request.upload_handlers.insert(0, ProgressBarUploadHandler()) + return _upload_file_view(request) + + @csrf_protect + def _upload_file_view(request): + ... # Process request + + Writing custom upload handlers ------------------------------ From 89e0fde94834da40f9083698227500d1ea0ec2cf Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Wed, 29 Sep 2010 17:58:29 +0000 Subject: [PATCH 233/902] [1,2,X] Additions to the contributing document explaining our new decision-making process. Backport of r13962. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13963 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/contributing.txt | 96 ++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 20 deletions(-) diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index 399e458c2af1..6bf8aa195184 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -812,6 +812,42 @@ repository: Subversion and Trac so that any commit message in that format will automatically post a comment to the appropriate ticket. +Reverting commits +----------------- + +Nobody's perfect; mistakes will be committed. When a mistaken commit is +discovered, please follow these guidelines: + + * Try very hard to ensure that mistakes don't happen. Just because we + have a reversion policy doesn't relax your responsibility to aim for + the highest quality possible. Really: double-check your work before + you commit it in the first place! + + * If possible, have the original author revert his/her own commit. + + * Don't revert another author's changes without permission from the + original author. + + * If the original author can't be reached (within a reasonable amount + of time -- a day or so) and the problem is severe -- crashing bug, + major test failures, etc -- then ask for objections on django-dev + then revert if there are none. + + * If the problem is small (a feature commit after feature freeze, + say), wait it out. + + * If there's a disagreement between the committer and the + reverter-to-be then try to work it out on the `django-developers`_ + mailing list. If an agreement can't be reached then it should + be put to a vote. + + * If the commit introduced a confirmed, disclosed security + vulnerability then the commit may be reverted immediately without + permission from anyone. + + * The release branch maintainer may back out commits to the release + branch without permission if the commit breaks the release branch. + Unit tests ========== @@ -1159,11 +1195,8 @@ file. Then copy the branch's version of the ``django`` directory into .. _path file: http://docs.python.org/library/site.html -Deciding on features -==================== - -Once a feature's been requested and discussed, eventually we'll have a decision -about whether to include the feature or drop it. +How we make decisions +===================== Whenever possible, we strive for a rough consensus. To that end, we'll often have informal votes on `django-developers`_ about a feature. In these votes we @@ -1183,24 +1216,45 @@ Although these votes on django-developers are informal, they'll be taken very seriously. After a suitable voting period, if an obvious consensus arises we'll follow the votes. -However, consensus is not always possible. Tough decisions will be discussed by -all full committers and finally decided by the Benevolent Dictators for Life, -Adrian and Jacob. +However, consensus is not always possible. If consensus cannot be reached, or +if the discussion towards a consensus fizzles out without a concrete decision, +we use a more formal process. + +Any core committer (see below) may call for a formal vote using the same +voting mechanism above. A proposition will be considered carried by the core team +if: + + * There are three "+1" votes from members of the core team. + + * There is no "-1" vote from any member of the core team. + + * The BDFLs haven't stepped in and executed their positive or negative + veto. + +When calling for a vote, the caller should specify a deadline by which +votes must be received. One week is generally suggested as the minimum +amount of time. + +Since this process allows any core committer to veto a proposal, any "-1" +votes (or BDFL vetos) should be accompanied by an explanation that explains +what it would take to convert that "-1" into at least a "+0". + +Whenever possible, these formal votes should be announced and held in +public on the `django-developers`_ mailing list. However, overly sensitive +or contentious issues -- including, most notably, votes on new core +committers -- may be held in private. Commit access ============= Django has two types of committers: -Full committers +Core committers These are people who have a long history of contributions to Django's - codebase, a solid track record of being polite and helpful on the mailing - lists, and a proven desire to dedicate serious time to Django's development. - - The bar is very high for full commit access. It will only be granted by - unanimous approval of all existing full committers, and the decision will err - on the side of rejection. - + codebase, a solid track record of being polite and helpful on the + mailing lists, and a proven desire to dedicate serious time to Django's + development. The bar is high for full commit access. + Partial committers These are people who are "domain experts." They have direct check-in access to the subsystems that fall under their jurisdiction, and they're given a @@ -1208,10 +1262,12 @@ Partial committers is likely to be given to someone who contributes a large subframework to Django and wants to continue to maintain it. - Like full committers, partial commit access is by unanimous approval of all - full committers (and any other partial committers in the same area). - However, the bar is set lower; proven expertise in the area in question is - likely to be sufficient. + Partial commit access is granted by the same process as full + committers. However, the bar is set lower; proven expertise in the area + in question is likely to be sufficient. + +Decisions on new committers will follow the process explained above in `how +we make decisions`_. To request commit access, please contact an existing committer privately. Public requests for commit access are potential flame-war starters, and will be ignored. From ebc084ca351a8d79a26f5239afc1b46a4d245139 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 1 Oct 2010 02:39:25 +0000 Subject: [PATCH 234/902] [1.2.X] Fixed #13876 -- Fixed duplication in docs. Thanks, zerok and timo. Backport from trunk (r13966). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13972 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/request-response.txt | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index bf984f4fa8b3..a944abeddc5d 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -405,16 +405,6 @@ file-like object:: >>> response.write("

        Here's the text of the Web page.

        ") >>> response.write("

        Here's another paragraph.

        ") -You can add and delete headers using dictionary syntax:: - - >>> response = HttpResponse() - >>> response['X-DJANGO'] = "It's the best." - >>> del response['X-PHP'] - >>> response['X-DJANGO'] - "It's the best." - -Note that ``del`` doesn't raise ``KeyError`` if the header doesn't exist. - Passing iterators ~~~~~~~~~~~~~~~~~ @@ -429,10 +419,14 @@ hard-coded strings. If you use this technique, follow these guidelines: Setting headers ~~~~~~~~~~~~~~~ -To set a header in your response, just treat it like a dictionary:: +To set or remove a header in your response, treat it like a dictionary:: >>> response = HttpResponse() >>> response['Cache-Control'] = 'no-cache' + >>> del response['Cache-Control'] + +Note that unlike a dictionary, ``del`` doesn't raise ``KeyError`` if the header +doesn't exist. .. versionadded:: 1.1 From a0cabd766f9cbbf776e3a776a9eb41265502864d Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 1 Oct 2010 02:39:45 +0000 Subject: [PATCH 235/902] [1.2.X] Fixed #13568 -- Fixed the blocktrans tag to not raise a KeyError if the number of variables in the singular and the plural block differ. Thanks, deloide. Backport from trunk (r13967). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13973 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/templatetags/i18n.py | 4 +++- tests/regressiontests/templates/tests.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 10ac900041db..c4dd2e7835b3 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -76,8 +76,10 @@ def render(self, context): if self.plural and self.countervar and self.counter: count = self.counter.resolve(context) context[self.countervar] = count - plural, vars = self.render_token_list(self.plural) + plural, plural_vars = self.render_token_list(self.plural) result = translation.ungettext(singular, plural, count) + if count != 1: + vars = plural_vars else: result = translation.ugettext(singular) # Escape all isolated '%' before substituting in the context. diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index bbbcae30ebb0..9e2d175677d0 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -1116,6 +1116,9 @@ def get_template_tests(self): 'i18n24': ("{% load i18n %}{% trans 'Page not found'|upper %}", {'LANGUAGE_CODE': 'de'}, u'SEITE NICHT GEFUNDEN'), 'i18n25': ('{% load i18n %}{% trans somevar|upper %}', {'somevar': 'Page not found', 'LANGUAGE_CODE': 'de'}, u'SEITE NICHT GEFUNDEN'), + # translation of plural form with extra field in singular form (#13568) + 'i18n26': ('{% load i18n %}{% blocktrans with myextra_field as extra_field count number as counter %}singular {{ extra_field }}{% plural %}plural{% endblocktrans %}', {'number': 1, 'myextra_field': 'test'}, "singular test"), + ### HANDLING OF TEMPLATE_STRING_IF_INVALID ################################### 'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')), From 6d30b2847c6002bda76210045e449b89d1da8d8e Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 1 Oct 2010 02:40:01 +0000 Subject: [PATCH 236/902] [1.2.X] Fixed #14362 -- Made sure all parameters are passed to the ManyToManyRawIdWidget. Thanks, tyron. Backport from trunk (r13970). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13974 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/widgets.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 5832f8225336..5a7ad959d6f3 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -163,9 +163,6 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): A Widget for displaying ManyToMany ids in the "raw_id" interface rather than in a box. """ def render(self, name, value, attrs=None): + if attrs is None: + attrs = {} attrs['class'] = 'vManyToManyRawIdAdminField' if value: value = ','.join([force_unicode(v) for v in value]) diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py index 59d625ba1296..c3c824e4a802 100644 --- a/tests/regressiontests/admin_widgets/models.py +++ b/tests/regressiontests/admin_widgets/models.py @@ -133,6 +133,8 @@ class CarTire(models.Model): >>> w = ManyToManyRawIdWidget(rel) >>> print conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})) Lookup +>>> print conditional_escape(w.render('test', [m1.pk])) + Lookup >>> w._has_changed(None, None) False >>> w._has_changed([], None) From 1429a3dbbad29809ad21947d123a64340d74e099 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 2 Oct 2010 13:14:47 +0000 Subject: [PATCH 238/902] [1.2.X] Corrected a test failure under MySQL. Backport of r13976 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13977 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/fixtures_regress/tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py index 7f9890a2ccdb..3fe026d5a733 100644 --- a/tests/regressiontests/fixtures_regress/tests.py +++ b/tests/regressiontests/fixtures_regress/tests.py @@ -238,15 +238,15 @@ def test_close_connection_after_loaddata(self): ) articles = Article.objects.exclude(id=9) self.assertEqual( - articles.values_list('id', flat=True).__repr__(), - "[1, 2, 3, 4, 5, 6, 7, 8]" + list(articles.values_list('id', flat=True)), + [1, 2, 3, 4, 5, 6, 7, 8] ) # Just for good measure, run the same query again. # Under the influence of ticket #7572, this will # give a different result to the previous call. self.assertEqual( - articles.values_list('id', flat=True).__repr__(), - "[1, 2, 3, 4, 5, 6, 7, 8]" + list(articles.values_list('id', flat=True)), + [1, 2, 3, 4, 5, 6, 7, 8] ) def test_field_value_coerce(self): From 826692568427328400492f899a315b628b18f877 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 2 Oct 2010 13:56:58 +0000 Subject: [PATCH 239/902] [1.2.X] Fixed #10215 -- Ensured that there is parity between enter and leave transaction calls in loaddata when commit=False. The test case for this is the fixtures_regress unittests under MyISAM, which were failing previous to this fix. Thanks to MockSoul for the report. Backport of r13978 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13979 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/commands/loaddata.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 2bbd9c57b01a..b8bb62feca6e 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -118,8 +118,9 @@ def read(self): self.stderr.write( self.style.ERROR("Problem installing fixture '%s': %s is not a known serialization format.\n" % (fixture_name, format))) - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) + if commit: + transaction.rollback(using=using) + transaction.leave_transaction_management(using=using) return if os.path.isabs(fixture_name): @@ -152,8 +153,9 @@ def read(self): fixture.close() self.stderr.write(self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting.\n" % (fixture_name, humanize(fixture_dir)))) - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) + if commit: + transaction.rollback(using=using) + transaction.leave_transaction_management(using=using) return else: fixture_count += 1 @@ -178,8 +180,9 @@ def read(self): except Exception: import traceback fixture.close() - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) + if commit: + transaction.rollback(using=using) + transaction.leave_transaction_management(using=using) if show_traceback: traceback.print_exc() else: @@ -196,8 +199,9 @@ def read(self): self.stderr.write( self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)\n" % (fixture_name))) - transaction.rollback(using=using) - transaction.leave_transaction_management(using=using) + if commit: + transaction.rollback(using=using) + transaction.leave_transaction_management(using=using) return except Exception, e: From cb0018185465c8aa22092fd887cfd4b25e6544ab Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 5 Oct 2010 19:41:48 +0000 Subject: [PATCH 240/902] [1.2.X] Fixed #11358: Don't include private flatpages in sitemap. Thanks dburke and mlavin. Backport of [13734] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13985 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sitemaps/__init__.py | 2 +- django/contrib/sitemaps/tests/basic.py | 24 ++++++++++++++++++++++++ django/contrib/sitemaps/tests/urls.py | 7 ++++++- docs/ref/contrib/sitemaps.txt | 4 ++-- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py index d31a9d7d2e09..eaa7f85de95d 100644 --- a/django/contrib/sitemaps/__init__.py +++ b/django/contrib/sitemaps/__init__.py @@ -79,7 +79,7 @@ class FlatPageSitemap(Sitemap): def items(self): from django.contrib.sites.models import Site current_site = Site.objects.get_current() - return current_site.flatpage_set.all() + return current_site.flatpage_set.filter(registration_required=False) class GenericSitemap(Sitemap): priority = None diff --git a/django/contrib/sitemaps/tests/basic.py b/django/contrib/sitemaps/tests/basic.py index 5dd2aa20101d..ad04db258f41 100644 --- a/django/contrib/sitemaps/tests/basic.py +++ b/django/contrib/sitemaps/tests/basic.py @@ -1,6 +1,7 @@ from datetime import date from django.conf import settings from django.contrib.auth.models import User +from django.contrib.flatpages.models import FlatPage from django.test import TestCase from django.utils.formats import localize from django.utils.translation import activate @@ -51,3 +52,26 @@ def test_generic_sitemap(self): http://example.com/users/testuser/ """) + + def test_flatpage_sitemap(self): + "Basic FlatPage sitemap test" + public = FlatPage.objects.create( + url=u'/public/', + title=u'Public Page', + enable_comments=True, + registration_required=False, + ) + public.sites.add(settings.SITE_ID) + private = FlatPage.objects.create( + url=u'/private/', + title=u'Public Page', + enable_comments=True, + registration_required=True + ) + private.sites.add(settings.SITE_ID) + response = self.client.get('/flatpages/sitemap.xml') + # Public flatpage should be in the sitemap + self.assertContains(response, 'http://example.com%s' % public.url) + # Private flatpage should not be in the sitemap + self.assertNotContains(response, 'http://example.com%s' % private.url) + diff --git a/django/contrib/sitemaps/tests/urls.py b/django/contrib/sitemaps/tests/urls.py index 0fb2a7214e73..6cdba36b0253 100644 --- a/django/contrib/sitemaps/tests/urls.py +++ b/django/contrib/sitemaps/tests/urls.py @@ -1,6 +1,6 @@ from datetime import datetime from django.conf.urls.defaults import * -from django.contrib.sitemaps import Sitemap, GenericSitemap +from django.contrib.sitemaps import Sitemap, GenericSitemap, FlatPageSitemap from django.contrib.auth.models import User class SimpleSitemap(Sitemap): @@ -22,7 +22,12 @@ def items(self): }), } +flatpage_sitemaps = { + 'flatpages': FlatPageSitemap, +} + urlpatterns = patterns('django.contrib.sitemaps.views', (r'^simple/sitemap\.xml$', 'sitemap', {'sitemaps': simple_sitemaps}), (r'^generic/sitemap\.xml$', 'sitemap', {'sitemaps': generic_sitemaps}), + (r'^flatpages/sitemap\.xml$', 'sitemap', {'sitemaps': flatpage_sitemaps}), ) diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 113d4d35314a..7a66cbe9a915 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -215,8 +215,8 @@ The sitemap framework provides a couple convenience classes for common cases: .. class:: FlatPageSitemap The :class:`django.contrib.sitemaps.FlatPageSitemap` class looks at all - :mod:`flatpages ` defined for the current - :setting:`SITE_ID` (see the + publicly visible :mod:`flatpages ` + defined for the current :setting:`SITE_ID` (see the :mod:`sites documentation `) and creates an entry in the sitemap. These entries include only the :attr:`~Sitemap.location` attribute -- not :attr:`~Sitemap.lastmod`, From c7ce5b8929b6dea9e1d0776fdb2bc37686ef9b6b Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 5 Oct 2010 20:27:11 +0000 Subject: [PATCH 241/902] [1.2.X] Fixed #13092 -- Added support for the "in" operator when dealing with context lists. Thanks to clelland for the patch. Backport of [13510] from trunk. Backported in order to support some other tests which need to be backported. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13986 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/utils.py | 6 ++++++ tests/regressiontests/test_client_regress/models.py | 2 ++ 2 files changed, 8 insertions(+) diff --git a/django/test/utils.py b/django/test/utils.py index f38c60fd5077..8ecb5a0e606a 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -36,6 +36,12 @@ def __getitem__(self, key): else: return super(ContextList, self).__getitem__(key) + def __contains__(self, key): + try: + value = self[key] + except KeyError: + return False + return True def instrumented_test_render(self, context): """ diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index 5f23c8ee13a2..b2a7f45cc3a2 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -620,6 +620,7 @@ def test_single_context(self): "Context variables can be retrieved from a single context" response = self.client.get("/test_client_regress/request_data/", data={'foo':'whiz'}) self.assertEqual(response.context.__class__, Context) + self.assertTrue('get-foo' in response.context) self.assertEqual(response.context['get-foo'], 'whiz') self.assertEqual(response.context['request-foo'], 'whiz') self.assertEqual(response.context['data'], 'sausage') @@ -635,6 +636,7 @@ def test_inherited_context(self): response = self.client.get("/test_client_regress/request_data_extended/", data={'foo':'whiz'}) self.assertEqual(response.context.__class__, ContextList) self.assertEqual(len(response.context), 2) + self.assertTrue('get-foo' in response.context) self.assertEqual(response.context['get-foo'], 'whiz') self.assertEqual(response.context['request-foo'], 'whiz') self.assertEqual(response.context['data'], 'bacon') From 12187bfd26fa8205d5a6a12bdf294c0a1fc21a6c Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 5 Oct 2010 20:31:02 +0000 Subject: [PATCH 242/902] [1.2.X] Fixed #14386, #8960, #10235, #10909, #10608, #13845, #14377 - standardize Site/RequestSite usage in various places. Many thanks to gabrielhurley for putting most of this together. Also to bmihelac, arthurk, qingfeng, hvendelbo, petr.pulc@s-cape.cz, Hraban for reports and some initial patches. The patch also contains some whitespace/PEP8 fixes. Backport of [13980] from trunk. Some items could be considered features (i.e. supporting RequestSite in various contrib apps), others are definite bugs, but it was much more robust to backport all these tightly related changes together. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13987 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/forms.py | 6 +- django/contrib/auth/tests/views.py | 6 ++ django/contrib/auth/views.py | 34 +++++---- django/contrib/contenttypes/tests.py | 96 ++++++++++++++++---------- django/contrib/contenttypes/views.py | 42 +++++------ django/contrib/gis/sitemaps/views.py | 4 +- django/contrib/sitemaps/__init__.py | 12 ++-- django/contrib/sitemaps/tests/basic.py | 18 ++++- django/contrib/sitemaps/views.py | 6 +- django/contrib/sites/models.py | 18 +++++ django/contrib/sites/tests.py | 85 +++++++++++++++-------- django/contrib/syndication/views.py | 7 +- docs/ref/contrib/sites.txt | 12 +++- 13 files changed, 223 insertions(+), 123 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index d20c472495c9..7670c85542b0 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -1,7 +1,7 @@ from django.contrib.auth.models import User from django.contrib.auth import authenticate from django.contrib.auth.tokens import default_token_generator -from django.contrib.sites.models import Site +from django.contrib.sites.models import get_current_site from django.template import Context, loader from django import forms from django.utils.translation import ugettext_lazy as _ @@ -117,14 +117,14 @@ def clean_email(self): return email def save(self, domain_override=None, email_template_name='registration/password_reset_email.html', - use_https=False, token_generator=default_token_generator): + use_https=False, token_generator=default_token_generator, request=None): """ Generates a one-use only link for resetting password and sends to the user """ from django.core.mail import send_mail for user in self.users_cache: if not domain_override: - current_site = Site.objects.get_current() + current_site = get_current_site(request) site_name = current_site.name domain = current_site.domain else: diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index d894e6dafd1e..4b32fec4e1cf 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -249,6 +249,12 @@ def test_logout_default(self): self.assert_('Logged out' in response.content) self.confirm_logged_out() + def test_14377(self): + # Bug 14377 + self.login() + response = self.client.get('/logout/') + self.assertTrue('site' in response.context) + def test_logout_with_next_page_specified(self): "Logout with next_page option given redirects to specified resource" self.login() diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index b2e875a8698c..197799eb9e3b 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -10,7 +10,7 @@ from django.views.decorators.csrf import csrf_protect from django.core.urlresolvers import reverse from django.shortcuts import render_to_response, get_object_or_404 -from django.contrib.sites.models import Site, RequestSite +from django.contrib.sites.models import get_current_site from django.http import HttpResponseRedirect, Http404 from django.template import RequestContext from django.utils.http import urlquote, base36_to_int @@ -26,21 +26,21 @@ def login(request, template_name='registration/login.html', """Displays the login form and handles the login action.""" redirect_to = request.REQUEST.get(redirect_field_name, '') - + if request.method == "POST": form = authentication_form(data=request.POST) if form.is_valid(): # Light security check -- make sure redirect_to isn't garbage. if not redirect_to or ' ' in redirect_to: redirect_to = settings.LOGIN_REDIRECT_URL - - # Heavier security check -- redirects to http://example.com should - # not be allowed, but things like /view/?param=http://example.com + + # Heavier security check -- redirects to http://example.com should + # not be allowed, but things like /view/?param=http://example.com # should be allowed. This regex checks if there is a '//' *before* a # question mark. elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to): redirect_to = settings.LOGIN_REDIRECT_URL - + # Okay, security checks complete. Log the user in. auth_login(request, form.get_user()) @@ -51,14 +51,11 @@ def login(request, template_name='registration/login.html', else: form = authentication_form(request) - + request.session.set_test_cookie() - - if Site._meta.installed: - current_site = Site.objects.get_current() - else: - current_site = RequestSite(request) - + + current_site = get_current_site(request) + return render_to_response(template_name, { 'form': form, redirect_field_name: redirect_to, @@ -75,7 +72,10 @@ def logout(request, next_page=None, template_name='registration/logged_out.html' if redirect_to: return HttpResponseRedirect(redirect_to) else: + current_site = get_current_site(request) return render_to_response(template_name, { + 'site': current_site, + 'site_name': current_site.name, 'title': _('Logged out') }, context_instance=RequestContext(request)) else: @@ -97,7 +97,7 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N # 4 views for password reset: # - password_reset sends the mail # - password_reset_done shows a success message for the above -# - password_reset_confirm checks the link the user clicked and +# - password_reset_confirm checks the link the user clicked and # prompts for a new password # - password_reset_complete shows a success message for the above @@ -114,12 +114,10 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas opts = {} opts['use_https'] = request.is_secure() opts['token_generator'] = token_generator + opts['email_template_name'] = email_template_name + opts['request'] = request if is_admin_site: opts['domain_override'] = request.META['HTTP_HOST'] - else: - opts['email_template_name'] = email_template_name - if not Site._meta.installed: - opts['domain_override'] = RequestSite(request).domain form.save(**opts) return HttpResponseRedirect(post_reset_redirect) else: diff --git a/django/contrib/contenttypes/tests.py b/django/contrib/contenttypes/tests.py index 148edfcbbb54..a846b077909e 100644 --- a/django/contrib/contenttypes/tests.py +++ b/django/contrib/contenttypes/tests.py @@ -1,47 +1,69 @@ -""" -Make sure that the content type cache (see ContentTypeManager) works correctly. -Lookups for a particular content type -- by model or by ID -- should hit the -database only on the first lookup. +from django import db +from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.contrib.sites.models import Site +from django.contrib.contenttypes.views import shortcut +from django.core.exceptions import ObjectDoesNotExist +from django.http import HttpRequest +from django.test import TestCase -First, let's make sure we're dealing with a blank slate (and that DEBUG is on so -that queries get logged):: - >>> from django.conf import settings - >>> settings.DEBUG = True +class ContentTypesTests(TestCase): - >>> from django.contrib.contenttypes.models import ContentType - >>> ContentType.objects.clear_cache() + def setUp(self): + # First, let's make sure we're dealing with a blank slate (and that + # DEBUG is on so that queries get logged) + self.old_DEBUG = settings.DEBUG + self.old_Site_meta_installed = Site._meta.installed + settings.DEBUG = True + ContentType.objects.clear_cache() + db.reset_queries() - >>> from django import db - >>> db.reset_queries() - -At this point, a lookup for a ContentType should hit the DB:: + def tearDown(self): + settings.DEBUG = self.old_DEBUG + Site._meta.installed = self.old_Site_meta_installed - >>> ContentType.objects.get_for_model(ContentType) - - - >>> len(db.connection.queries) - 1 + def test_lookup_cache(self): + """ + Make sure that the content type cache (see ContentTypeManager) + works correctly. Lookups for a particular content type -- by model or + by ID -- should hit the database only on the first lookup. + """ -A second hit, though, won't hit the DB, nor will a lookup by ID:: + # At this point, a lookup for a ContentType should hit the DB + ContentType.objects.get_for_model(ContentType) + self.assertEqual(1, len(db.connection.queries)) - >>> ct = ContentType.objects.get_for_model(ContentType) - >>> len(db.connection.queries) - 1 - >>> ContentType.objects.get_for_id(ct.id) - - >>> len(db.connection.queries) - 1 + # A second hit, though, won't hit the DB, nor will a lookup by ID + ct = ContentType.objects.get_for_model(ContentType) + self.assertEqual(1, len(db.connection.queries)) + ContentType.objects.get_for_id(ct.id) + self.assertEqual(1, len(db.connection.queries)) -Once we clear the cache, another lookup will again hit the DB:: + # Once we clear the cache, another lookup will again hit the DB + ContentType.objects.clear_cache() + ContentType.objects.get_for_model(ContentType) + len(db.connection.queries) + self.assertEqual(2, len(db.connection.queries)) - >>> ContentType.objects.clear_cache() - >>> ContentType.objects.get_for_model(ContentType) - - >>> len(db.connection.queries) - 2 + def test_shortcut_view(self): + """ + Check that the shortcut view (used for the admin "view on site" + functionality) returns a complete URL regardless of whether the sites + framework is installed + """ -Don't forget to reset DEBUG! - - >>> settings.DEBUG = False -""" \ No newline at end of file + request = HttpRequest() + request.META = { + "SERVER_NAME": "Example.com", + "SERVER_PORT": "80", + } + from django.contrib.auth.models import User + user_ct = ContentType.objects.get_for_model(User) + obj = User.objects.create(username="john") + Site._meta.installed = True + response = shortcut(request, user_ct.id, obj.id) + self.assertEqual("http://example.com/users/john/", response._headers.get("location")[1]) + Site._meta.installed = False + response = shortcut(request, user_ct.id, obj.id) + self.assertEqual("http://Example.com/users/john/", response._headers.get("location")[1]) diff --git a/django/contrib/contenttypes/views.py b/django/contrib/contenttypes/views.py index 26961201cd17..ba82564974f4 100644 --- a/django/contrib/contenttypes/views.py +++ b/django/contrib/contenttypes/views.py @@ -1,6 +1,6 @@ from django import http from django.contrib.contenttypes.models import ContentType -from django.contrib.sites.models import Site +from django.contrib.sites.models import Site, get_current_site from django.core.exceptions import ObjectDoesNotExist def shortcut(request, content_type_id, object_id): @@ -26,35 +26,37 @@ def shortcut(request, content_type_id, object_id): # Otherwise, we need to introspect the object's relationships for a # relation to the Site object object_domain = None - opts = obj._meta - # First, look for an many-to-many relationship to Site. - for field in opts.many_to_many: - if field.rel.to is Site: - try: - # Caveat: In the case of multiple related Sites, this just - # selects the *first* one, which is arbitrary. - object_domain = getattr(obj, field.name).all()[0].domain - except IndexError: - pass - if object_domain is not None: - break + if Site._meta.installed: + opts = obj._meta - # Next, look for a many-to-one relationship to Site. - if object_domain is None: - for field in obj._meta.fields: - if field.rel and field.rel.to is Site: + # First, look for an many-to-many relationship to Site. + for field in opts.many_to_many: + if field.rel.to is Site: try: - object_domain = getattr(obj, field.name).domain - except Site.DoesNotExist: + # Caveat: In the case of multiple related Sites, this just + # selects the *first* one, which is arbitrary. + object_domain = getattr(obj, field.name).all()[0].domain + except IndexError: pass if object_domain is not None: break + # Next, look for a many-to-one relationship to Site. + if object_domain is None: + for field in obj._meta.fields: + if field.rel and field.rel.to is Site: + try: + object_domain = getattr(obj, field.name).domain + except Site.DoesNotExist: + pass + if object_domain is not None: + break + # Fall back to the current site (if possible). if object_domain is None: try: - object_domain = Site.objects.get_current().domain + object_domain = get_current_site(request).domain except Site.DoesNotExist: pass diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index f3c1da24ed8c..c7bd22e5d88e 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -1,6 +1,6 @@ from django.http import HttpResponse, Http404 from django.template import loader -from django.contrib.sites.models import Site +from django.contrib.sites.models import get_current_site from django.core import urlresolvers from django.core.paginator import EmptyPage, PageNotAnInteger from django.contrib.gis.db.models.fields import GeometryField @@ -15,7 +15,7 @@ def index(request, sitemaps): This view generates a sitemap index that uses the proper view for resolving geographic section sitemap URLs. """ - current_site = Site.objects.get_current() + current_site = get_current_site(request) sites = [] protocol = request.is_secure() and 'https' or 'http' for section, site in sitemaps.items(): diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py index eaa7f85de95d..114a791572d6 100644 --- a/django/contrib/sitemaps/__init__.py +++ b/django/contrib/sitemaps/__init__.py @@ -1,3 +1,4 @@ +from django.contrib.sites.models import get_current_site from django.core import urlresolvers, paginator import urllib @@ -60,8 +61,7 @@ def _get_paginator(self): paginator = property(_get_paginator) def get_urls(self, page=1): - from django.contrib.sites.models import Site - current_site = Site.objects.get_current() + current_site = get_current_site(self.request) urls = [] for item in self.paginator.page(page).object_list: loc = "http://%s%s" % (current_site.domain, self.__get('location', item)) @@ -77,9 +77,11 @@ def get_urls(self, page=1): class FlatPageSitemap(Sitemap): def items(self): - from django.contrib.sites.models import Site - current_site = Site.objects.get_current() - return current_site.flatpage_set.filter(registration_required=False) + current_site = get_current_site(self.request) + if hasattr(current_site, "flatpage_set"): + return current_site.flatpage_set.filter(registration_required=False) + else: + return () class GenericSitemap(Sitemap): priority = None diff --git a/django/contrib/sitemaps/tests/basic.py b/django/contrib/sitemaps/tests/basic.py index ad04db258f41..80f336e50aaf 100644 --- a/django/contrib/sitemaps/tests/basic.py +++ b/django/contrib/sitemaps/tests/basic.py @@ -2,6 +2,7 @@ from django.conf import settings from django.contrib.auth.models import User from django.contrib.flatpages.models import FlatPage +from django.contrib.sites.models import Site from django.test import TestCase from django.utils.formats import localize from django.utils.translation import activate @@ -12,11 +13,13 @@ class SitemapTests(TestCase): def setUp(self): self.old_USE_L10N = settings.USE_L10N + self.old_Site_meta_installed = Site._meta.installed # Create a user that will double as sitemap content User.objects.create_user('testuser', 'test@example.com', 's3krit') def tearDown(self): settings.USE_L10N = self.old_USE_L10N + Site._meta.installed = self.old_Site_meta_installed def test_simple_sitemap(self): "A simple sitemap can be rendered" @@ -66,7 +69,7 @@ def test_flatpage_sitemap(self): url=u'/private/', title=u'Public Page', enable_comments=True, - registration_required=True + registration_required=True ) private.sites.add(settings.SITE_ID) response = self.client.get('/flatpages/sitemap.xml') @@ -75,3 +78,16 @@ def test_flatpage_sitemap(self): # Private flatpage should not be in the sitemap self.assertNotContains(response, 'http://example.com%s' % private.url) + def test_requestsite_sitemap(self): + # Make sure hitting the flatpages sitemap without the sites framework + # installed doesn't raise an exception + Site._meta.installed = False + response = self.client.get('/flatpages/sitemap.xml') + # Retrieve the sitemap. + response = self.client.get('/simple/sitemap.xml') + # Check for all the important bits: + self.assertEquals(response.content, """ + +http://testserver/location/%snever0.5 + +""" % date.today().strftime('%Y-%m-%d')) diff --git a/django/contrib/sitemaps/views.py b/django/contrib/sitemaps/views.py index 7a5fe38a0817..1058cec5014f 100644 --- a/django/contrib/sitemaps/views.py +++ b/django/contrib/sitemaps/views.py @@ -1,15 +1,16 @@ from django.http import HttpResponse, Http404 from django.template import loader -from django.contrib.sites.models import Site +from django.contrib.sites.models import get_current_site from django.core import urlresolvers from django.utils.encoding import smart_str from django.core.paginator import EmptyPage, PageNotAnInteger def index(request, sitemaps): - current_site = Site.objects.get_current() + current_site = get_current_site(request) sites = [] protocol = request.is_secure() and 'https' or 'http' for section, site in sitemaps.items(): + site.request = request if callable(site): pages = site().paginator.num_pages else: @@ -32,6 +33,7 @@ def sitemap(request, sitemaps, section=None): maps = sitemaps.values() page = request.GET.get("p", 1) for site in maps: + site.request = request try: if callable(site): urls.extend(site().get_urls(page)) diff --git a/django/contrib/sites/models.py b/django/contrib/sites/models.py index 9b1697e251ab..fecbff79d802 100644 --- a/django/contrib/sites/models.py +++ b/django/contrib/sites/models.py @@ -1,9 +1,12 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ + SITE_CACHE = {} + class SiteManager(models.Manager): + def get_current(self): """ Returns the current ``Site`` based on the SITE_ID in the @@ -28,7 +31,9 @@ def clear_cache(self): global SITE_CACHE SITE_CACHE = {} + class Site(models.Model): + domain = models.CharField(_('domain name'), max_length=100) name = models.CharField(_('display name'), max_length=50) objects = SiteManager() @@ -56,6 +61,7 @@ def delete(self): except KeyError: pass + class RequestSite(object): """ A class that shares the primary interface of Site (i.e., it has @@ -75,3 +81,15 @@ def save(self, force_insert=False, force_update=False): def delete(self): raise NotImplementedError('RequestSite cannot be deleted.') + + +def get_current_site(request): + """ + Checks if contrib.sites is installed and returns either the current + ``Site`` object or a ``RequestSite`` object based on the request. + """ + if Site._meta.installed: + current_site = Site.objects.get_current() + else: + current_site = RequestSite(request) + return current_site diff --git a/django/contrib/sites/tests.py b/django/contrib/sites/tests.py index e3fa81b9d11a..85cb53c403b0 100644 --- a/django/contrib/sites/tests.py +++ b/django/contrib/sites/tests.py @@ -1,29 +1,56 @@ -""" ->>> from django.contrib.sites.models import Site ->>> from django.conf import settings ->>> Site(id=settings.SITE_ID, domain="example.com", name="example.com").save() - -# Make sure that get_current() does not return a deleted Site object. ->>> s = Site.objects.get_current() ->>> isinstance(s, Site) -True - ->>> s.delete() ->>> Site.objects.get_current() -Traceback (most recent call last): -... -DoesNotExist: Site matching query does not exist. - -# After updating a Site object (e.g. via the admin), we shouldn't return a -# bogus value from the SITE_CACHE. ->>> _ = Site.objects.create(id=settings.SITE_ID, domain="example.com", name="example.com") ->>> site = Site.objects.get_current() ->>> site.name -u"example.com" ->>> s2 = Site.objects.get(id=settings.SITE_ID) ->>> s2.name = "Example site" ->>> s2.save() ->>> site = Site.objects.get_current() ->>> site.name -u"Example site" -""" +from django.conf import settings +from django.contrib.sites.models import Site, RequestSite, get_current_site +from django.core.exceptions import ObjectDoesNotExist +from django.http import HttpRequest +from django.test import TestCase + + +class SitesFrameworkTests(TestCase): + + def setUp(self): + Site(id=settings.SITE_ID, domain="example.com", name="example.com").save() + self.old_Site_meta_installed = Site._meta.installed + Site._meta.installed = True + + def tearDown(self): + Site._meta.installed = self.old_Site_meta_installed + + def test_site_manager(self): + # Make sure that get_current() does not return a deleted Site object. + s = Site.objects.get_current() + self.assert_(isinstance(s, Site)) + s.delete() + self.assertRaises(ObjectDoesNotExist, Site.objects.get_current) + + def test_site_cache(self): + # After updating a Site object (e.g. via the admin), we shouldn't return a + # bogus value from the SITE_CACHE. + site = Site.objects.get_current() + self.assertEqual(u"example.com", site.name) + s2 = Site.objects.get(id=settings.SITE_ID) + s2.name = "Example site" + s2.save() + site = Site.objects.get_current() + self.assertEqual(u"Example site", site.name) + + def test_get_current_site(self): + # Test that the correct Site object is returned + request = HttpRequest() + request.META = { + "SERVER_NAME": "example.com", + "SERVER_PORT": "80", + } + site = get_current_site(request) + self.assert_(isinstance(site, Site)) + self.assertEqual(site.id, settings.SITE_ID) + + # Test that an exception is raised if the sites framework is installed + # but there is no matching Site + site.delete() + self.assertRaises(ObjectDoesNotExist, get_current_site, request) + + # A RequestSite is returned if the sites framework is not installed + Site._meta.installed = False + site = get_current_site(request) + self.assert_(isinstance(site, RequestSite)) + self.assertEqual(site.name, u"example.com") diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index b99f3f9affbb..804a94508f87 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -1,6 +1,6 @@ import datetime from django.conf import settings -from django.contrib.sites.models import Site, RequestSite +from django.contrib.sites.models import get_current_site from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.http import HttpResponse, Http404 from django.template import loader, Template, TemplateDoesNotExist, RequestContext @@ -91,10 +91,7 @@ def get_feed(self, obj, request): Returns a feedgenerator.DefaultFeed object, fully populated, for this feed. Raises FeedDoesNotExist for invalid parameters. """ - if Site._meta.installed: - current_site = Site.objects.get_current() - else: - current_site = RequestSite(request) + current_site = get_current_site(request) link = self.__get_dynamic_attr('link', obj) link = add_domain(current_site.domain, link) diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 516233b59624..54ca668300cc 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -105,7 +105,7 @@ This has the same benefits as described in the last section. Hooking into the current site from views ---------------------------------------- -On a lower level, you can use the sites framework in your Django views to do +You can use the sites framework in your Django views to do particular things based on the site in which the view is being called. For example:: @@ -146,6 +146,16 @@ the :class:`~django.contrib.sites.models.Site` model's manager has a else: # Do something else. +.. versionchanged:: 1.3 + +For code which relies on getting the current domain but cannot be certain +that the sites framework will be installed for any given project, there is a +utility function :func:`~django.contrib.sites.models.get_current_site` that +takes a request object as an argument and returns either a Site instance (if +the sites framework is installed) or a RequestSite instance (if it is not). +This allows loose coupling with the sites framework and provides a usable +fallback for cases where it is not installed. + Getting the current domain for display -------------------------------------- From 01b12a6fab25a9891085acc718d52e033188d7e7 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 7 Oct 2010 22:33:02 +0000 Subject: [PATCH 243/902] [1.2.X] Fixed #5425 - Incorrect plurals in admin pagination template. Thanks to Petr Marhoun for the report, and mk for the patch. Backport of [13998] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13999 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/templates/admin/pagination.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/admin/templates/admin/pagination.html b/django/contrib/admin/templates/admin/pagination.html index 358813290c0a..5a0f65a93f54 100644 --- a/django/contrib/admin/templates/admin/pagination.html +++ b/django/contrib/admin/templates/admin/pagination.html @@ -6,7 +6,7 @@ {% paginator_number cl i %} {% endfor %} {% endif %} -{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %} +{% blocktrans with cl.opts.verbose_name as verbose_name and cl.opts.verbose_name_plural as verbose_name_plural count cl.result_count as count %}{{ count }} {{ verbose_name }}{% plural %}{{ count }} {{ verbose_name_plural }}{% endblocktrans %} {% if show_all_url %}  {% trans 'Show all' %}{% endif %} {% if cl.formset and cl.result_count %}{% endif %}

        From cf1d9f4b2cb30e8eb29b29714df592a31ddedcfa Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 7 Oct 2010 23:49:31 +0000 Subject: [PATCH 244/902] [1.2.X] Fixed #14430 - Test failure on Windows with get_image_dimensions since [13715] Thanks to gabrielhurley for report and patch. Backport of [14001] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14002 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/file_storage/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index d59fe9c30757..46411aca8759 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -375,7 +375,7 @@ def test_multiple_calls(self): """ from django.core.files.images import ImageFile img_path = os.path.join(os.path.dirname(__file__), "test.png") - image = ImageFile(open(img_path)) + image = ImageFile(open(img_path, 'rb')) image_pil = Image.open(img_path) size_1, size_2 = get_image_dimensions(image), get_image_dimensions(image) self.assertEqual(image_pil.size, size_1) From 0a52a04a070fd46d50c2a0385349ef5f96d6dc58 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 13:53:25 +0000 Subject: [PATCH 245/902] Backport of r14003 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14004 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/tests/templates/registration/logged_out.html | 1 + .../auth/tests/templates/registration/password_change_form.html | 1 + 2 files changed, 2 insertions(+) create mode 100644 django/contrib/auth/tests/templates/registration/logged_out.html create mode 100644 django/contrib/auth/tests/templates/registration/password_change_form.html diff --git a/django/contrib/auth/tests/templates/registration/logged_out.html b/django/contrib/auth/tests/templates/registration/logged_out.html new file mode 100644 index 000000000000..d69065375cbc --- /dev/null +++ b/django/contrib/auth/tests/templates/registration/logged_out.html @@ -0,0 +1 @@ +Logged out \ No newline at end of file diff --git a/django/contrib/auth/tests/templates/registration/password_change_form.html b/django/contrib/auth/tests/templates/registration/password_change_form.html new file mode 100644 index 000000000000..d96011199234 --- /dev/null +++ b/django/contrib/auth/tests/templates/registration/password_change_form.html @@ -0,0 +1 @@ +{{ form }} \ No newline at end of file From 5b1e21e2006ec0b2b5484ca92e429fc457e9eb05 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 14:16:29 +0000 Subject: [PATCH 246/902] [1.2.X] Fixed #14381 -- Clarified exception handling when instantiating Routers. Thanks to dauerbaustelle for the suggestion and patch. Backport of r14005 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14008 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/django/db/utils.py b/django/db/utils.py index 00b3568cb065..7c3e4137266a 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -111,9 +111,11 @@ def __init__(self, routers): except ImportError, e: raise ImproperlyConfigured('Error importing database router %s: "%s"' % (klass_name, e)) try: - router = getattr(module, klass_name)() + router_class = getattr(module, klass_name) except AttributeError: raise ImproperlyConfigured('Module "%s" does not define a database router name "%s"' % (module, klass_name)) + else: + router = router_class() else: router = r self.routers.append(router) From 36167517aa6e08cfcaf1ed7c8fb8cb1dd53938d8 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 14:16:50 +0000 Subject: [PATCH 247/902] [1.2.X] Fixed #14221 -- Cleaned up some text in the GIS tutorial. Thanks to Grant for the report. Backport of r14006 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14009 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/gis/tutorial.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 3a56c2e7c0f8..e9599866ff4b 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -25,10 +25,10 @@ yourself with basic Django concepts. please consult the :ref:`installation documentation ` for more details. -This tutorial is going to guide you through guide the user 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. [#]_ +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. [#]_ .. note:: From 0d4d924ca5cae8aadb5f5973fe4b26c9c7fc7a0f Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 14:17:13 +0000 Subject: [PATCH 248/902] [1.2.X] Fixed #13218 -- Ensure that syndicated content served over HTTPS uses https:// links by default. Thanks to schaefer for the report, and Ben Firshman for the patch. Backport of r14007 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14010 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/syndication/views.py | 23 ++++++++++++++++------ tests/regressiontests/syndication/tests.py | 23 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/django/contrib/syndication/views.py b/django/contrib/syndication/views.py index 804a94508f87..d942b2f9dcf0 100644 --- a/django/contrib/syndication/views.py +++ b/django/contrib/syndication/views.py @@ -8,13 +8,17 @@ from django.utils.encoding import force_unicode, iri_to_uri, smart_unicode from django.utils.html import escape -def add_domain(domain, url): +def add_domain(domain, url, secure=False): if not (url.startswith('http://') or url.startswith('https://') or url.startswith('mailto:')): # 'url' must already be ASCII and URL-quoted, so no need for encoding # conversions here. - url = iri_to_uri(u'http://%s%s' % (domain, url)) + if secure: + protocol = 'https' + else: + protocol = 'http' + url = iri_to_uri(u'%s://%s%s' % (protocol, domain, url)) return url class FeedDoesNotExist(ObjectDoesNotExist): @@ -94,7 +98,7 @@ def get_feed(self, obj, request): current_site = get_current_site(request) link = self.__get_dynamic_attr('link', obj) - link = add_domain(current_site.domain, link) + link = add_domain(current_site.domain, link, request.is_secure()) feed = self.feed_type( title = self.__get_dynamic_attr('title', obj), @@ -102,8 +106,11 @@ def get_feed(self, obj, request): link = link, description = self.__get_dynamic_attr('description', obj), language = settings.LANGUAGE_CODE.decode(), - feed_url = add_domain(current_site.domain, - self.__get_dynamic_attr('feed_url', obj) or request.path), + feed_url = add_domain( + current_site.domain, + self.__get_dynamic_attr('feed_url', obj) or request.path, + request.is_secure(), + ), author_name = self.__get_dynamic_attr('author_name', obj), author_link = self.__get_dynamic_attr('author_link', obj), author_email = self.__get_dynamic_attr('author_email', obj), @@ -137,7 +144,11 @@ def get_feed(self, obj, request): description = description_tmp.render(RequestContext(request, {'obj': item, 'site': current_site})) else: description = self.__get_dynamic_attr('item_description', item) - link = add_domain(current_site.domain, self.__get_dynamic_attr('item_link', item)) + link = add_domain( + current_site.domain, + self.__get_dynamic_attr('item_link', item), + request.is_secure(), + ) enc = None enc_url = self.__get_dynamic_attr('item_enclosure_url', item) if enc_url: diff --git a/tests/regressiontests/syndication/tests.py b/tests/regressiontests/syndication/tests.py index 79116570795f..76a6c88bc13c 100644 --- a/tests/regressiontests/syndication/tests.py +++ b/tests/regressiontests/syndication/tests.py @@ -236,6 +236,25 @@ def test_feed_url(self): if link.getAttribute('rel') == 'self': self.assertEqual(link.getAttribute('href'), 'http://example.com/customfeedurl/') + def test_secure_urls(self): + """ + Test URLs are prefixed with https:// when feed is requested over HTTPS. + """ + response = self.client.get('/syndication/rss2/', **{ + 'wsgi.url_scheme': 'https', + }) + doc = minidom.parseString(response.content) + chan = doc.getElementsByTagName('channel')[0] + self.assertEqual( + chan.getElementsByTagName('link')[0].firstChild.wholeText[0:5], + 'https' + ) + atom_link = chan.getElementsByTagName('atom:link')[0] + self.assertEqual(atom_link.getAttribute('href')[0:5], 'https') + for link in doc.getElementsByTagName('link'): + if link.getAttribute('rel') == 'self': + self.assertEqual(link.getAttribute('href')[0:5], 'https') + def test_item_link_error(self): """ Test that a ImproperlyConfigured is raised if no link could be found @@ -270,6 +289,10 @@ def test_add_domain(self): views.add_domain('example.com', '/foo/?arg=value'), 'http://example.com/foo/?arg=value' ) + self.assertEqual( + views.add_domain('example.com', '/foo/?arg=value', True), + 'https://example.com/foo/?arg=value' + ) self.assertEqual( views.add_domain('example.com', 'http://djangoproject.com/doc/'), 'http://djangoproject.com/doc/' From 83f762e3ac8a531eec0886d0f10d38c86dcde715 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 8 Oct 2010 15:43:20 +0000 Subject: [PATCH 249/902] Fixed #13188 -- Moved date format documentation from the now template tag to the date filter. Thanks, dwillis and timo. Backport from trunk (r14013). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14021 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/templates/builtins.txt | 159 ++++++++++++++++---------------- 1 file changed, 81 insertions(+), 78 deletions(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index a20ab170c08f..ff6d88f38fe7 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -644,75 +644,12 @@ See :doc:`Custom tag and filter libraries ` for mor now ~~~ -Display the date, formatted according to the given string. +Display the current date and/or time, according to the given string. -Uses the same format as PHP's ``date()`` function (http://php.net/date) -with some custom extensions. - -Available format strings: - - ================ ======================================== ===================== - Format character Description Example output - ================ ======================================== ===================== - a ``'a.m.'`` or ``'p.m.'`` (Note that ``'a.m.'`` - this is slightly different than PHP's - output, because this includes periods - to match Associated Press style.) - A ``'AM'`` or ``'PM'``. ``'AM'`` - b Month, textual, 3 letters, lowercase. ``'jan'`` - B Not implemented. - c ISO 8601 Format. ``2008-01-02T10:30:00.000123`` - d Day of the month, 2 digits with ``'01'`` to ``'31'`` - leading zeros. - D Day of the week, textual, 3 letters. ``'Fri'`` - f Time, in 12-hour hours and minutes, ``'1'``, ``'1:30'`` - with minutes left off if they're zero. - Proprietary extension. - F Month, textual, long. ``'January'`` - g Hour, 12-hour format without leading ``'1'`` to ``'12'`` - zeros. - G Hour, 24-hour format without leading ``'0'`` to ``'23'`` - zeros. - h Hour, 12-hour format. ``'01'`` to ``'12'`` - H Hour, 24-hour format. ``'00'`` to ``'23'`` - i Minutes. ``'00'`` to ``'59'`` - I Not implemented. - j Day of the month without leading ``'1'`` to ``'31'`` - zeros. - l Day of the week, textual, long. ``'Friday'`` - L Boolean for whether it's a leap year. ``True`` or ``False`` - m Month, 2 digits with leading zeros. ``'01'`` to ``'12'`` - M Month, textual, 3 letters. ``'Jan'`` - n Month without leading zeros. ``'1'`` to ``'12'`` - N Month abbreviation in Associated Press ``'Jan.'``, ``'Feb.'``, ``'March'``, ``'May'`` - style. Proprietary extension. - O Difference to Greenwich time in hours. ``'+0200'`` - P Time, in 12-hour hours, minutes and ``'1 a.m.'``, ``'1:30 p.m.'``, ``'midnight'``, ``'noon'``, ``'12:30 p.m.'`` - 'a.m.'/'p.m.', with minutes left off - if they're zero and the special-case - strings 'midnight' and 'noon' if - appropriate. Proprietary extension. - r RFC 2822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'`` - s Seconds, 2 digits with leading zeros. ``'00'`` to ``'59'`` - S English ordinal suffix for day of the ``'st'``, ``'nd'``, ``'rd'`` or ``'th'`` - month, 2 characters. - t Number of days in the given month. ``28`` to ``31`` - T Time zone of this machine. ``'EST'``, ``'MDT'`` - u Microseconds. ``0`` to ``999999`` - U Seconds since the Unix Epoch - (January 1 1970 00:00:00 UTC). - w Day of the week, digits without ``'0'`` (Sunday) to ``'6'`` (Saturday) - leading zeros. - W ISO-8601 week number of year, with ``1``, ``53`` - weeks starting on Monday. - y Year, 2 digits. ``'99'`` - Y Year, 4 digits. ``'1999'`` - z Day of the year. ``0`` to ``365`` - Z Time zone offset in seconds. The ``-43200`` to ``43200`` - offset for timezones west of UTC is - always negative, and for those east of - UTC is always positive. - ================ ======================================== ===================== +Given format can be one of the predefined ones ``DATE_FORMAT``, +``DATETIME_FORMAT``, ``SHORT_DATE_FORMAT`` or ``SHORT_DATETIME_FORMAT``, +or a custom format, same as the :tfilter:`date` filter. Note that predefined +formats may vary depending on the current locale. Example:: @@ -727,10 +664,6 @@ escaped, because it's not a format character:: This would display as "It is the 4th of September". -.. versionadded:: 1.2 - -The ``c`` and ``u`` format specification characters were added in Django 1.2. - .. templatetag:: regroup regroup @@ -1138,10 +1071,77 @@ date Formats a date according to the given format. -Given format can be one of the predefined ones ``DATE_FORMAT``, -``DATETIME_FORMAT``, ``SHORT_DATE_FORMAT`` or ``SHORT_DATETIME_FORMAT``, -or a custom format, same as the :ttag:`now` tag. Note that predefined formats -may vary depending on the current locale. +Uses the same format as PHP's ``date()`` function (http://php.net/date) +with some custom extensions. + +Available format strings: + + ================ ======================================== ===================== + Format character Description Example output + ================ ======================================== ===================== + a ``'a.m.'`` or ``'p.m.'`` (Note that ``'a.m.'`` + this is slightly different than PHP's + output, because this includes periods + to match Associated Press style.) + A ``'AM'`` or ``'PM'``. ``'AM'`` + b Month, textual, 3 letters, lowercase. ``'jan'`` + B Not implemented. + c ISO 8601 Format. ``2008-01-02T10:30:00.000123`` + d Day of the month, 2 digits with ``'01'`` to ``'31'`` + leading zeros. + D Day of the week, textual, 3 letters. ``'Fri'`` + f Time, in 12-hour hours and minutes, ``'1'``, ``'1:30'`` + with minutes left off if they're zero. + Proprietary extension. + F Month, textual, long. ``'January'`` + g Hour, 12-hour format without leading ``'1'`` to ``'12'`` + zeros. + G Hour, 24-hour format without leading ``'0'`` to ``'23'`` + zeros. + h Hour, 12-hour format. ``'01'`` to ``'12'`` + H Hour, 24-hour format. ``'00'`` to ``'23'`` + i Minutes. ``'00'`` to ``'59'`` + I Not implemented. + j Day of the month without leading ``'1'`` to ``'31'`` + zeros. + l Day of the week, textual, long. ``'Friday'`` + L Boolean for whether it's a leap year. ``True`` or ``False`` + m Month, 2 digits with leading zeros. ``'01'`` to ``'12'`` + M Month, textual, 3 letters. ``'Jan'`` + n Month without leading zeros. ``'1'`` to ``'12'`` + N Month abbreviation in Associated Press ``'Jan.'``, ``'Feb.'``, ``'March'``, ``'May'`` + style. Proprietary extension. + O Difference to Greenwich time in hours. ``'+0200'`` + P Time, in 12-hour hours, minutes and ``'1 a.m.'``, ``'1:30 p.m.'``, ``'midnight'``, ``'noon'``, ``'12:30 p.m.'`` + 'a.m.'/'p.m.', with minutes left off + if they're zero and the special-case + strings 'midnight' and 'noon' if + appropriate. Proprietary extension. + r RFC 2822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'`` + s Seconds, 2 digits with leading zeros. ``'00'`` to ``'59'`` + S English ordinal suffix for day of the ``'st'``, ``'nd'``, ``'rd'`` or ``'th'`` + month, 2 characters. + t Number of days in the given month. ``28`` to ``31`` + T Time zone of this machine. ``'EST'``, ``'MDT'`` + u Microseconds. ``0`` to ``999999`` + U Seconds since the Unix Epoch + (January 1 1970 00:00:00 UTC). + w Day of the week, digits without ``'0'`` (Sunday) to ``'6'`` (Saturday) + leading zeros. + W ISO-8601 week number of year, with ``1``, ``53`` + weeks starting on Monday. + y Year, 2 digits. ``'99'`` + Y Year, 4 digits. ``'1999'`` + z Day of the year. ``0`` to ``365`` + Z Time zone offset in seconds. The ``-43200`` to ``43200`` + offset for timezones west of UTC is + always negative, and for those east of + UTC is always positive. + ================ ======================================== ===================== + +.. versionadded:: 1.2 + +The ``c`` and ``u`` format specification characters were added in Django 1.2. For example:: @@ -1151,7 +1151,10 @@ If ``value`` is a ``datetime`` object (e.g., the result of ``datetime.datetime.now()``), the output will be the string ``'Wed 09 Jan 2008'``. -Another example: +Given format can be one of the predefined ones ``DATE_FORMAT``, +``DATETIME_FORMAT``, ``SHORT_DATE_FORMAT`` or ``SHORT_DATETIME_FORMAT``, +or a custom format, same as the :ttag:`now` tag. Note that predefined formats +may vary depending on the current locale. Assuming that :setting:`USE_L10N` is ``True`` and :setting:`LANGUAGE_CODE` is, for example, ``"es"``, then for:: @@ -1788,7 +1791,7 @@ time Formats a time according to the given format. Given format can be the predefined one ``TIME_FORMAT``, or a custom format, -same as the :ttag:`now` tag. Note that the predefined format is locale- +same as the :tfilter:`date` filter. Note that the predefined format is locale- dependant. The time filter will only accept parameters in the format string that relate From 21be09dbb40ba7300b5b1fdb90cf25e8637a4971 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 15:44:28 +0000 Subject: [PATCH 250/902] [1.2.X] Fixed #14225 -- Added a documentation marker (and a 1.2.2 release notes file, required to satisfy Sphinx) for the enable_csrf_checks flag on the test client. Thanks to public@grep.ro for the report. Backport of r14011 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14022 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/releases/1.2.2.txt | 29 +++++++++++++++++++++++++++++ docs/releases/index.txt | 1 + docs/topics/testing.txt | 2 ++ 3 files changed, 32 insertions(+) create mode 100644 docs/releases/1.2.2.txt diff --git a/docs/releases/1.2.2.txt b/docs/releases/1.2.2.txt new file mode 100644 index 000000000000..4ae74abbf9aa --- /dev/null +++ b/docs/releases/1.2.2.txt @@ -0,0 +1,29 @@ +========================== +Django 1.2.2 release notes +========================== + +Welcome to Django 1.2.2! + +This is the second "bugfix" release in the Django 1.2 series, +improving the stability and performance of the Django 1.2 codebase. + +Django 1.2.2 maintains backwards compatibility with Django +1.2.1, but contain a number of fixes and other +improvements. Django 1.2.2 is a recommended upgrade for any +development or deployment currently using or targeting Django 1.2. + +For full details on the new features, backwards incompatibilities, and +deprecated features in the 1.2 branch, see the :doc:`/releases/1.2`. + +One new feature +=============== + +Ordinarily, a point release would not include new features, but in the +case of Django 1.2.2, we have made an exception to this rule. + +In order to test a bug fix that forms part of the 1.2.2 release, it +was necessary to add a feature -- the ``enforce_csrf_checks`` flag -- +to the :mod:`test client `. This flag forces +the test client to perform full CSRF checks on forms. The default +behavior of the test client hasn't changed, but if you want to do +CSRF checks with the test client, it is now possible to do so. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 25c06e8f03ff..20d9469d6c50 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -19,6 +19,7 @@ Final releases .. toctree:: :maxdepth: 1 + 1.2.2 1.2 1.1 release diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 074f94846140..02be4cfe22e4 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -575,6 +575,8 @@ Note a few important things about how the test client works: * By default, the test client will disable any CSRF checks performed by your site. + .. versionadded:: 1.2.2 + If, for some reason, you *want* the test client to perform CSRF checks, you can create an instance of the test client that enforces CSRF checks. To do this, pass in the From bbab38dc503a7d4c3e5c41d73e42fa9d6081e67b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 15:45:07 +0000 Subject: [PATCH 251/902] [1.2.X] Fixed #14421 -- Clarified the english in the i18n documentation. Thanks to Ned Batchelder for the report and patch. Backport of r14015 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14023 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/internationalization.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 879c7739fbf0..5fc347c89d04 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -387,15 +387,14 @@ separate the pieces with ``and``:: This is {{ book_t }} by {{ author_t }} {% endblocktrans %} -This tag is also in charge of handling another functionality: Pluralization. -To make use of it you should: +This tag also provides for pluralization. To use it: - * Designate and bind a counter value by using ``count``, such value will + * Designate and bind a counter value with the name ``count``. This value will be the one used to select the right plural form. * Specify both the singular and plural forms separating them with the - ``{% plural %}`` tag, which appears within ``{% blocktrans %}`` and - ``{% endblocktrans %}``. + ``{% plural %}`` tag within the ``{% blocktrans %}`` and + ``{% endblocktrans %}`` tags. An example:: @@ -414,7 +413,7 @@ A more complex example:: {% endblocktrans %} When you both use the pluralization feature and bind values to local variables -in addition to the counter value, have in mind that the ``blocktrans`` +in addition to the counter value, keep in mind that the ``blocktrans`` construct is internally converted to an ``ungettext`` call. This means the same :ref:`notes regarding ungettext variables ` apply. From 5170a12a880ea470875981bf7e75553bed11ac00 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 15:45:33 +0000 Subject: [PATCH 252/902] [1.2.X] Fixed #14384 -- Updated mod_wsgi docs to match documented best practice. Thanks to monokrome for the report and wogan for the patch. Backport of r14016 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14024 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/deployment/modwsgi.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/howto/deployment/modwsgi.txt b/docs/howto/deployment/modwsgi.txt index fc51f24f80d5..17ba0e383511 100644 --- a/docs/howto/deployment/modwsgi.txt +++ b/docs/howto/deployment/modwsgi.txt @@ -47,7 +47,9 @@ mentioned in the second part of ``WSGIScriptAlias`` and add:: If your project is not on your ``PYTHONPATH`` by default you can add:: - sys.path.append('/usr/local/django') + path = '/usr/local/django' + if path not in sys.path: + sys.path.append(path) just above the final ``import`` line to place your project on the path. Remember to replace 'mysite.settings' with your correct settings file, and '/usr/local/django' From 9ad73135c0e028341cd5dd7574a80b2875587973 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 15:46:03 +0000 Subject: [PATCH 253/902] [1.2.X] Fixed #14383 -- Corrected the capitalization of reStructuredText. Thanks to timo for the patch. Backport of r14017 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14025 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/markup/templatetags/markup.py | 2 +- docs/faq/general.txt | 4 ++-- docs/internals/contributing.txt | 4 ++-- docs/internals/documentation.txt | 2 +- docs/ref/contrib/markup.txt | 6 +++--- docs/ref/templates/builtins.txt | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index 912655f83b78..7cdc04c65383 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -8,7 +8,7 @@ * Markdown, which requires the Python-markdown library from http://www.freewisdom.org/projects/python-markdown - * ReStructuredText, which requires docutils from http://docutils.sf.net/ + * reStructuredText, which requires docutils from http://docutils.sf.net/ """ from django import template diff --git a/docs/faq/general.txt b/docs/faq/general.txt index 1fc0f1882a93..96abad2f4fab 100644 --- a/docs/faq/general.txt +++ b/docs/faq/general.txt @@ -167,14 +167,14 @@ How can I download the Django documentation to read it offline? --------------------------------------------------------------- The Django docs are available in the ``docs`` directory of each Django tarball -release. These docs are in ReST (ReStructured Text) format, and each text file +release. These docs are in reST (reStructuredText) format, and each text file corresponds to a Web page on the official Django site. Because the documentation is `stored in revision control`_, you can browse documentation changes just like you can browse code changes. Technically, the docs on Django's site are generated from the latest development -versions of those ReST documents, so the docs on the Django site may offer more +versions of those reST documents, so the docs on the Django site may offer more information than the docs that come with the latest Django release. .. _stored in revision control: http://code.djangoproject.com/browser/django/trunk/docs diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index 6bf8aa195184..6310562cf68d 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -700,10 +700,10 @@ There's a full page of information about the :doc:`Django documentation system ` that you should read prior to working on the documentation. -Guidelines for ReST files +Guidelines for reST files ------------------------- -These guidelines regulate the format of our ReST documentation: +These guidelines regulate the format of our reST documentation: * In section titles, capitalize only initial words and proper nouns. diff --git a/docs/internals/documentation.txt b/docs/internals/documentation.txt index 63f248d3a972..5185ec7feb08 100644 --- a/docs/internals/documentation.txt +++ b/docs/internals/documentation.txt @@ -20,7 +20,7 @@ Sphinx -- ``easy_install Sphinx`` should do the trick. Then, building the html is easy; just ``make html`` from the ``docs`` directory. -To get started contributing, you'll want to read the `ReStructuredText +To get started contributing, you'll want to read the `reStructuredText Primer`__. After that, you'll want to read about the `Sphinx-specific markup`__ that's used to manage metadata, indexing, and cross-references. diff --git a/docs/ref/contrib/markup.txt b/docs/ref/contrib/markup.txt index f2c43fe25f60..92823132d603 100644 --- a/docs/ref/contrib/markup.txt +++ b/docs/ref/contrib/markup.txt @@ -10,7 +10,7 @@ languages: * ``textile`` -- implements `Textile`_ -- requires `PyTextile`_ * ``markdown`` -- implements `Markdown`_ -- requires `Python-markdown`_ - * ``restructuredtext`` -- implements `ReST (ReStructured Text)`_ + * ``restructuredtext`` -- implements `reST (reStructured Text)`_ -- requires `doc-utils`_ In each case, the filter expects formatted markup as a string and @@ -26,12 +26,12 @@ For more documentation, read the source code in .. _Textile: http://en.wikipedia.org/wiki/Textile_%28markup_language%29 .. _Markdown: http://en.wikipedia.org/wiki/Markdown -.. _ReST (ReStructured Text): http://en.wikipedia.org/wiki/ReStructuredText +.. _reST (reStructured Text): http://en.wikipedia.org/wiki/ReStructuredText .. _PyTextile: http://loopcore.com/python-textile/ .. _Python-markdown: http://www.freewisdom.org/projects/python-markdown .. _doc-utils: http://docutils.sf.net/ -ReStructured Text +reStructured Text ----------------- When using the ``restructuredtext`` markup filter you can define a diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index ff6d88f38fe7..01f39e7b893d 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2095,7 +2095,7 @@ A collection of template filters that implement these common markup languages: * Textile * Markdown - * ReST (ReStructured Text) + * reST (reStructuredText) See the :doc:`markup documentation `. From eae8c651774c613f1039510e560c57abfec87c68 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 15:46:27 +0000 Subject: [PATCH 254/902] [1.2.X] Fixed #14375 -- Corrected the capitalization of MultiValueField. Thanks to Blue for the report. Backport of r14018 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14026 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/forms/fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 0dd9095a779a..5047d4f5edd1 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -775,7 +775,7 @@ Takes one extra required argument: ... ValidationError: [u'Ensure this value has at most 20 characters (it has 28).'] -``MultiValuefield`` +``MultiValueField`` ~~~~~~~~~~~~~~~~~~~ .. class:: MultiValueField(**kwargs) From de3669d2fa16ea7603b76e9e21e575974916ecf4 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 15:46:49 +0000 Subject: [PATCH 255/902] [1.2.X] Fixed #14274 -- Added admonition about using -Wall when you run tests. Thanks to Eric Holscher for the suggestion and draft, and to timo for contributions to the patch. Backport of r14019 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14027 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/testing.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 02be4cfe22e4..1290e4ee008d 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -309,6 +309,15 @@ can press ``Ctrl-C`` a second time and the test run will halt immediately, but not gracefully. No details of the tests run before the interruption will be reported, and any test databases created by the run will not be destroyed. +.. admonition:: Test with warnings enabled + + It is a good idea to run your tests with ``python -Wall manage.py + test``. This will allow you to catch any deprecation warnings that + might be in your code. Django (as well as many other libraries) use + warnings to flag when features are deprecated. It can also flag + areas in your code that are not strictly wrong, but may benefit + from a better implementation. + Running tests outside the test runner ------------------------------------- From 9d1bf9d5ad6c1ad97b1fb915975f3de3c13c4f4e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Oct 2010 15:47:09 +0000 Subject: [PATCH 256/902] [1.2.X] Fixed #14232 -- Clarified the data type of date_list in date-based generic views. Thanks to clelland for the report and patch. Backport of r14020 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14028 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/generic-views.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/ref/generic-views.txt b/docs/ref/generic-views.txt index 65f0d2eb3002..ea7fe2a49b50 100644 --- a/docs/ref/generic-views.txt +++ b/docs/ref/generic-views.txt @@ -197,10 +197,10 @@ If ``template_name`` isn't specified, this view will use the template In addition to ``extra_context``, the template's context will be: - * ``date_list``: A list of ``datetime.date`` objects representing all - years that have objects available according to ``queryset``. These are - ordered in reverse. This is equivalent to - ``queryset.dates(date_field, 'year')[::-1]``. + * ``date_list``: A ``DateQuerySet`` object containing all years that have + have objects available according to ``queryset``, represented as + ``datetime.datetime`` objects. These are ordered in reverse. This is + equivalent to ``queryset.dates(date_field, 'year')[::-1]``. .. versionchanged:: 1.0 The behaviour depending on ``template_object_name`` is new in this version. @@ -282,9 +282,9 @@ If ``template_name`` isn't specified, this view will use the template In addition to ``extra_context``, the template's context will be: - * ``date_list``: A list of ``datetime.date`` objects representing all - months that have objects available in the given year, according to - ``queryset``, in ascending order. + * ``date_list``: A ``DateQuerySet`` object containing all months that have + have objects available according to ``queryset``, represented as + ``datetime.datetime`` objects, in ascending order. * ``year``: The given year, as a four-character string. @@ -372,9 +372,9 @@ If ``template_name`` isn't specified, this view will use the template In addition to ``extra_context``, the template's context will be: - * ``date_list``: A list of ``datetime.date`` objects representing all - days that have objects available in the given month, according to - ``queryset``, in ascending order. + * ``date_list``: A ``DateQuerySet`` object containing all days that have + have objects available in the given month, according to ``queryset``, + represented as ``datetime.datetime`` objects, in ascending order. * ``month``: A ``datetime.date`` object representing the given month. From fb3f629457117cb754208af68dc7f63b73f92e1b Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 8 Oct 2010 17:54:11 +0000 Subject: [PATCH 257/902] [1.2.X] Fixed #10970 -- Initialize DateFields with datetime.date objects, not datetime.datetime. Thanks, summerisgone, Cyberj and Ramiro Morales. Backport from trunk (r14029). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14030 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/__init__.py | 10 +++++++++- tests/regressiontests/datatypes/models.py | 4 ++++ tests/regressiontests/datatypes/tests.py | 22 ++++++++++++++++------ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 3c5887303562..e5ed44dec5d7 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -622,7 +622,7 @@ def to_python(self, value): def pre_save(self, model_instance, add): if self.auto_now or (self.auto_now_add and add): - value = datetime.datetime.now() + value = datetime.date.today() setattr(model_instance, self.attname, value) return value else: @@ -709,6 +709,14 @@ def to_python(self, value): except ValueError: raise exceptions.ValidationError(self.error_messages['invalid']) + def pre_save(self, model_instance, add): + if self.auto_now or (self.auto_now_add and add): + value = datetime.datetime.now() + setattr(model_instance, self.attname, value) + return value + else: + return super(DateTimeField, self).pre_save(model_instance, add) + def get_prep_value(self, value): return self.to_python(value) diff --git a/tests/regressiontests/datatypes/models.py b/tests/regressiontests/datatypes/models.py index 47834b73cd49..f6fb72dbea68 100644 --- a/tests/regressiontests/datatypes/models.py +++ b/tests/regressiontests/datatypes/models.py @@ -19,3 +19,7 @@ class Meta: def __str__(self): return self.name + +class RumBaba(models.Model): + baked_date = models.DateField(auto_now_add=True) + baked_timestamp = models.DateTimeField(auto_now_add=True) diff --git a/tests/regressiontests/datatypes/tests.py b/tests/regressiontests/datatypes/tests.py index fc8085c89357..f7a0447ac107 100644 --- a/tests/regressiontests/datatypes/tests.py +++ b/tests/regressiontests/datatypes/tests.py @@ -3,7 +3,7 @@ from django.test import TestCase from django.utils import tzinfo -from models import Donut +from models import Donut, RumBaba from django.conf import settings class DataTypesTestCase(TestCase): @@ -43,7 +43,7 @@ def test_time_field(self): self.assertEqual(d2.baked_time, datetime.time(16, 19, 59)) def test_year_boundaries(self): - # Year boundary tests (ticket #3689) + """Year boundary tests (ticket #3689)""" d = Donut.objects.create(name='Date Test 2007', baked_date=datetime.datetime(year=2007, month=12, day=31), consumed_at=datetime.datetime(year=2007, month=12, day=31, hour=23, minute=59, second=59)) @@ -67,17 +67,27 @@ def test_year_boundaries(self): self.assertEqual(0, Donut.objects.filter(consumed_at__year=2008).count()) def test_textfields_unicode(self): - # Regression test for #10238: TextField values returned from the database - # should be unicode. + """Regression test for #10238: TextField values returned from the + database should be unicode.""" d = Donut.objects.create(name=u'Jelly Donut', review=u'Outstanding') newd = Donut.objects.get(id=d.id) self.assert_(isinstance(newd.review, unicode)) def test_tz_awareness_mysql(self): - # Regression test for #8354: the MySQL backend should raise an error if given - # a timezone-aware datetime object. + """Regression test for #8354: the MySQL backend should raise an error + if given a timezone-aware datetime object.""" if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.mysql': dt = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(0)) d = Donut(name='Bear claw', consumed_at=dt) self.assertRaises(ValueError, d.save) # ValueError: MySQL backend does not support timezone-aware datetimes. + + def test_datefield_auto_now_add(self): + """Regression test for #10970, auto_now_add for DateField should store + a Python datetime.date, not a datetime.datetime""" + b = RumBaba.objects.create() + # Verify we didn't break DateTimeField behavior + self.assert_(isinstance(b.baked_timestamp, datetime.datetime)) + # We need to test this this way because datetime.datetime inherits + # from datetime.date: + self.assert_(isinstance(b.baked_date, datetime.date) and not isinstance(b.baked_date, datetime.datetime)) From 9937943f3a4d6f9e6e0121b41c22ec7ea64dc66c Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 8 Oct 2010 17:54:40 +0000 Subject: [PATCH 258/902] [1.2.X] Fixed a few minor backporting oversights that prevented the tests to pass. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14031 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/model_package/tests.py | 1 - tests/regressiontests/urlpatterns_reverse/tests.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/modeltests/model_package/tests.py b/tests/modeltests/model_package/tests.py index cf1785f70878..99970faba15c 100644 --- a/tests/modeltests/model_package/tests.py +++ b/tests/modeltests/model_package/tests.py @@ -10,7 +10,6 @@ class Meta: __test__ = {'API_TESTS': """ >>> from models.publication import Publication >>> from models.article import Article ->>> from django.contrib.auth.views import Site >>> p = Publication(title="FooBar") >>> p.save() diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index 5b691eafa3f7..d0b714644e3e 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -5,7 +5,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404 +from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404, RegexURLResolver from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect from django.shortcuts import redirect from django.test import TestCase @@ -305,7 +305,6 @@ class ErrorHandlerResolutionTests(TestCase): """Tests for handler404 and handler500""" def setUp(self): - from django.core.urlresolvers import RegexURLResolver urlconf = 'regressiontests.urlpatterns_reverse.urls_error_handlers' urlconf_callables = 'regressiontests.urlpatterns_reverse.urls_error_handlers_callables' self.resolver = RegexURLResolver(r'^$', urlconf) From e8051e983a9a6a2fbec1ba79cb79d00c62fddb24 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 8 Oct 2010 21:12:55 +0000 Subject: [PATCH 259/902] [1.2.X] Added myself to the committers docs. This is a backport of [14034] and [14035]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14036 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 2 +- docs/internals/committers.txt | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 52d3d3ad26dd..d322014db5ee 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,6 +18,7 @@ The PRIMARY AUTHORS are (and/or have been): * Karen Tracey * Jannis Leidel * James Tauber + * Alex Gaynor More information on the main contributors to Django can be found in docs/internals/committers.txt. @@ -180,7 +181,6 @@ answer newbie questions, and generally made Django that much better: Jorge Gajon gandalf@owca.info Marc Garcia - Alex Gaynor Andy Gayton Idan Gazit geber@datacollect.com diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index b0bb18b9557d..e5c04c35f325 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -211,6 +211,17 @@ Karen Tracey .. _James Tauber: http://jtauber.com/ +`Alex Gaynor`_ + Alex is a student at Rensselaer Polytechnic Institute, and is also an + independent contractor. He found Django in 2007 and has been addicted ever + since he found out you don't need to write out your forms by hand. He has + a small obsession with compilers. He's contributed to the ORM, forms, + admin, and other components of Django. + + Alex lives in Chicago, IL, but spends most of his time in Troy, NY. + +.. _Alex Gaynor: http://alexgaynor.net + Specialists ----------- From cd1134d6df87b6a8a7bebbf4c70db8e5664132ed Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Fri, 8 Oct 2010 22:52:41 +0000 Subject: [PATCH 260/902] [1.2.X] Added my bio to committers.txt. Backport of [14037] git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14038 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/committers.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index e5c04c35f325..9dde5a53a98b 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -255,6 +255,16 @@ Jeremy Dunck Jeremy lives in Dallas, Texas, USA. +`Simon Meers`_ + Simon discovered Django 0.96 during his Computer Science PhD research and + has been developing with it full-time ever since. His core code + contributions are mostly in Django's admin application. He is also helping + to improve Django's documentation. + + Simon works as a freelance developer based in Wollongong, Australia. + +.. _simon meers: http://simonmeers.com/ + Developers Emeritus =================== From 475fb82d9874d465910c446349e254e9e8afa12f Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Fri, 8 Oct 2010 23:17:14 +0000 Subject: [PATCH 261/902] [1.2.X] Adding my bio. This is a backport of [14309]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14040 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + docs/internals/committers.txt | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/AUTHORS b/AUTHORS index d322014db5ee..c555144e1b4f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ The PRIMARY AUTHORS are (and/or have been): * Jannis Leidel * James Tauber * Alex Gaynor + * Andrew Godwin More information on the main contributors to Django can be found in docs/internals/committers.txt. diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 9dde5a53a98b..29058f00f035 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -222,6 +222,17 @@ Karen Tracey .. _Alex Gaynor: http://alexgaynor.net +`Andrew Godwin`_ + Andrew is a freelance Python developer and tinkerer, and has been + developing against Django since 2007. He graduated from Oxford University + with a degree in Computer Science, and has become most well known + in the Django community for his work on South, the schema migrations + library. + + Andrew lives in London, UK. + +.. _Andrew Godwin: http://www.aeracode.org/ + Specialists ----------- From 0eb8c02c6e0a9cbf88505aaf81e931a548da0ec5 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Fri, 8 Oct 2010 23:37:48 +0000 Subject: [PATCH 262/902] [1.2.X] Fixed #14023 -- include non_field_errors in example. Thanks to konryd for the report and patch. Backport of r14041 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14042 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/forms/index.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index cef322a02ffd..f845586ab416 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -210,6 +210,7 @@ the way a form is presented using the Django template language. Extending the above example:: + {{ form.non_field_errors }}
        {{ form.subject.errors }} From 3ab1a0c7d6bd88063e526a8ec36e1468b9364f9e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 8 Oct 2010 23:48:07 +0000 Subject: [PATCH 263/902] [1.2.X] Converted order_with_respect_to to unittests. We have always been at war with doctests. Backport of [14043]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14044 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../order_with_respect_to/models.py | 61 +----------------- .../modeltests/order_with_respect_to/tests.py | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 60 deletions(-) create mode 100644 tests/modeltests/order_with_respect_to/tests.py diff --git a/tests/modeltests/order_with_respect_to/models.py b/tests/modeltests/order_with_respect_to/models.py index 99c9f13e2efb..7e4acdadf7cf 100644 --- a/tests/modeltests/order_with_respect_to/models.py +++ b/tests/modeltests/order_with_respect_to/models.py @@ -4,6 +4,7 @@ from django.db import models + class Question(models.Model): text = models.CharField(max_length=200) @@ -16,63 +17,3 @@ class Meta: def __unicode__(self): return unicode(self.text) - -__test__ = {'API_TESTS': """ ->>> q1 = Question(text="Which Beatle starts with the letter 'R'?") ->>> q1.save() ->>> q2 = Question(text="What is your name?") ->>> q2.save() ->>> Answer(text="John", question=q1).save() ->>> Answer(text="Jonno",question=q2).save() ->>> Answer(text="Paul", question=q1).save() ->>> Answer(text="Paulo", question=q2).save() ->>> Answer(text="George", question=q1).save() ->>> Answer(text="Ringo", question=q1).save() - -The answers will always be ordered in the order they were inserted. - ->>> q1.answer_set.all() -[, , , ] - -We can retrieve the answers related to a particular object, in the order -they were created, once we have a particular object. - ->>> a1 = Answer.objects.filter(question=q1)[0] ->>> a1 - ->>> a2 = a1.get_next_in_order() ->>> a2 - ->>> a4 = list(Answer.objects.filter(question=q1))[-1] ->>> a4 - ->>> a4.get_previous_in_order() - - -Determining (and setting) the ordering for a particular item is also possible. - ->>> id_list = [o.pk for o in q1.answer_set.all()] ->>> a2.question.get_answer_order() == id_list -True - ->>> a5 = Answer(text="Number five", question=q1) ->>> a5.save() - -It doesn't matter which answer we use to check the order, it will always be the same. - ->>> a2.question.get_answer_order() == a5.question.get_answer_order() -True - -The ordering can be altered: - ->>> id_list = [o.pk for o in q1.answer_set.all()] ->>> x = id_list.pop() ->>> id_list.insert(-1, x) ->>> a5.question.get_answer_order() == id_list -False ->>> a5.question.set_answer_order(id_list) ->>> q1.answer_set.all() -[, , , , ] - -""" -} diff --git a/tests/modeltests/order_with_respect_to/tests.py b/tests/modeltests/order_with_respect_to/tests.py new file mode 100644 index 000000000000..4f1c8a4e163b --- /dev/null +++ b/tests/modeltests/order_with_respect_to/tests.py @@ -0,0 +1,62 @@ +from operator import attrgetter + +from django.test import TestCase + +from models import Question, Answer + + +class OrderWithRespectToTests(TestCase): + def test_basic(self): + q1 = Question.objects.create(text="Which Beatle starts with the letter 'R'?") + q2 = Question.objects.create(text="What is your name?") + + Answer.objects.create(text="John", question=q1) + Answer.objects.create(text="Jonno", question=q2) + Answer.objects.create(text="Paul", question=q1) + Answer.objects.create(text="Paulo", question=q2) + Answer.objects.create(text="George", question=q1) + Answer.objects.create(text="Ringo", question=q1) + + # The answers will always be ordered in the order they were inserted. + self.assertQuerysetEqual( + q1.answer_set.all(), [ + "John", "Paul", "George", "Ringo", + ], + attrgetter("text"), + ) + + # We can retrieve the answers related to a particular object, in the + # order they were created, once we have a particular object. + a1 = Answer.objects.filter(question=q1)[0] + self.assertEqual(a1.text, "John") + a2 = a1.get_next_in_order() + self.assertEqual(a2.text, "Paul") + a4 = list(Answer.objects.filter(question=q1))[-1] + self.assertEqual(a4.text, "Ringo") + self.assertEqual(a4.get_previous_in_order().text, "George") + + # Determining (and setting) the ordering for a particular item is also + # possible. + id_list = [o.pk for o in q1.answer_set.all()] + self.assertEqual(a2.question.get_answer_order(), id_list) + + a5 = Answer.objects.create(text="Number five", question=q1) + + # It doesn't matter which answer we use to check the order, it will + # always be the same. + self.assertEqual( + a2.question.get_answer_order(), a5.question.get_answer_order() + ) + + # The ordering can be altered: + id_list = [o.pk for o in q1.answer_set.all()] + x = id_list.pop() + id_list.insert(-1, x) + self.assertNotEqual(a5.question.get_answer_order(), id_list) + a5.question.set_answer_order(id_list) + self.assertQuerysetEqual( + q1.answer_set.all(), [ + "John", "Paul", "George", "Number five", "Ringo" + ], + attrgetter("text") + ) From ae448e4439f306aeec4989739f137e7d64ab63ef Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 8 Oct 2010 23:55:38 +0000 Subject: [PATCH 264/902] [1.2.X] Fixed #13241. order_with_respect_to now works with ForeignKeys who refer to their model lazily (i.e. with a string). Thanks to Gabriel Grant for the patch. This is a backport of [14045]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14046 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/db/models/base.py | 24 ++++++++++++++++--- .../order_with_respect_to/models.py | 10 ++++++++ .../modeltests/order_with_respect_to/tests.py | 11 ++++++++- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index c555144e1b4f..f71b0301c515 100644 --- a/AUTHORS +++ b/AUTHORS @@ -197,6 +197,7 @@ answer newbie questions, and generally made Django that much better: David Gouldin pradeep.gowda@gmail.com Collin Grady + Gabriel Grant Simon Greenhill Owen Griffiths Espen Grindhaug diff --git a/django/db/models/base.py b/django/db/models/base.py index d1232ee4cf26..05a6c1aa9360 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -5,7 +5,8 @@ from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS from django.core import validators from django.db.models.fields import AutoField, FieldDoesNotExist -from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField +from django.db.models.fields.related import (OneToOneRel, ManyToOneRel, + OneToOneField, add_lazy_relation) from django.db.models.query import delete_objects, Q from django.db.models.query_utils import CollectedObjects, DeferredAttribute from django.db.models.options import Options @@ -223,8 +224,25 @@ def _prepare(cls): if opts.order_with_respect_to: cls.get_next_in_order = curry(cls._get_next_or_previous_in_order, is_next=True) cls.get_previous_in_order = curry(cls._get_next_or_previous_in_order, is_next=False) - setattr(opts.order_with_respect_to.rel.to, 'get_%s_order' % cls.__name__.lower(), curry(method_get_order, cls)) - setattr(opts.order_with_respect_to.rel.to, 'set_%s_order' % cls.__name__.lower(), curry(method_set_order, cls)) + # defer creating accessors on the foreign class until we are + # certain it has been created + def make_foreign_order_accessors(field, model, cls): + setattr( + field.rel.to, + 'get_%s_order' % cls.__name__.lower(), + curry(method_get_order, cls) + ) + setattr( + field.rel.to, + 'set_%s_order' % cls.__name__.lower(), + curry(method_set_order, cls) + ) + add_lazy_relation( + cls, + opts.order_with_respect_to, + opts.order_with_respect_to.rel.to, + make_foreign_order_accessors + ) # Give the class a docstring -- its definition. if cls.__doc__ is None: diff --git a/tests/modeltests/order_with_respect_to/models.py b/tests/modeltests/order_with_respect_to/models.py index 7e4acdadf7cf..59f01d4cd146 100644 --- a/tests/modeltests/order_with_respect_to/models.py +++ b/tests/modeltests/order_with_respect_to/models.py @@ -17,3 +17,13 @@ class Meta: def __unicode__(self): return unicode(self.text) + +class Post(models.Model): + title = models.CharField(max_length=200) + parent = models.ForeignKey("self", related_name="children", null=True) + + class Meta: + order_with_respect_to = "parent" + + def __unicode__(self): + return self.title diff --git a/tests/modeltests/order_with_respect_to/tests.py b/tests/modeltests/order_with_respect_to/tests.py index 4f1c8a4e163b..328d968fd4ba 100644 --- a/tests/modeltests/order_with_respect_to/tests.py +++ b/tests/modeltests/order_with_respect_to/tests.py @@ -2,7 +2,7 @@ from django.test import TestCase -from models import Question, Answer +from models import Post, Question, Answer class OrderWithRespectToTests(TestCase): @@ -60,3 +60,12 @@ def test_basic(self): ], attrgetter("text") ) + + def test_recursive_ordering(self): + p1 = Post.objects.create(title='1') + p2 = Post.objects.create(title='2') + p1_1 = Post.objects.create(title="1.1", parent=p1) + p1_2 = Post.objects.create(title="1.2", parent=p1) + p2_1 = Post.objects.create(title="2.1", parent=p2) + p1_3 = Post.objects.create(title="1.3", parent=p1) + self.assertEqual(p1.get_post_order(), [p1_1.pk, p1_2.pk, p1_3.pk]) From 5ff085d65ed87793e06c94569c79ef41e7c4afcc Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 9 Oct 2010 02:11:18 +0000 Subject: [PATCH 265/902] [1.2.X] Added myself to committers. Backport of [14047]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14048 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 2 +- docs/internals/committers.txt | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index f71b0301c515..48a9a8afa389 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,6 +20,7 @@ The PRIMARY AUTHORS are (and/or have been): * James Tauber * Alex Gaynor * Andrew Godwin + * Carl Meyer More information on the main contributors to Django can be found in docs/internals/committers.txt. @@ -331,7 +332,6 @@ answer newbie questions, and generally made Django that much better: Tobias McNulty Zain Memon Christian Metts - Carl Meyer michal@plovarna.cz Slawek Mikula mitakummaa@gmail.com diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 29058f00f035..88105d061048 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -233,6 +233,18 @@ Karen Tracey .. _Andrew Godwin: http://www.aeracode.org/ +`Carl Meyer`_ + Carl has been working with Django since 2007 (long enough to remember + queryset-refactor, but not magic-removal), and works as a freelance + developer with OddBird_ and Eldarion_. He became a Django contributor by + accident, because fixing bugs is more interesting than working around + them. + + Carl lives in Elkhart, IN, USA. + +.. _Carl Meyer: http://www.oddbird.net/about/#hcard-carl +.. _OddBird: http://www.oddbird.net/ + Specialists ----------- From 52e781290174b71b37e4675ff504edc612ea66e8 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Sat, 9 Oct 2010 02:57:11 +0000 Subject: [PATCH 266/902] [1.2.X] Fixed #5537 -- document trailing '+' on related_name for supressing backward relation. Thanks to dcramer for the report, and Russ for pointing out the workaround. Backport of r14049 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14050 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/fields.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 68208b3bfe93..a390b4b843a6 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -925,6 +925,15 @@ define the details of how the relation works. `; and when you do so :ref:`some special syntax ` is available. + If you wish to supress the provision of a backwards relation, you may + simply provide a ``related_name`` which ends with a '+' character. + For example:: + + user = models.ForeignKey(User, related_name='+') + + will ensure that no backwards relation to this model is provided on the + ``User`` model. + .. attribute:: ForeignKey.to_field The field on the related object that the relation is to. By default, Django From 714694aeed99848b80d67444fc7280b3b67c90d6 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 9 Oct 2010 03:23:01 +0000 Subject: [PATCH 267/902] [1.2.X] Added myself to the committers list. Backport of [14051] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14052 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 2 +- docs/internals/committers.txt | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/AUTHORS b/AUTHORS index 48a9a8afa389..b8ad8c9e5116 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,6 +21,7 @@ The PRIMARY AUTHORS are (and/or have been): * Alex Gaynor * Andrew Godwin * Carl Meyer + * Ramiro Morales More information on the main contributors to Django can be found in docs/internals/committers.txt. @@ -339,7 +340,6 @@ answer newbie questions, and generally made Django that much better: Andreas Mock Reza Mohammadi Aljosa Mohorovic - Ramiro Morales Eric Moritz msaelices Gregor Müllegger diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 88105d061048..324a37840f52 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -137,7 +137,7 @@ Joseph Kocherhans Django / Pinax_ based websites. He enjoys learning more about programming languages and system architectures and contributing to open source projects. Brian is the host of the `Django Dose`_ podcasts. - + Brian helped immensely in getting Django's "newforms-admin" branch finished in time for Django 1.0; he's now a full committer, continuing to improve on the admin and forms system. @@ -204,9 +204,9 @@ Karen Tracey since 1998 and Django since 2006. He serves on the board of the Python Software Foundation and is currently on a leave of absence from a PhD in linguistics. - - James currently lives in Boston, MA, USA but originally hails from - Perth, Western Australia where he attended the same high school as + + James currently lives in Boston, MA, USA but originally hails from + Perth, Western Australia where he attended the same high school as Russell Keith-Magee. .. _James Tauber: http://jtauber.com/ @@ -217,7 +217,7 @@ Karen Tracey since he found out you don't need to write out your forms by hand. He has a small obsession with compilers. He's contributed to the ORM, forms, admin, and other components of Django. - + Alex lives in Chicago, IL, but spends most of his time in Troy, NY. .. _Alex Gaynor: http://alexgaynor.net @@ -245,6 +245,17 @@ Karen Tracey .. _Carl Meyer: http://www.oddbird.net/about/#hcard-carl .. _OddBird: http://www.oddbird.net/ +Ramiro Morales + Ramiro has been reading Django source code and submitting patches since + mid-2006 after researching for a Python Web tool with matching awesomeness + and being pointed to it by an old ninja. + + A software developer in the electronic transactions industry, he is a + living proof of the fact that anyone with enough enthusiasm can contribute + to Django, learning a lot and having fun in the process. + + Ramiro lives in Córdoba, Argentina. + Specialists ----------- @@ -279,9 +290,9 @@ Jeremy Dunck Jeremy lives in Dallas, Texas, USA. `Simon Meers`_ - Simon discovered Django 0.96 during his Computer Science PhD research and - has been developing with it full-time ever since. His core code - contributions are mostly in Django's admin application. He is also helping + Simon discovered Django 0.96 during his Computer Science PhD research and + has been developing with it full-time ever since. His core code + contributions are mostly in Django's admin application. He is also helping to improve Django's documentation. Simon works as a freelance developer based in Wollongong, Australia. From a3581f9a5542178ea870dcc986b659892adb0ee8 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 9 Oct 2010 03:36:46 +0000 Subject: [PATCH 268/902] [1.2.X] Fixed #14354 -- Normalized the handling of empty/null passwords in contrib.auth. This also updates the createsuperuser command to be more testable, and migrates some auth doctests. Thanks to berryp for the report, and Laurent Luce for the patch. Backport of r14053 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14054 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + .../management/commands/createsuperuser.py | 15 +- django/contrib/auth/models.py | 25 +-- django/contrib/auth/tests/__init__.py | 3 +- django/contrib/auth/tests/basic.py | 153 ++++++++++-------- 5 files changed, 109 insertions(+), 88 deletions(-) diff --git a/AUTHORS b/AUTHORS index b8ad8c9e5116..11beb1bc13e8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -307,6 +307,7 @@ answer newbie questions, and generally made Django that much better: Simon Litchfield Daniel Lindsley Trey Long + Laurent Luce Martin Mahner Matt McClanahan Stanislaus Madueke diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index aad6489b3869..87751527c841 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -41,7 +41,8 @@ def handle(self, *args, **options): username = options.get('username', None) email = options.get('email', None) interactive = options.get('interactive') - + verbosity = int(options.get('verbosity', 1)) + # Do quick and dirty validation if --noinput if not interactive: if not username or not email: @@ -79,7 +80,7 @@ def handle(self, *args, **options): # try/except to trap for a keyboard interrupt and exit gracefully. if interactive: try: - + # Get a username while 1: if not username: @@ -100,7 +101,7 @@ def handle(self, *args, **options): else: sys.stderr.write("Error: That username is already taken.\n") username = None - + # Get an email while 1: if not email: @@ -112,7 +113,7 @@ def handle(self, *args, **options): email = None else: break - + # Get a password while 1: if not password: @@ -130,6 +131,8 @@ def handle(self, *args, **options): except KeyboardInterrupt: sys.stderr.write("\nOperation cancelled.\n") sys.exit(1) - + User.objects.create_superuser(username, email, password) - print "Superuser created successfully." + if verbosity >= 1: + self.stdout.write("Superuser created successfully.\n") + diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 681cf1422ab0..3d927d813e34 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -106,7 +106,6 @@ def create_user(self, username, email, password=None): """ Creates and saves a User with the given username, e-mail and password. """ - now = datetime.datetime.now() # Normalize the address by lowercasing the domain part of the email @@ -122,10 +121,7 @@ def create_user(self, username, email, password=None): is_active=True, is_superuser=False, last_login=now, date_joined=now) - if password: - user.set_password(password) - else: - user.set_unusable_password() + user.set_password(password) user.save(using=self._db) return user @@ -238,11 +234,14 @@ def get_full_name(self): return full_name.strip() def set_password(self, raw_password): - import random - algo = 'sha1' - salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5] - hsh = get_hexdigest(algo, salt, raw_password) - self.password = '%s$%s$%s' % (algo, salt, hsh) + if raw_password is None: + self.set_unusable_password() + else: + import random + algo = 'sha1' + salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5] + hsh = get_hexdigest(algo, salt, raw_password) + self.password = '%s$%s$%s' % (algo, salt, hsh) def check_password(self, raw_password): """ @@ -265,7 +264,11 @@ def set_unusable_password(self): self.password = UNUSABLE_PASSWORD def has_usable_password(self): - return self.password != UNUSABLE_PASSWORD + if self.password is None \ + or self.password == UNUSABLE_PASSWORD: + return False + else: + return True def get_group_permissions(self, obj=None): """ diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py index a1d02b6014a4..bc67d16d8663 100644 --- a/django/contrib/auth/tests/__init__.py +++ b/django/contrib/auth/tests/__init__.py @@ -1,5 +1,5 @@ from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest -from django.contrib.auth.tests.basic import BASIC_TESTS +from django.contrib.auth.tests.basic import BasicTestCase from django.contrib.auth.tests.decorators import LoginRequiredTestCase from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest from django.contrib.auth.tests.remote_user \ @@ -12,6 +12,5 @@ # The password for the fixture data users is 'password' __test__ = { - 'BASIC_TESTS': BASIC_TESTS, 'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS, } diff --git a/django/contrib/auth/tests/basic.py b/django/contrib/auth/tests/basic.py index ffa11d5d0ec0..7493dc68dadc 100644 --- a/django/contrib/auth/tests/basic.py +++ b/django/contrib/auth/tests/basic.py @@ -1,77 +1,92 @@ +from django.test import TestCase +from django.contrib.auth.models import User, AnonymousUser +from django.core.management import call_command +from StringIO import StringIO -BASIC_TESTS = """ ->>> from django.contrib.auth.models import User, AnonymousUser ->>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw') ->>> u.has_usable_password() -True ->>> u.check_password('bad') -False ->>> u.check_password('testpw') -True ->>> u.set_unusable_password() ->>> u.save() ->>> u.check_password('testpw') -False ->>> u.has_usable_password() -False ->>> u2 = User.objects.create_user('testuser2', 'test2@example.com') ->>> u2.has_usable_password() -False +class BasicTestCase(TestCase): + def test_user(self): + "Check that users can be created and can set their password" + u = User.objects.create_user('testuser', 'test@example.com', 'testpw') + self.assertTrue(u.has_usable_password()) + self.assertFalse(u.check_password('bad')) + self.assertTrue(u.check_password('testpw')) ->>> u.is_authenticated() -True ->>> u.is_staff -False ->>> u.is_active -True ->>> u.is_superuser -False + # Check we can manually set an unusable password + u.set_unusable_password() + u.save() + self.assertFalse(u.check_password('testpw')) + self.assertFalse(u.has_usable_password()) + u.set_password('testpw') + self.assertTrue(u.check_password('testpw')) + u.set_password(None) + self.assertFalse(u.has_usable_password()) ->>> a = AnonymousUser() ->>> a.is_authenticated() -False ->>> a.is_staff -False ->>> a.is_active -False ->>> a.is_superuser -False ->>> a.groups.all() -[] ->>> a.user_permissions.all() -[] + # Check authentication/permissions + self.assertTrue(u.is_authenticated()) + self.assertFalse(u.is_staff) + self.assertTrue(u.is_active) + self.assertFalse(u.is_superuser) -# superuser tests. ->>> super = User.objects.create_superuser('super', 'super@example.com', 'super') ->>> super.is_superuser -True ->>> super.is_active -True ->>> super.is_staff -True + # Check API-based user creation with no password + u2 = User.objects.create_user('testuser2', 'test2@example.com') + self.assertFalse(u.has_usable_password()) -# -# Tests for createsuperuser management command. -# It's nearly impossible to test the interactive mode -- a command test helper -# would be needed (and *awesome*) -- so just test the non-interactive mode. -# This covers most of the important validation, but not all. -# ->>> from django.core.management import call_command + def test_anonymous_user(self): + "Check the properties of the anonymous user" + a = AnonymousUser() + self.assertFalse(a.is_authenticated()) + self.assertFalse(a.is_staff) + self.assertFalse(a.is_active) + self.assertFalse(a.is_superuser) + self.assertEqual(a.groups.all().count(), 0) + self.assertEqual(a.user_permissions.all().count(), 0) ->>> call_command("createsuperuser", interactive=False, username="joe", email="joe@somewhere.org") -Superuser created successfully. + def test_superuser(self): + "Check the creation and properties of a superuser" + super = User.objects.create_superuser('super', 'super@example.com', 'super') + self.assertTrue(super.is_superuser) + self.assertTrue(super.is_active) + self.assertTrue(super.is_staff) ->>> u = User.objects.get(username="joe") ->>> u.email -u'joe@somewhere.org' ->>> u.password -u'!' ->>> call_command("createsuperuser", interactive=False, username="joe+admin@somewhere.org", email="joe@somewhere.org") -Superuser created successfully. + def test_createsuperuser_management_command(self): + "Check the operation of the createsuperuser management command" + # We can use the management command to create a superuser + new_io = StringIO() + call_command("createsuperuser", + interactive=False, + username="joe", + email="joe@somewhere.org", + stdout=new_io + ) + command_output = new_io.getvalue().strip() + self.assertEqual(command_output, 'Superuser created successfully.') + u = User.objects.get(username="joe") + self.assertEquals(u.email, 'joe@somewhere.org') + self.assertTrue(u.check_password('')) + + # We can supress output on the management command + new_io = StringIO() + call_command("createsuperuser", + interactive=False, + username="joe2", + email="joe2@somewhere.org", + verbosity=0, + stdout=new_io + ) + command_output = new_io.getvalue().strip() + self.assertEqual(command_output, '') + u = User.objects.get(username="joe2") + self.assertEquals(u.email, 'joe2@somewhere.org') + self.assertTrue(u.check_password('')) + + new_io = StringIO() + call_command("createsuperuser", + interactive=False, + username="joe+admin@somewhere.org", + email="joe@somewhere.org", + stdout=new_io + ) + u = User.objects.get(username="joe+admin@somewhere.org") + self.assertEquals(u.email, 'joe@somewhere.org') + self.assertTrue(u.check_password('')) ->>> u = User.objects.get(username="joe+admin@somewhere.org") ->>> u.email -u'joe@somewhere.org' ->>> u.password -u'!' -""" From fae5b04f11611a891d28abc214412cf8c4fe9549 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 9 Oct 2010 04:10:39 +0000 Subject: [PATCH 269/902] [1.2.X] Fixed #12872 -- Removed vestiges of mythic old template validation admin app functionality. Backport of [14055] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14057 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../templates/admin/template_validator.html | 31 -------- django/contrib/admin/views/template.py | 79 ------------------- 2 files changed, 110 deletions(-) delete mode 100644 django/contrib/admin/templates/admin/template_validator.html delete mode 100644 django/contrib/admin/views/template.py diff --git a/django/contrib/admin/templates/admin/template_validator.html b/django/contrib/admin/templates/admin/template_validator.html deleted file mode 100644 index 9a139c5d4929..000000000000 --- a/django/contrib/admin/templates/admin/template_validator.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "admin/base_site.html" %} - -{% block content %} - -
        - -{% csrf_token %} - -{% if form.errors %} -

        Your template had {{ form.errors|length }} error{{ form.errors|pluralize }}:

        -{% endif %} - -
        -
        - {{ form.errors.site }} -

        {{ form.site }}

        -
        -
        - {{ form.errors.template }} -

        {{ form.template }}

        -
        -
        - -
        - -
        - - -
        - -{% endblock %} diff --git a/django/contrib/admin/views/template.py b/django/contrib/admin/views/template.py deleted file mode 100644 index 0b8ed3ea9efa..000000000000 --- a/django/contrib/admin/views/template.py +++ /dev/null @@ -1,79 +0,0 @@ -from django import template, forms -from django.contrib.admin.views.decorators import staff_member_required -from django.template import loader -from django.shortcuts import render_to_response -from django.contrib.sites.models import Site -from django.conf import settings -from django.utils.importlib import import_module -from django.utils.translation import ugettext_lazy as _ -from django.contrib import messages - - -def template_validator(request): - """ - Displays the template validator form, which finds and displays template - syntax errors. - """ - # get a dict of {site_id : settings_module} for the validator - settings_modules = {} - for mod in settings.ADMIN_FOR: - settings_module = import_module(mod) - settings_modules[settings_module.SITE_ID] = settings_module - site_list = Site.objects.in_bulk(settings_modules.keys()).values() - if request.POST: - form = TemplateValidatorForm(settings_modules, site_list, - data=request.POST) - if form.is_valid(): - messages.info(request, 'The template is valid.') - else: - form = TemplateValidatorForm(settings_modules, site_list) - return render_to_response('admin/template_validator.html', { - 'title': 'Template validator', - 'form': form, - }, context_instance=template.RequestContext(request)) -template_validator = staff_member_required(template_validator) - - -class TemplateValidatorForm(forms.Form): - site = forms.ChoiceField(_('site')) - template = forms.CharField( - _('template'), widget=forms.Textarea({'rows': 25, 'cols': 80})) - - def __init__(self, settings_modules, site_list, *args, **kwargs): - self.settings_modules = settings_modules - super(TemplateValidatorForm, self).__init__(*args, **kwargs) - self.fields['site'].choices = [(s.id, s.name) for s in site_list] - - def clean_template(self): - # Get the settings module. If the site isn't set, we don't raise an - # error since the site field will. - try: - site_id = int(self.cleaned_data.get('site', None)) - except (ValueError, TypeError): - return - settings_module = self.settings_modules.get(site_id, None) - if settings_module is None: - return - - # So that inheritance works in the site's context, register a new - # function for "extends" that uses the site's TEMPLATE_DIRS instead. - def new_do_extends(parser, token): - node = loader.do_extends(parser, token) - node.template_dirs = settings_module.TEMPLATE_DIRS - return node - register = template.Library() - register.tag('extends', new_do_extends) - template.builtins.append(register) - - # Now validate the template using the new TEMPLATE_DIRS, making sure to - # reset the extends function in any case. - error = None - template_string = self.cleaned_data['template'] - try: - tmpl = loader.get_template_from_string(template_string) - tmpl.render(template.Context({})) - except template.TemplateSyntaxError, e: - error = e - template.builtins.remove(register) - if error: - raise forms.ValidationError(e.args) From 8fd9f2eb9f2706dc774ffbdea6bd172c83741e68 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Sat, 9 Oct 2010 05:10:32 +0000 Subject: [PATCH 270/902] [1.2.X] Fixed #5677 -- update modpython stdout documentation. Thanks to Manfred Wassmann for the report, nickefford for the initial patch and Graham Dumpleton for the expert advice. Backported of r14059 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14060 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/deployment/modpython.txt | 35 ++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/howto/deployment/modpython.txt b/docs/howto/deployment/modpython.txt index d35cac8fcd5f..3e413f9bceb7 100644 --- a/docs/howto/deployment/modpython.txt +++ b/docs/howto/deployment/modpython.txt @@ -2,6 +2,13 @@ How to use Django with Apache and mod_python ============================================ +.. warning:: + + Support for mod_python will be deprecated in a future release of Django. If + you are configuring a new deployment, you are strongly encouraged to + consider using :doc:`mod_wsgi ` or any of the + other :doc:`supported backends `. + .. highlight:: apache The `mod_python`_ module for Apache_ can be used to deploy Django to a @@ -151,6 +158,8 @@ the full URL. When deploying Django sites on mod_python, you'll need to restart Apache each time you make changes to your Python code. +.. _mod_python documentation: http://modpython.org/live/current/doc-html/directives.html + Multiple Django installations on the same Apache ================================================ @@ -204,17 +213,25 @@ everything for each request. But don't do that on a production server, or we'll revoke your Django privileges. If you're the type of programmer who debugs using scattered ``print`` -statements, note that ``print`` statements have no effect in mod_python; they -don't appear in the Apache log, as one might expect. If you have the need to -print debugging information in a mod_python setup, either do this:: +statements, note that output to ``stdout`` will not appear in the Apache +log and can even `cause response errors`_. - assert False, the_value_i_want_to_see +.. _cause response errors: http://blog.dscpl.com.au/2009/04/wsgi-and-printing-to-standard-output.html -Or add the debugging information to the template of your page. +If you have the need to print debugging information in a mod_python setup, you +have a few options. You can print to ``stderr`` explicitly, like so:: + + print >> sys.stderr, 'debug text' + sys.stderr.flush() + +(note that ``stderr`` is buffered, so calling ``flush`` is necessary if you wish +debugging information to be displayed promptly.) + +A more compact approach is to use an assertion:: -.. _mod_python documentation: http://modpython.org/live/current/doc-html/directives.html + assert False, 'debug text' -.. _serving-media-files: +Another alternative is to add debugging information to the template of your page. Serving media files =================== @@ -267,10 +284,6 @@ the ``media`` subdirectory and any URL that ends with ``.jpg``, ``.gif`` or .. _Apache: http://httpd.apache.org/ .. _Cherokee: http://www.cherokee-project.com/ -.. _howto-deployment-modpython-serving-the-admin-files: - -.. _serving-the-admin-files: - Serving the admin files ======================= From 19352c9cd9b3ae42ef945fa1de8399e38b2533e0 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 9 Oct 2010 07:28:47 +0000 Subject: [PATCH 271/902] [1.2.X] Fixed #13279 -- Made the paths in the Unix install docs more explicit. Thanks to KathyManwaring for the report and stumbles for the patch. Backport of [14062] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14063 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/install.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 3114517f62b2..cd3195c25103 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -243,11 +243,12 @@ latest bug fixes and improvements, follow these instructions: .. code-block:: bash - ln -s `pwd`/django-trunk/django SITE-PACKAGES-DIR/django + ln -s WORKING-DIR/django-trunk/django SITE-PACKAGES-DIR/django (In the above line, change ``SITE-PACKAGES-DIR`` to match the location of your system's ``site-packages`` directory, as explained in the - "Where are my ``site-packages`` stored?" section above.) + "Where are my ``site-packages`` stored?" section above. Change WORKING-DIR + to match the full path to your new ``djanjo-trunk`` directory.) Alternatively, you can define your ``PYTHONPATH`` environment variable so that it includes the ``django-trunk`` directory. This is perhaps the @@ -272,7 +273,10 @@ latest bug fixes and improvements, follow these instructions: .. code-block:: bash - ln -s `pwd`/django-trunk/django/bin/django-admin.py /usr/local/bin + ln -s WORKING-DIR/django-trunk/django/bin/django-admin.py /usr/local/bin + + (In the above line, change WORKING-DIR to match the full path to your new + ``djanjo-trunk`` directory.) This simply lets you type ``django-admin.py`` from within any directory, rather than having to qualify the command with the full path to the file. From b2bd6c9d56092ed4f7a3436de764baf9e44b6f12 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 9 Oct 2010 07:36:07 +0000 Subject: [PATCH 272/902] [1.2.X] Corrects a typo in [14063]. Thanks to Russ for the report. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14065 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/install.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/install.txt b/docs/topics/install.txt index cd3195c25103..cb6c29f5f897 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -248,7 +248,7 @@ latest bug fixes and improvements, follow these instructions: (In the above line, change ``SITE-PACKAGES-DIR`` to match the location of your system's ``site-packages`` directory, as explained in the "Where are my ``site-packages`` stored?" section above. Change WORKING-DIR - to match the full path to your new ``djanjo-trunk`` directory.) + to match the full path to your new ``django-trunk`` directory.) Alternatively, you can define your ``PYTHONPATH`` environment variable so that it includes the ``django-trunk`` directory. This is perhaps the @@ -276,7 +276,7 @@ latest bug fixes and improvements, follow these instructions: ln -s WORKING-DIR/django-trunk/django/bin/django-admin.py /usr/local/bin (In the above line, change WORKING-DIR to match the full path to your new - ``djanjo-trunk`` directory.) + ``django-trunk`` directory.) This simply lets you type ``django-admin.py`` from within any directory, rather than having to qualify the command with the full path to the file. From 824ca0d6e560684bd0c09b09afece2586176a2c7 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Sat, 9 Oct 2010 07:48:09 +0000 Subject: [PATCH 273/902] [1.2.X] Fixed #14255 -- factor project name out of app imports in tutorial. Thanks to adamend for the report and initial patch. Backport of r14066 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14067 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial01.txt | 26 ++++++++++----------- docs/intro/tutorial02.txt | 4 ++-- docs/intro/tutorial03.txt | 48 +++++++++++++++++++-------------------- docs/intro/tutorial04.txt | 12 +++++----- 4 files changed, 44 insertions(+), 46 deletions(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 6045eb111e69..be98742469ba 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -268,10 +268,8 @@ so you can focus on writing code rather than creating directories. configuration and apps for a particular Web site. A project can contain multiple apps. An app can be in multiple projects. -In this tutorial, we'll create our poll app in the :file:`mysite` directory, -for simplicity. As a consequence, the app will be coupled to the project -- -that is, Python code within the poll app will refer to ``mysite.polls``. -Later in this tutorial, we'll discuss decoupling your apps for distribution. +Your apps can live anywhere on your `Python path`_. In this tutorial we will +create our poll app in the :file:`mysite` directory for simplicity. To create your app, make sure you're in the :file:`mysite` directory and type this command: @@ -369,7 +367,7 @@ But first we need to tell our project that the ``polls`` app is installed. Django installation. Edit the :file:`settings.py` file again, and change the -:setting:`INSTALLED_APPS` setting to include the string ``'mysite.polls'``. So +:setting:`INSTALLED_APPS` setting to include the string ``'polls'``. So it'll look like this:: INSTALLED_APPS = ( @@ -377,10 +375,10 @@ it'll look like this:: 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', - 'mysite.polls' + 'polls' ) -Now Django knows ``mysite`` includes the ``polls`` app. Let's run another +Now Django knows to include the ``polls`` app. Let's run another command: .. code-block:: bash @@ -488,9 +486,9 @@ We're using this instead of simply typing "python", because ``manage.py`` sets up the project's environment for you. "Setting up the environment" involves two things: - * Putting ``mysite`` on ``sys.path``. For flexibility, several pieces of + * Putting ``polls`` on ``sys.path``. For flexibility, several pieces of Django refer to projects in Python dotted-path notation (e.g. - ``'mysite.polls.models'``). In order for this to work, the ``mysite`` + ``'polls.models'``). In order for this to work, the ``polls`` package has to be on ``sys.path``. We've already seen one example of this: the :setting:`INSTALLED_APPS` @@ -502,16 +500,16 @@ things: .. admonition:: Bypassing manage.py If you'd rather not use ``manage.py``, no problem. Just make sure ``mysite`` - is at the root level on the Python path (i.e., ``import mysite`` works) and - set the ``DJANGO_SETTINGS_MODULE`` environment variable to - ``mysite.settings``. + and ``polls`` are at the root level on the Python path (i.e., ``import mysite`` + and ``import polls`` work) and set the ``DJANGO_SETTINGS_MODULE`` environment + variable to ``mysite.settings``. For more information on all of this, see the :doc:`django-admin.py documentation `. Once you're in the shell, explore the :doc:`database API `:: - >>> from mysite.polls.models import Poll, Choice # Import the model classes we just wrote. + >>> from polls.models import Poll, Choice # Import the model classes we just wrote. # No polls are in the system yet. >>> Poll.objects.all() @@ -619,7 +617,7 @@ Note the addition of ``import datetime`` to reference Python's standard Save these changes and start a new Python interactive shell by running ``python manage.py shell`` again:: - >>> from mysite.polls.models import Poll, Choice + >>> from polls.models import Poll, Choice # Make sure our __unicode__() addition worked. >>> Poll.objects.all() diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index d43df9ed53c2..c80d87d83578 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -103,7 +103,7 @@ Just one thing to do: We need to tell the admin that ``Poll`` objects have an admin interface. To do this, create a file called ``admin.py`` in your ``polls`` directory, and edit it to look like this:: - from mysite.polls.models import Poll + from polls.models import Poll from django.contrib import admin admin.site.register(Poll) @@ -239,7 +239,7 @@ Yet. There are two ways to solve this problem. The first is to register ``Choice`` with the admin just as we did with ``Poll``. That's easy:: - from mysite.polls.models import Choice + from polls.models import Choice admin.site.register(Choice) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index d3470359aaf8..6da0ad4029b7 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -84,10 +84,10 @@ Time for an example. Edit ``mysite/urls.py`` so it looks like this:: admin.autodiscover() urlpatterns = patterns('', - (r'^polls/$', 'mysite.polls.views.index'), - (r'^polls/(?P\d+)/$', 'mysite.polls.views.detail'), - (r'^polls/(?P\d+)/results/$', 'mysite.polls.views.results'), - (r'^polls/(?P\d+)/vote/$', 'mysite.polls.views.vote'), + (r'^polls/$', 'polls.views.index'), + (r'^polls/(?P\d+)/$', 'polls.views.detail'), + (r'^polls/(?P\d+)/results/$', 'polls.views.results'), + (r'^polls/(?P\d+)/vote/$', 'polls.views.vote'), (r'^admin/', include(admin.site.urls)), ) @@ -96,8 +96,8 @@ This is worth a review. When somebody requests a page from your Web site -- say, the :setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns`` and traverses the regular expressions in order. When it finds a regular expression that matches -- ``r'^polls/(?P\d+)/$'`` -- it loads the -function ``detail()`` from ``mysite/polls/views.py``. Finally, -it calls that ``detail()`` function like so:: +function ``detail()`` from ``polls/views.py``. Finally, it calls that +``detail()`` function like so:: detail(request=, poll_id='23') @@ -112,7 +112,7 @@ 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:: - (r'^polls/latest\.php$', 'mysite.polls.views.index'), + (r'^polls/latest\.php$', 'polls.views.index'), But, don't do that. It's silly. @@ -148,17 +148,17 @@ You should get a pleasantly-colored error page with the following message:: ViewDoesNotExist at /polls/ - Tried index in module mysite.polls.views. Error was: 'module' + Tried index in module polls.views. Error was: 'module' object has no attribute 'index' This error happened because you haven't written a function ``index()`` in the -module ``mysite/polls/views.py``. +module ``polls/views.py``. Try "/polls/23/", "/polls/23/results/" and "/polls/23/vote/". The error messages tell you which view Django tried (and failed to find, because you haven't written any views yet). -Time to write the first view. Open the file ``mysite/polls/views.py`` +Time to write the first view. Open the file ``polls/views.py`` and put the following Python code in it:: from django.http import HttpResponse @@ -207,7 +207,7 @@ in :doc:`Tutorial 1 `. Here's one stab at the ``index()`` view, which displays the latest 5 poll questions in the system, separated by commas, according to publication date:: - from mysite.polls.models import Poll + from polls.models import Poll from django.http import HttpResponse def index(request): @@ -220,7 +220,7 @@ you want to change the way the page looks, you'll have to edit this Python code. So let's use Django's template system to separate the design from Python:: from django.template import Context, loader - from mysite.polls.models import Poll + from polls.models import Poll from django.http import HttpResponse def index(request): @@ -279,7 +279,7 @@ template. Django provides a shortcut. Here's the full ``index()`` view, rewritten:: from django.shortcuts import render_to_response - from mysite.polls.models import Poll + from polls.models import Poll def index(request): latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] @@ -432,19 +432,19 @@ Take some time to play around with the views and template system. As you edit the URLconf, you may notice there's a fair bit of redundancy in it:: urlpatterns = patterns('', - (r'^polls/$', 'mysite.polls.views.index'), - (r'^polls/(?P\d+)/$', 'mysite.polls.views.detail'), - (r'^polls/(?P\d+)/results/$', 'mysite.polls.views.results'), - (r'^polls/(?P\d+)/vote/$', 'mysite.polls.views.vote'), + (r'^polls/$', 'polls.views.index'), + (r'^polls/(?P\d+)/$', 'polls.views.detail'), + (r'^polls/(?P\d+)/results/$', 'polls.views.results'), + (r'^polls/(?P\d+)/vote/$', 'polls.views.vote'), ) -Namely, ``mysite.polls.views`` is in every callback. +Namely, ``polls.views`` is in every callback. Because this is a common case, the URLconf framework provides a shortcut for common prefixes. You can factor out the common prefixes and add them as the first argument to :func:`~django.conf.urls.defaults.patterns`, like so:: - urlpatterns = patterns('mysite.polls.views', + urlpatterns = patterns('polls.views', (r'^polls/$', 'index'), (r'^polls/(?P\d+)/$', 'detail'), (r'^polls/(?P\d+)/results/$', 'results'), @@ -470,7 +470,7 @@ We've been editing the URLs in ``mysite/urls.py``, but the URL design of an app is specific to the app, not to the Django installation -- so let's move the URLs within the app directory. -Copy the file ``mysite/urls.py`` to ``mysite/polls/urls.py``. Then, change +Copy the file ``mysite/urls.py`` to ``polls/urls.py``. Then, change ``mysite/urls.py`` to remove the poll-specific URLs and insert an :func:`~django.conf.urls.defaults.include`:: @@ -479,7 +479,7 @@ Copy the file ``mysite/urls.py`` to ``mysite/polls/urls.py``. Then, change # ... urlpatterns = patterns('', - (r'^polls/', include('mysite.polls.urls')), + (r'^polls/', include('polls.urls')), # ... ) @@ -495,14 +495,14 @@ Here's what happens if a user goes to "/polls/34/" in this system: * Django will find the match at ``'^polls/'`` * Then, Django will strip off the matching text (``"polls/"``) and send the - remaining text -- ``"34/"`` -- to the 'mysite.polls.urls' URLconf for + remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for further processing. -Now that we've decoupled that, we need to decouple the 'mysite.polls.urls' +Now that we've decoupled that, we need to decouple the 'polls.urls' URLconf by removing the leading "polls/" from each line, and removing the lines registering the admin site:: - urlpatterns = patterns('mysite.polls.views', + urlpatterns = patterns('polls.views', (r'^$', 'index'), (r'^(?P\d+)/$', 'detail'), (r'^(?P\d+)/results/$', 'results'), diff --git a/docs/intro/tutorial04.txt b/docs/intro/tutorial04.txt index a7a9aaea3355..dfbd82df5548 100644 --- a/docs/intro/tutorial04.txt +++ b/docs/intro/tutorial04.txt @@ -74,13 +74,13 @@ created a URLconf for the polls application that includes this line:: (r'^(?P\d+)/vote/$', 'vote'), We also created a dummy implementation of the ``vote()`` function. Let's -create a real version. Add the following to ``mysite/polls/views.py``:: +create a real version. Add the following to ``polls/views.py``:: from django.shortcuts import get_object_or_404, render_to_response from django.http import HttpResponseRedirect, HttpResponse from django.core.urlresolvers import reverse from django.template import RequestContext - from mysite.polls.models import Choice, Poll + from polls.models import Choice, Poll # ... def vote(request, poll_id): p = get_object_or_404(Poll, pk=poll_id) @@ -98,7 +98,7 @@ create a real version. Add the following to ``mysite/polls/views.py``:: # Always return an HttpResponseRedirect after successfully dealing # with POST data. This prevents data from being posted twice if a # user hits the Back button. - return HttpResponseRedirect(reverse('mysite.polls.views.results', args=(p.id,))) + return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,))) This code includes a few things we haven't covered yet in this tutorial: @@ -222,7 +222,7 @@ tutorial so far:: from django.conf.urls.defaults import * - urlpatterns = patterns('mysite.polls.views', + urlpatterns = patterns('polls.views', (r'^$', 'index'), (r'^(?P\d+)/$', 'detail'), (r'^(?P\d+)/results/$', 'results'), @@ -232,7 +232,7 @@ tutorial so far:: Change it like so:: from django.conf.urls.defaults import * - from mysite.polls.models import Poll + from polls.models import Poll info_dict = { 'queryset': Poll.objects.all(), @@ -242,7 +242,7 @@ Change it like so:: (r'^$', 'django.views.generic.list_detail.object_list', info_dict), (r'^(?P\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict), url(r'^(?P\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'), - (r'^(?P\d+)/vote/$', 'mysite.polls.views.vote'), + (r'^(?P\d+)/vote/$', 'polls.views.vote'), ) We're using two generic views here: From a322ba66a175abe4c34bfc6d7cdecb8d4e801ed3 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 9 Oct 2010 08:23:32 +0000 Subject: [PATCH 274/902] [1.2.X] Migrated the custom_columns doctests to unit tests. Thanks to Alex Gaynor. Backport of r13765 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14071 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/custom_columns/models.py | 65 --------------------- tests/modeltests/custom_columns/tests.py | 71 +++++++++++++++++++++++ 2 files changed, 71 insertions(+), 65 deletions(-) create mode 100644 tests/modeltests/custom_columns/tests.py diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py index 74691cd1bc68..651f8a61b28e 100644 --- a/tests/modeltests/custom_columns/models.py +++ b/tests/modeltests/custom_columns/models.py @@ -38,68 +38,3 @@ def __unicode__(self): class Meta: ordering = ('headline',) -__test__ = {'API_TESTS':""" -# Create a Author. ->>> a = Author(first_name='John', last_name='Smith') ->>> a.save() - ->>> a.id -1 - -# Create another author ->>> a2 = Author(first_name='Peter', last_name='Jones') ->>> a2.save() - -# Create an article ->>> art = Article(headline='Django lets you build web apps easily') ->>> art.save() ->>> art.authors = [a, a2] - -# Although the table and column names on Author have been set to custom values, -# nothing about using the Author model has changed... - -# Query the available authors ->>> Author.objects.all() -[, ] - ->>> Author.objects.filter(first_name__exact='John') -[] - ->>> Author.objects.get(first_name__exact='John') - - ->>> Author.objects.filter(firstname__exact='John') -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'firstname' into field. Choices are: article, first_name, id, last_name - ->>> a = Author.objects.get(last_name__exact='Smith') ->>> a.first_name -u'John' ->>> a.last_name -u'Smith' ->>> a.firstname -Traceback (most recent call last): - ... -AttributeError: 'Author' object has no attribute 'firstname' ->>> a.last -Traceback (most recent call last): - ... -AttributeError: 'Author' object has no attribute 'last' - -# Although the Article table uses a custom m2m table, -# nothing about using the m2m relationship has changed... - -# Get all the authors for an article ->>> art.authors.all() -[, ] - -# Get the articles for an author ->>> a.article_set.all() -[] - -# Query the authors across the m2m relation ->>> art.authors.filter(last_name='Jones') -[] - -"""} diff --git a/tests/modeltests/custom_columns/tests.py b/tests/modeltests/custom_columns/tests.py new file mode 100644 index 000000000000..e8ec21f06fcd --- /dev/null +++ b/tests/modeltests/custom_columns/tests.py @@ -0,0 +1,71 @@ +from django.core.exceptions import FieldError +from django.test import TestCase + +from models import Author, Article + + +class CustomColumnsTests(TestCase): + def test_db_column(self): + a1 = Author.objects.create(first_name="John", last_name="Smith") + a2 = Author.objects.create(first_name="Peter", last_name="Jones") + + art = Article.objects.create(headline="Django lets you build web apps easily") + art.authors = [a1, a2] + + # Although the table and column names on Author have been set to custom + # values, nothing about using the Author model has changed... + + # Query the available authors + self.assertQuerysetEqual( + Author.objects.all(), [ + "Peter Jones", "John Smith", + ], + unicode + ) + self.assertQuerysetEqual( + Author.objects.filter(first_name__exact="John"), [ + "John Smith", + ], + unicode + ) + self.assertEqual( + Author.objects.get(first_name__exact="John"), + a1, + ) + + self.assertRaises(FieldError, + lambda: Author.objects.filter(firstname__exact="John") + ) + + a = Author.objects.get(last_name__exact="Smith") + a.first_name = "John" + a.last_name = "Smith" + + self.assertRaises(AttributeError, lambda: a.firstname) + self.assertRaises(AttributeError, lambda: a.last) + + # Although the Article table uses a custom m2m table, + # nothing about using the m2m relationship has changed... + + # Get all the authors for an article + self.assertQuerysetEqual( + art.authors.all(), [ + "Peter Jones", + "John Smith", + ], + unicode + ) + # Get the articles for an author + self.assertQuerysetEqual( + a.article_set.all(), [ + "Django lets you build web apps easily", + ], + lambda a: a.headline + ) + # Query the authors across the m2m relation + self.assertQuerysetEqual( + art.authors.filter(last_name='Jones'), [ + "Peter Jones" + ], + unicode + ) From 9584b77c35752b76116d34ac9d8b57c560ef5846 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 9 Oct 2010 08:25:01 +0000 Subject: [PATCH 275/902] [1.2.X] Fixed #11509 -- Modified usage of "Web" to match our style guide in various documentation, comments and code. Thanks to timo and Simon Meers for the work on the patch Backport of r14069 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14072 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/middleware.py | 2 +- django/contrib/comments/moderation.py | 2 +- django/contrib/gis/gdal/srs.py | 2 +- django/contrib/gis/geometry/regex.py | 2 +- django/contrib/gis/maps/google/__init__.py | 2 +- django/contrib/sessions/models.py | 2 +- django/core/files/storage.py | 2 +- django/core/handlers/base.py | 2 +- django/core/servers/fastcgi.py | 2 +- django/db/models/fields/__init__.py | 4 ++-- django/db/transaction.py | 2 +- django/utils/feedgenerator.py | 2 +- django/views/csrf.py | 2 +- docs/howto/deployment/fastcgi.txt | 4 ++-- docs/howto/deployment/index.txt | 2 +- docs/howto/deployment/modpython.txt | 2 +- docs/howto/error-reporting.txt | 2 +- docs/howto/jython.txt | 2 +- docs/internals/committers.txt | 18 +++++++++--------- docs/internals/svn.txt | 6 +++--- docs/intro/index.txt | 2 +- docs/intro/tutorial01.txt | 2 +- docs/intro/tutorial03.txt | 2 +- docs/intro/whatsnext.txt | 4 ++-- docs/man/daily_cleanup.1 | 2 +- docs/man/django-admin.1 | 2 +- docs/man/gather_profile_stats.1 | 2 +- docs/misc/api-stability.txt | 2 +- docs/ref/contrib/comments/moderation.txt | 2 +- docs/ref/contrib/gis/deployment.txt | 2 +- docs/ref/contrib/gis/index.txt | 4 ++-- docs/ref/contrib/gis/install.txt | 8 ++++---- docs/ref/contrib/gis/model-api.txt | 4 ++-- docs/ref/contrib/gis/tutorial.txt | 8 ++++---- docs/ref/contrib/gis/utils.txt | 2 +- docs/ref/contrib/sitemaps.txt | 2 +- docs/ref/contrib/sites.txt | 2 +- docs/ref/forms/widgets.txt | 4 ++-- docs/ref/middleware.txt | 2 +- docs/ref/models/instances.txt | 2 +- docs/ref/models/querysets.txt | 2 +- docs/ref/request-response.txt | 2 +- docs/ref/templates/builtins.txt | 2 +- docs/ref/utils.txt | 2 +- docs/releases/1.0.txt | 2 +- docs/releases/1.1-beta-1.txt | 2 +- docs/releases/1.1.txt | 2 +- docs/topics/auth.txt | 2 +- docs/topics/conditional-view-processing.txt | 2 +- docs/topics/db/optimization.txt | 2 +- docs/topics/db/queries.txt | 2 +- docs/topics/email.txt | 2 +- docs/topics/forms/media.txt | 4 ++-- docs/topics/http/middleware.txt | 2 +- docs/topics/install.txt | 4 ++-- tests/modeltests/custom_columns/tests.py | 4 ++-- .../custom_columns_regress/tests.py | 6 +++--- tests/regressiontests/file_storage/tests.py | 2 +- 58 files changed, 85 insertions(+), 85 deletions(-) diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index 11c61b28a3b3..c1211c9234a3 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -19,7 +19,7 @@ def process_request(self, request): class RemoteUserMiddleware(object): """ - Middleware for utilizing web-server-provided authentication. + Middleware for utilizing Web-server-provided authentication. If request.user is not authenticated, then this middleware attempts to authenticate the username passed in the ``REMOTE_USER`` request header. diff --git a/django/contrib/comments/moderation.py b/django/contrib/comments/moderation.py index fd6f318a7c2d..7f429c55e015 100644 --- a/django/contrib/comments/moderation.py +++ b/django/contrib/comments/moderation.py @@ -16,7 +16,7 @@ class you want to use. ------- First, we define a simple model class which might represent entries in -a weblog:: +a Weblog:: from django.db import models diff --git a/django/contrib/gis/gdal/srs.py b/django/contrib/gis/gdal/srs.py index 93bd8416ff6c..95e71f1e31d3 100644 --- a/django/contrib/gis/gdal/srs.py +++ b/django/contrib/gis/gdal/srs.py @@ -37,7 +37,7 @@ #### Spatial Reference class. #### class SpatialReference(GDALBase): """ - A wrapper for the OGRSpatialReference object. According to the GDAL website, + A wrapper for the OGRSpatialReference object. According to the GDAL Web site, the SpatialReference object "provide[s] services to represent coordinate systems (projections and datums) and to transform between them." """ diff --git a/django/contrib/gis/geometry/regex.py b/django/contrib/gis/geometry/regex.py index 85238581bf7c..1b9e2f46f427 100644 --- a/django/contrib/gis/geometry/regex.py +++ b/django/contrib/gis/geometry/regex.py @@ -2,7 +2,7 @@ # Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure # to prevent potentially malicious input from reaching the underlying C -# library. Not a substitute for good web security programming practices. +# library. Not a substitute for good Web security programming practices. hex_regex = re.compile(r'^[0-9A-F]+$', re.I) wkt_regex = re.compile(r'^(SRID=(?P\d+);)?' r'(?P' diff --git a/django/contrib/gis/maps/google/__init__.py b/django/contrib/gis/maps/google/__init__.py index e1e38a9aff82..9be689c07abf 100644 --- a/django/contrib/gis/maps/google/__init__.py +++ b/django/contrib/gis/maps/google/__init__.py @@ -1,6 +1,6 @@ """ This module houses the GoogleMap object, used for generating - the needed javascript to embed Google Maps in a webpage. + the needed javascript to embed Google Maps in a Web page. Google(R) is a registered trademark of Google, Inc. of Mountain View, California. diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py index c3b72e6eafac..4c76ddf09a0e 100644 --- a/django/contrib/sessions/models.py +++ b/django/contrib/sessions/models.py @@ -40,7 +40,7 @@ class Session(models.Model): For complete documentation on using Sessions in your code, consult the sessions documentation that is shipped with Django (also available - on the Django website). + on the Django Web site). """ session_key = models.CharField(_('session key'), max_length=40, primary_key=True) diff --git a/django/core/files/storage.py b/django/core/files/storage.py index 4f27502167d1..17e694745020 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -116,7 +116,7 @@ def size(self, name): def url(self, name): """ Returns an absolute URL where the file's contents can be accessed - directly by a web browser. + directly by a Web browser. """ raise NotImplementedError() diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index b03c2fd71e77..45f8445f0b1c 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -208,7 +208,7 @@ def get_script_name(environ): # If Apache's mod_rewrite had a whack at the URL, Apache set either # SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any - # rewrites. Unfortunately not every webserver (lighttpd!) passes this + # rewrites. Unfortunately not every Web server (lighttpd!) passes this # information through all the time, so FORCE_SCRIPT_NAME, above, is still # needed. script_url = environ.get('SCRIPT_URL', u'') diff --git a/django/core/servers/fastcgi.py b/django/core/servers/fastcgi.py index 607cdb4628ac..da52064fd6d2 100644 --- a/django/core/servers/fastcgi.py +++ b/django/core/servers/fastcgi.py @@ -46,7 +46,7 @@ Examples: Run a "standard" fastcgi process on a file-descriptor - (for webservers which spawn your processes for you) + (for Web servers which spawn your processes for you) $ manage.py runfcgi method=threaded Run a scgi server on a TCP host/port diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index e5ed44dec5d7..fd0a29548372 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -514,7 +514,7 @@ def to_python(self, value): raise exceptions.ValidationError(self.error_messages['invalid']) def get_prep_lookup(self, lookup_type, value): - # Special-case handling for filters coming from a web request (e.g. the + # Special-case handling for filters coming from a Web request (e.g. the # admin interface). Only works for scalar values (not lists). If you're # passing in a list, you might as well make things the right type when # constructing the list. @@ -954,7 +954,7 @@ def to_python(self, value): raise exceptions.ValidationError(self.error_messages['invalid']) def get_prep_lookup(self, lookup_type, value): - # Special-case handling for filters coming from a web request (e.g. the + # Special-case handling for filters coming from a Web request (e.g. the # admin interface). Only works for scalar values (not lists). If you're # passing in a list, you might as well make things the right type when # constructing the list. diff --git a/django/db/transaction.py b/django/db/transaction.py index 3c767f1ae061..af42fd5493fe 100644 --- a/django/db/transaction.py +++ b/django/db/transaction.py @@ -288,7 +288,7 @@ def commit_on_success(using=None): This decorator activates commit on response. This way, if the view function runs successfully, a commit is made; if the viewfunc produces an exception, a rollback is made. This is one of the most common ways to do transaction - control in web apps. + control in Web apps. """ def inner_commit_on_success(func, db=None): def _commit_on_success(*args, **kw): diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index 417531725002..af167697c2e0 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -7,7 +7,7 @@ >>> feed = feedgenerator.Rss201rev2Feed( ... title=u"Poynter E-Media Tidbits", ... link=u"http://www.poynter.org/column.asp?id=31", -... description=u"A group weblog by the sharpest minds in online media/journalism/publishing.", +... description=u"A group Weblog by the sharpest minds in online media/journalism/publishing.", ... language=u"en", ... ) >>> feed.add_item( diff --git a/django/views/csrf.py b/django/views/csrf.py index c627812dcbaa..b590dd121346 100644 --- a/django/views/csrf.py +++ b/django/views/csrf.py @@ -34,7 +34,7 @@

        CSRF verification failed. Request aborted.

        {% if no_referer %}

        You are seeing this message because this HTTPS site requires a 'Referer - header' to be sent by your web browser, but none was sent. This header is + header' to be sent by your Web browser, but none was sent. This header is required for security reasons, to ensure that your browser is not being hijacked by third parties.

        diff --git a/docs/howto/deployment/fastcgi.txt b/docs/howto/deployment/fastcgi.txt index 9326ee97dc7c..a445a2d1a7c7 100644 --- a/docs/howto/deployment/fastcgi.txt +++ b/docs/howto/deployment/fastcgi.txt @@ -368,14 +368,14 @@ Forcing the URL prefix to a particular value ============================================ Because many of these fastcgi-based solutions require rewriting the URL at -some point inside the webserver, the path information that Django sees may not +some point inside the Web server, the path information that Django sees may not resemble the original URL that was passed in. This is a problem if the Django application is being served from under a particular prefix and you want your URLs from the ``{% url %}`` tag to look like the prefix, rather than the rewritten version, which might contain, for example, ``mysite.fcgi``. Django makes a good attempt to work out what the real script name prefix -should be. In particular, if the webserver sets the ``SCRIPT_URL`` (specific +should be. In particular, if the Web server sets the ``SCRIPT_URL`` (specific to Apache's mod_rewrite), or ``REDIRECT_URL`` (set by a few servers, including Apache + mod_rewrite in some situations), Django will work out the original prefix automatically. diff --git a/docs/howto/deployment/index.txt b/docs/howto/deployment/index.txt index 70c2ff8bbd26..740f9bc8d66f 100644 --- a/docs/howto/deployment/index.txt +++ b/docs/howto/deployment/index.txt @@ -1,7 +1,7 @@ Deploying Django ================ -Django's chock-full of shortcuts to make web developer's lives easier, but all +Django's chock-full of shortcuts to make Web developer's lives easier, but all those tools are of no use if you can't easily deploy your sites. Since Django's inception, ease of deployment has been a major goal. There's a number of good ways to easily deploy Django: diff --git a/docs/howto/deployment/modpython.txt b/docs/howto/deployment/modpython.txt index 3e413f9bceb7..5bb09d8c245f 100644 --- a/docs/howto/deployment/modpython.txt +++ b/docs/howto/deployment/modpython.txt @@ -317,7 +317,7 @@ project (or somewhere else) that contains something like the following: import os os.environ['PYTHON_EGG_CACHE'] = '/some/directory' -Here, ``/some/directory`` is a directory that the Apache webserver process can +Here, ``/some/directory`` is a directory that the Apache Web server process can write to. It will be used as the location for any unpacking of code the eggs need to do. diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index 1ec009dd2a27..2c197e1acbca 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -55,7 +55,7 @@ not found" errors). Django sends emails about 404 errors when: If those conditions are met, Django will e-mail the users listed in the :setting:`MANAGERS` setting whenever your code raises a 404 and the request has a referer. (It doesn't bother to e-mail for 404s that don't have a referer -- -those are usually just people typing in broken URLs or broken web 'bots). +those are usually just people typing in broken URLs or broken Web 'bots). You can tell Django to stop reporting particular 404s by tweaking the :setting:`IGNORABLE_404_ENDS` and :setting:`IGNORABLE_404_STARTS` settings. Both diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt index 673c9360bd51..1bf8d6c1f455 100644 --- a/docs/howto/jython.txt +++ b/docs/howto/jython.txt @@ -51,7 +51,7 @@ on top of Jython. .. _`django-jython`: http://code.google.com/p/django-jython/ To install it, follow the `installation instructions`_ detailed on the project -website. Also, read the `database backends`_ documentation there. +Web site. Also, read the `database backends`_ documentation there. .. _`installation instructions`: http://code.google.com/p/django-jython/wiki/Install .. _`database backends`: http://code.google.com/p/django-jython/wiki/DatabaseBackends diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 324a37840f52..d3d95e099b1f 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -20,10 +20,10 @@ Journal-World`_ of Lawrence, Kansas, USA. Adrian lives in Chicago, USA. `Simon Willison`_ - Simon is a well-respected web developer from England. He had a one-year + Simon is a well-respected Web developer from England. He had a one-year internship at World Online, during which time he and Adrian developed Django from scratch. The most enthusiastic Brit you'll ever meet, he's passionate - about best practices in web development and maintains a well-read + about best practices in Web development and maintains a well-read `web-development blog`_. Simon lives in Brighton, England. @@ -33,13 +33,13 @@ Journal-World`_ of Lawrence, Kansas, USA. around Django and related open source technologies. A good deal of Jacob's work time is devoted to working on Django. Jacob previously worked at World Online, where Django was invented, where he was the lead developer of - Ellington, a commercial web publishing platform for media companies. + Ellington, a commercial Web publishing platform for media companies. Jacob lives in Lawrence, Kansas, USA. `Wilson Miner`_ Wilson's design-fu is what makes Django look so nice. He designed the - website you're looking at right now, as well as Django's acclaimed admin + Web site you're looking at right now, as well as Django's acclaimed admin interface. Wilson is the designer for EveryBlock_. Wilson lives in San Francisco, USA. @@ -89,7 +89,7 @@ and have free rein to hack on all parts of Django. Russell studied physics as an undergraduate, and studied neural networks for his PhD. His first job was with a startup in the defense industry developing simulation frameworks. Over time, mostly through work with Django, he's - become more involved in web development. + become more involved in Web development. Russell has helped with several major aspects of Django, including a couple major internal refactorings, creation of the test system, and more. @@ -134,7 +134,7 @@ Joseph Kocherhans `Brian Rosner`_ Brian is currently the tech lead at Eldarion_ managing and developing - Django / Pinax_ based websites. He enjoys learning more about programming + Django / Pinax_ based Web sites. He enjoys learning more about programming languages and system architectures and contributing to open source projects. Brian is the host of the `Django Dose`_ podcasts. @@ -180,7 +180,7 @@ Karen Tracey Karen has a background in distributed operating systems (graduate school), communications software (industry) and crossword puzzle construction (freelance). The last of these brought her to Django, in late 2006, when - she set out to put a web front-end on her crossword puzzle database. + she set out to put a Web front-end on her crossword puzzle database. That done, she stuck around in the community answering questions, debugging problems, etc. -- because coding puzzles are as much fun as word puzzles. @@ -190,7 +190,7 @@ Karen Tracey Jannis graduated in media design from `Bauhaus-University Weimar`_, is the author of a number of pluggable Django apps and likes to contribute to Open Source projects like Pinax_. He currently works as - a freelance web developer and designer. + a freelance Web developer and designer. Jannis lives in Berlin, Germany. @@ -262,7 +262,7 @@ Specialists `James Bennett`_ James is Django's release manager; he also contributes to the documentation. - James came to web development from philosophy when he discovered + James came to Web development from philosophy when he discovered that programmers get to argue just as much while collecting much better pay. He lives in Lawrence, Kansas, where he works for the Journal-World developing Ellington. He `keeps a blog`_, has diff --git a/docs/internals/svn.txt b/docs/internals/svn.txt index c66e494e5f06..9efbe28913e1 100644 --- a/docs/internals/svn.txt +++ b/docs/internals/svn.txt @@ -22,7 +22,7 @@ The Django source code repository uses `Subversion`_ to track changes to the code over time, so you'll need a copy of the Subversion client (a program called ``svn``) on your computer, and you'll want to familiarize yourself with the basics of how Subversion -works. Subversion's web site offers downloads for various operating +works. Subversion's Web site offers downloads for various operating systems, and `a free online book`_ is available to help you get up to speed with using Subversion. @@ -34,7 +34,7 @@ repository address instead. At the top level of the repository are two directories: ``django`` contains the full source code for all Django releases, while ``djangoproject.com`` contains the source code and templates for the `djangoproject.com `_ -web site. For trying out in-development Django code, or contributing +Web site. For trying out in-development Django code, or contributing to Django, you'll always want to check out code from some location in the ``django`` directory. @@ -58,7 +58,7 @@ into three areas: .. _Subversion: http://subversion.tigris.org/ .. _a free online book: http://svnbook.red-bean.com/ -.. _A friendly web-based interface for browsing the code: http://code.djangoproject.com/browser/ +.. _A friendly Web-based interface for browsing the code: http://code.djangoproject.com/browser/ Working with Django's trunk diff --git a/docs/intro/index.txt b/docs/intro/index.txt index 90ee627ba6db..bc61be778a25 100644 --- a/docs/intro/index.txt +++ b/docs/intro/index.txt @@ -1,7 +1,7 @@ Getting started =============== -New to Django? Or to web development in general? Well, you came to the right +New to Django? Or to Web development in general? Well, you came to the right place: read this material to quickly get up and running. .. toctree:: diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index be98742469ba..a68afea83d67 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -263,7 +263,7 @@ so you can focus on writing code rather than creating directories. .. admonition:: Projects vs. apps What's the difference between a project and an app? An app is a Web - application that does something -- e.g., a weblog system, a database of + application that does something -- e.g., a Weblog system, a database of public records or a simple poll app. A project is a collection of configuration and apps for a particular Web site. A project can contain multiple apps. An app can be in multiple projects. diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 6da0ad4029b7..af686bcbdee4 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -10,7 +10,7 @@ Philosophy ========== A view is a "type" of Web page in your Django application that generally serves -a specific function and has a specific template. For example, in a weblog +a specific function and has a specific template. For example, in a Weblog application, you might have the following views: * Blog homepage -- displays the latest few entries. diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index fe385ffd9ac0..00c1654c19e1 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -36,7 +36,7 @@ Django's main documentation is broken up into "chunks" designed to fill different needs: * The :doc:`introductory material ` is designed for people new - to Django -- or to web development in general. It doesn't cover anything + to Django -- or to Web development in general. It doesn't cover anything in depth, but instead gives a high-level overview of how developing in Django "feels". @@ -166,7 +166,7 @@ You can get a local copy of the HTML documentation following a few easy steps: * Django's documentation uses a system called Sphinx__ to convert from plain text to HTML. You'll need to install Sphinx by either downloading - and installing the package from the Sphinx website, or by Python's + and installing the package from the Sphinx Web site, or by Python's ``easy_install``: .. code-block:: bash diff --git a/docs/man/daily_cleanup.1 b/docs/man/daily_cleanup.1 index 444d4d0e6e52..dfcde1dff758 100644 --- a/docs/man/daily_cleanup.1 +++ b/docs/man/daily_cleanup.1 @@ -1,6 +1,6 @@ .TH "daily_cleanup.py" "1" "August 2007" "Django Project" "" .SH "NAME" -daily_cleanup.py \- Database clean-up for the Django web framework +daily_cleanup.py \- Database clean-up for the Django Web framework .SH "SYNOPSIS" .B daily_cleanup.py diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 index ce3fdb16754b..a931d419dab0 100644 --- a/docs/man/django-admin.1 +++ b/docs/man/django-admin.1 @@ -1,6 +1,6 @@ .TH "django-admin.py" "1" "March 2008" "Django Project" "" .SH "NAME" -django\-admin.py \- Utility script for the Django web framework +django\-admin.py \- Utility script for the Django Web framework .SH "SYNOPSIS" .B django\-admin.py .I diff --git a/docs/man/gather_profile_stats.1 b/docs/man/gather_profile_stats.1 index 5ff13d8e69ec..fc56ee229134 100644 --- a/docs/man/gather_profile_stats.1 +++ b/docs/man/gather_profile_stats.1 @@ -1,6 +1,6 @@ .TH "gather_profile_stats.py" "1" "August 2007" "Django Project" "" .SH "NAME" -gather_profile_stats.py \- Performance analysis tool for the Django web +gather_profile_stats.py \- Performance analysis tool for the Django Web framework .SH "SYNOPSIS" .B python gather_profile_stats.py diff --git a/docs/misc/api-stability.txt b/docs/misc/api-stability.txt index 70e522159282..456d84b45f59 100644 --- a/docs/misc/api-stability.txt +++ b/docs/misc/api-stability.txt @@ -125,7 +125,7 @@ Contributed applications (``django.contrib``) While we'll make every effort to keep these APIs stable -- and have no plans to break any contrib apps -- this is an area that will have more flux between -releases. As the web evolves, Django must evolve with it. +releases. As the Web evolves, Django must evolve with it. However, any changes to contrib apps will come with an important guarantee: we'll make sure it's always possible to use an older version of a contrib app if diff --git a/docs/ref/contrib/comments/moderation.txt b/docs/ref/contrib/comments/moderation.txt index 198f78fa8905..519bc5edd15a 100644 --- a/docs/ref/contrib/comments/moderation.txt +++ b/docs/ref/contrib/comments/moderation.txt @@ -29,7 +29,7 @@ and uses a two-step process to enable moderation for any given model: model class and the class which specifies its moderation options. A simple example is the best illustration of this. Suppose we have the -following model, which would represent entries in a weblog:: +following model, which would represent entries in a Weblog:: from django.db import models diff --git a/docs/ref/contrib/gis/deployment.txt b/docs/ref/contrib/gis/deployment.txt index c8dde3e540dc..035b23fc8e05 100644 --- a/docs/ref/contrib/gis/deployment.txt +++ b/docs/ref/contrib/gis/deployment.txt @@ -8,7 +8,7 @@ Deploying GeoDjango not thread safe at this time. Thus, it is *highly* recommended to not use threading when deploying -- in other words, use a an appropriate configuration of Apache or the prefork method - when using FastCGI through another web server. + when using FastCGI through another Web server. Apache ====== diff --git a/docs/ref/contrib/gis/index.txt b/docs/ref/contrib/gis/index.txt index 074fa3a7b604..c4959e0f846e 100644 --- a/docs/ref/contrib/gis/index.txt +++ b/docs/ref/contrib/gis/index.txt @@ -9,8 +9,8 @@ GeoDjango .. module:: django.contrib.gis :synopsis: Geographic Information System (GIS) extensions for Django -GeoDjango intends to be a world-class geographic web framework. Its goal is to -make it as easy as possible to build GIS web applications and harness the power +GeoDjango intends to be a world-class geographic Web framework. Its goal is to +make it as easy as possible to build GIS Web applications and harness the power of spatially enabled data. .. toctree:: diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index ae36e167aef0..90191dcec605 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -147,7 +147,7 @@ internal geometry representation used by GeoDjango (it's behind the "lazy" geometries). Specifically, the C API library is called (e.g., ``libgeos_c.so``) directly from Python using ctypes. -First, download GEOS 3.2 from the refractions website and untar the source +First, download GEOS 3.2 from the refractions Web site and untar the source archive:: $ wget http://download.osgeo.org/geos/geos-3.2.2.tar.bz2 @@ -640,7 +640,7 @@ If you can't find the solution to your problem here then participate in the community! You can: * Join the ``#geodjango`` IRC channel on FreeNode (may be accessed on the - web via `Mibbit`__). Please be patient and polite -- while you may not + Web via `Mibbit`__). Please be patient and polite -- while you may not get an immediate response, someone will attempt to answer your question as soon as they see it. * Ask your question on the `GeoDjango`__ mailing list. @@ -1085,7 +1085,7 @@ Windows XP Python ^^^^^^ -First, download the `Python 2.6 installer`__ from the Python website. Next, +First, download the `Python 2.6 installer`__ from the Python Web site. Next, execute the installer and use defaults, e.g., keep 'Install for all users' checked and the installation path set as ``C:\Python26``. @@ -1101,7 +1101,7 @@ PostgreSQL ^^^^^^^^^^ First, select a mirror and download the latest `PostgreSQL 8.3 installer`__ from -the EnterpriseDB website. +the EnterpriseDB Web site. .. note:: diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index cf73747463a1..b6d92dd24ce0 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -97,7 +97,7 @@ corresponds to the projection system that will be used to interpret the data in the spatial database. [#fnsrid]_ Projection systems give the context to the coordinates that specify a location. Although the details of `geodesy`__ are beyond the scope of this documentation, the general problem is that the earth -is spherical and representations of the earth (e.g., paper maps, web maps) +is spherical and representations of the earth (e.g., paper maps, Web maps) are not. Most people are familiar with using latitude and longitude to reference a @@ -133,7 +133,7 @@ Additional Resources: * `spatialreference.org`__: A Django-powered database of spatial reference systems. -* `The State Plane Coordinate System`__: A website covering the various +* `The State Plane Coordinate System`__: A Web site covering the various projection systems used in the United States. Much of the U.S. spatial data encountered will be in one of these coordinate systems rather than in a geographic coordinate system such as WGS84. diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index e9599866ff4b..9deeb7887317 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -6,8 +6,8 @@ Introduction ============ GeoDjango is an add-on for Django that turns it into a world-class geographic -web framework. GeoDjango strives to make at as simple as possible to create -geographic web applications, like location-based services. Some features include: +Web framework. GeoDjango strives to make at as simple as possible to create +geographic Web applications, like location-based services. Some features include: * Django model fields for `OGC`_ geometries. * Extensions to Django's ORM for the querying and manipulation of spatial data. @@ -25,7 +25,7 @@ yourself with basic Django concepts. please consult the :ref:`installation documentation ` 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. [#]_ @@ -197,7 +197,7 @@ 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`_ website, this shows +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. diff --git a/docs/ref/contrib/gis/utils.txt b/docs/ref/contrib/gis/utils.txt index 8b1480283154..9f8e518d09d1 100644 --- a/docs/ref/contrib/gis/utils.txt +++ b/docs/ref/contrib/gis/utils.txt @@ -8,7 +8,7 @@ GeoDjango Utilities :synopsis: GeoDjango's collection of utilities. The :mod:`django.contrib.gis.utils` module contains various utilities that are -useful in creating geospatial web applications. +useful in creating geospatial Web applications. .. toctree:: :maxdepth: 2 diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 7a66cbe9a915..e8bfcbc60bb3 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -76,7 +76,7 @@ Sitemap classes A :class:`~django.contrib.sitemaps.Sitemap` class is a simple Python class that represents a "section" of entries in your sitemap. For example, one :class:`~django.contrib.sitemaps.Sitemap` class could represent -all the entries of your weblog, while another could represent all of the +all the entries of your Weblog, while another could represent all of the events in your events calendar. In the simplest case, all these sections get lumped together into one diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 54ca668300cc..6d795d056cb3 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -3,7 +3,7 @@ The "sites" framework ===================== .. module:: django.contrib.sites - :synopsis: Lets you operate multiple web sites from the same database and + :synopsis: Lets you operate multiple Web sites from the same database and Django project Django comes with an optional "sites" framework. It's a hook for associating diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 59847509437a..9d78b8438ef5 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -197,7 +197,7 @@ Customizing widget instances When Django renders a widget as HTML, it only renders the bare minimum HTML - Django doesn't add a class definition, or any other widget-specific attributes. This means that all 'TextInput' widgets will appear the same -on your web page. +on your Web page. If you want to make one widget look different to another, you need to specify additional attributes for each widget. When you specify a @@ -222,7 +222,7 @@ each widget will be rendered exactly the same::

        -On a real web page, you probably don't want every widget to look the same. You +On a real Web page, you probably don't want every widget to look the same. You might want a larger input element for the comment, and you might want the 'name' widget to have some special CSS class. To do this, you use the ``attrs`` argument when creating the widget: diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 290ea2736da0..de2a99f90298 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -193,7 +193,7 @@ Transaction middleware ---------------------- .. module:: django.middleware.transaction - :synopsis: Middleware binding a database transaction to each web request. + :synopsis: Middleware binding a database transaction to each Web request. .. class:: django.middleware.transaction.TransactionMiddleware diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 8bb6cf9cc54c..b11a7e193d61 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -9,7 +9,7 @@ material presented in the :doc:`model ` and :doc:`database query ` guides, so you'll probably want to read and understand those documents before reading this one. -Throughout this reference we'll use the :ref:`example weblog models +Throughout this reference we'll use the :ref:`example Weblog models ` presented in the :doc:`database query guide `. diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index c1c586aada97..b366ef33a17a 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -9,7 +9,7 @@ material presented in the :doc:`model ` and :doc:`database query ` guides, so you'll probably want to read and understand those documents before reading this one. -Throughout this reference we'll use the :ref:`example weblog models +Throughout this reference we'll use the :ref:`example Weblog models ` presented in the :doc:`database query guide `. diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index a944abeddc5d..0eaa4b6e22aa 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -137,7 +137,7 @@ All attributes except ``session`` should be considered read-only. * ``QUERY_STRING`` -- The query string, as a single (unparsed) string. * ``REMOTE_ADDR`` -- The IP address of the client. * ``REMOTE_HOST`` -- The hostname of the client. - * ``REMOTE_USER`` -- The user authenticated by the web server, if any. + * ``REMOTE_USER`` -- The user authenticated by the Web server, if any. * ``REQUEST_METHOD`` -- A string such as ``"GET"`` or ``"POST"``. * ``SERVER_NAME`` -- The hostname of the server. * ``SERVER_PORT`` -- The port of the server. diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 01f39e7b893d..c0ae8cf36d23 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2102,7 +2102,7 @@ See the :doc:`markup documentation `. django.contrib.webdesign ~~~~~~~~~~~~~~~~~~~~~~~~ -A collection of template tags that can be useful while designing a website, +A collection of template tags that can be useful while designing a Web site, such as a generator of Lorem Ipsum text. See :doc:`/ref/contrib/webdesign`. i18n diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 39b01df0becf..e4ce7c4f8643 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -193,7 +193,7 @@ Sample usage:: >>> feed = feedgenerator.Rss201rev2Feed( ... title=u"Poynter E-Media Tidbits", ... link=u"http://www.poynter.org/column.asp?id=31", - ... description=u"A group weblog by the sharpest minds in online media/journalism/publishing.", + ... description=u"A group Weblog by the sharpest minds in online media/journalism/publishing.", ... language=u"en", ... ) >>> feed.add_item( diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt index 359490aad3a7..a2b6083e38cb 100644 --- a/docs/releases/1.0.txt +++ b/docs/releases/1.0.txt @@ -6,7 +6,7 @@ Welcome to Django 1.0! We've been looking forward to this moment for over three years, and it's finally here. Django 1.0 represents a the largest milestone in Django's development to -date: a web framework that a group of perfectionists can truly be proud of. +date: a Web framework that a group of perfectionists can truly be proud of. Django 1.0 represents over three years of community development as an Open Source project. Django's received contributions from hundreds of developers, diff --git a/docs/releases/1.1-beta-1.txt b/docs/releases/1.1-beta-1.txt index 83423962b316..535f81890866 100644 --- a/docs/releases/1.1-beta-1.txt +++ b/docs/releases/1.1-beta-1.txt @@ -142,7 +142,7 @@ release, including: notably, the memcached backend -- these operations will be atomic, and quite fast. - * Django now can :doc:`easily delegate authentication to the web server + * Django now can :doc:`easily delegate authentication to the Web server ` via a new authentication backend that supports the standard ``REMOTE_USER`` environment variable used for this purpose. diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 39cb0ab2b0bd..3ca8344fb15d 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -426,7 +426,7 @@ Other new features and changes introduced since Django 1.0 include: notably, the memcached backend -- these operations will be atomic, and quite fast. -* Django now can :doc:`easily delegate authentication to the web server +* Django now can :doc:`easily delegate authentication to the Web server ` via a new authentication backend that supports the standard ``REMOTE_USER`` environment variable used for this purpose. diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 4a6e30fc0017..5d1cc189f443 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -658,7 +658,7 @@ How to log a user out When you call :func:`~django.contrib.auth.logout()`, the session data for the current request is completely cleaned out. All existing data is - removed. This is to prevent another person from using the same web browser + removed. This is to prevent another person from using the same Web browser to log in and have access to the previous user's session data. If you want to put anything into the session that will be available to the user immediately after logging out, do that *after* calling diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt index b5da14082776..c631a136b46c 100644 --- a/docs/topics/conditional-view-processing.txt +++ b/docs/topics/conditional-view-processing.txt @@ -6,7 +6,7 @@ Conditional View Processing HTTP clients can send a number of headers to tell the server about copies of a resource that they have already seen. This is commonly used when retrieving a -web page (using an HTTP ``GET`` request) to avoid sending all the data for +Web page (using an HTTP ``GET`` request) to avoid sending all the data for something the client has already retrieved. However, the same headers can be used for all HTTP methods (``POST``, ``PUT``, ``DELETE``, etc). diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index baf8cfa26852..6700e1c6e7d6 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -68,7 +68,7 @@ Understand cached attributes As well as caching of the whole ``QuerySet``, there is caching of the result of attributes on ORM objects. In general, attributes that are not callable will be -cached. For example, assuming the :ref:`example weblog models +cached. For example, assuming the :ref:`example Weblog models `: >>> entry = Entry.objects.get(id=1) diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 5c4941fbf2fd..3d598f87a1e1 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -11,7 +11,7 @@ API. Refer to the :doc:`data model reference ` for full details of all the various model lookup options. Throughout this guide (and in the reference), we'll refer to the following -models, which comprise a weblog application: +models, which comprise a Weblog application: .. _queryset-model-example: diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 31092b0aaaef..33564b6f1961 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -574,7 +574,7 @@ Testing e-mail sending ====================== There are times when you do not want Django to send e-mails at -all. For example, while developing a website, you probably don't want +all. For example, while developing a Web site, you probably don't want to send out thousands of e-mails -- but you may want to validate that e-mails will be sent to the right people under the right conditions, and that those e-mails will contain the correct content. diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt index 64cf50743dd4..548095d83b0f 100644 --- a/docs/topics/forms/media.txt +++ b/docs/topics/forms/media.txt @@ -1,7 +1,7 @@ Form Media ========== -Rendering an attractive and easy-to-use web form requires more than just +Rendering an attractive and easy-to-use Web form requires more than just HTML - it also requires CSS stylesheets, and if you want to use fancy "Web2.0" widgets, you may also need to include some JavaScript on each page. The exact combination of CSS and JavaScript that is required for @@ -14,7 +14,7 @@ you can define a custom Calendar widget. This widget can then be associated with the CSS and JavaScript that is required to render the calendar. When the Calendar widget is used on a form, Django is able to identify the CSS and JavaScript files that are required, and provide the list of file names -in a form suitable for easy inclusion on your web page. +in a form suitable for easy inclusion on your Web page. .. admonition:: Media and Django Admin diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index eee398a3dc9f..24d2a8ef7d56 100644 --- a/docs/topics/http/middleware.txt +++ b/docs/topics/http/middleware.txt @@ -152,7 +152,7 @@ of caveats: define ``__init__`` as requiring any arguments. * Unlike the ``process_*`` methods which get called once per request, - ``__init__`` gets called only *once*, when the web server starts up. + ``__init__`` gets called only *once*, when the Web server starts up. Marking middleware as unused ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/install.txt b/docs/topics/install.txt index cb6c29f5f897..ad99b33890bd 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -28,7 +28,7 @@ Install Apache and mod_wsgi ============================= If you just want to experiment with Django, skip ahead to the next section; -Django includes a lightweight web server you can use for testing, so you won't +Django includes a lightweight Web server you can use for testing, so you won't need to set up Apache until you're ready to deploy Django in production. If you want to use Django on a production site, use Apache with `mod_wsgi`_. @@ -274,7 +274,7 @@ latest bug fixes and improvements, follow these instructions: .. code-block:: bash ln -s WORKING-DIR/django-trunk/django/bin/django-admin.py /usr/local/bin - + (In the above line, change WORKING-DIR to match the full path to your new ``django-trunk`` directory.) diff --git a/tests/modeltests/custom_columns/tests.py b/tests/modeltests/custom_columns/tests.py index e8ec21f06fcd..f38f087c8967 100644 --- a/tests/modeltests/custom_columns/tests.py +++ b/tests/modeltests/custom_columns/tests.py @@ -9,7 +9,7 @@ def test_db_column(self): a1 = Author.objects.create(first_name="John", last_name="Smith") a2 = Author.objects.create(first_name="Peter", last_name="Jones") - art = Article.objects.create(headline="Django lets you build web apps easily") + art = Article.objects.create(headline="Django lets you build Web apps easily") art.authors = [a1, a2] # Although the table and column names on Author have been set to custom @@ -58,7 +58,7 @@ def test_db_column(self): # Get the articles for an author self.assertQuerysetEqual( a.article_set.all(), [ - "Django lets you build web apps easily", + "Django lets you build Web apps easily", ], lambda a: a.headline ) diff --git a/tests/regressiontests/custom_columns_regress/tests.py b/tests/regressiontests/custom_columns_regress/tests.py index 0587ab70704f..8507601ef9f0 100644 --- a/tests/regressiontests/custom_columns_regress/tests.py +++ b/tests/regressiontests/custom_columns_regress/tests.py @@ -22,7 +22,7 @@ def setUp(self): self.authors = [self.a1, self.a2] def test_basic_creation(self): - art = Article(headline='Django lets you build web apps easily', primary_author=self.a1) + art = Article(headline='Django lets you build Web apps easily', primary_author=self.a1) art.save() art.authors = [self.a1, self.a2] @@ -68,7 +68,7 @@ def test_author_get_attributes(self): ) def test_m2m_table(self): - art = Article.objects.create(headline='Django lets you build web apps easily', primary_author=self.a1) + art = Article.objects.create(headline='Django lets you build Web apps easily', primary_author=self.a1) art.authors = self.authors self.assertQuerysetEqual( art.authors.all().order_by('last_name'), @@ -76,7 +76,7 @@ def test_m2m_table(self): ) self.assertQuerysetEqual( self.a1.article_set.all(), - [''] + [''] ) self.assertQuerysetEqual( art.authors.filter(last_name='Jones'), diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index 46411aca8759..94b307971363 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -142,7 +142,7 @@ def test_file_path(self): def test_file_url(self): """ - File storage returns a url to access a given file from the web. + File storage returns a url to access a given file from the Web. """ self.assertEqual(self.storage.url('test.file'), '%s%s' % (self.storage.base_url, 'test.file')) From 91310e759a616d7bc26be5274417279a42d9ff4a Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 9 Oct 2010 08:26:29 +0000 Subject: [PATCH 276/902] [1.2.X] Fixed #13538 -- Clarified query examples with more explicit import statements and model vs. instance differentiation. Thanks to yipengh87 and kmtracey for the report, and timo for the patch. Backport of [14070] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14073 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/queries.txt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 3d598f87a1e1..e8966807b127 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -94,18 +94,23 @@ the database until you explicitly call ``save()``. Saving ``ForeignKey`` and ``ManyToManyField`` fields ---------------------------------------------------- -Updating ``ForeignKey`` fields works exactly the same way as saving a normal -field; simply assign an object of the right type to the field in question:: +Updating a ``ForeignKey`` field works exactly the same way as saving a normal +field; simply assign an object of the right type to the field in question. +This example updates the ``blog`` attribute of an ``Entry`` instance ``entry``:: + >>> from mysite.blog.models import Entry + >>> entry = Entry.objects.get(pk=1) >>> cheese_blog = Blog.objects.get(name="Cheddar Talk") >>> entry.blog = cheese_blog >>> entry.save() Updating a ``ManyToManyField`` works a little differently; use the ``add()`` -method on the field to add a record to the relation:: +method on the field to add a record to the relation. This example adds the +``Author`` instance ``joe`` to the ``entry`` object:: - >> joe = Author.objects.create(name="Joe") - >> entry.authors.add(joe) + >>> from mysite.blog.models import Author + >>> joe = Author.objects.create(name="Joe") + >>> entry.authors.add(joe) Django will complain if you try to assign or add an object of the wrong type. From a96e50b76b38edb94356bb5d346ef96067175906 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 9 Oct 2010 10:01:01 +0000 Subject: [PATCH 277/902] [1.2.X] Adds documentation for QuerySet.update() method. Thanks to dwillis and timo for the majority of the wording. Backport of [14074] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14075 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/querysets.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index b366ef33a17a..18cc1c679e72 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1221,6 +1221,31 @@ that it will be at some point, then using ``some_query_set.exists()`` will do more overall work (an additional query) than simply using ``bool(some_query_set)``. +``update(**kwargs)`` +~~~~~~~~~~~~~~~~~~~~ + +.. method:: update(**kwargs) + +Performs an SQL update query for the specified fields, and returns +the number of rows affected. The ``update()`` method is applied instantly and +the only restriction on the :class:`QuerySet` that is updated is that it can +only update columns in the model's main table. Filtering based on related +fields is still possible. You cannot call ``update()`` on a +:class:`QuerySet` that has had a slice taken or can otherwise no longer be +filtered. + +For example, if you wanted to update all the entries in a particular blog +to use the same headline:: + + >>> b = Blog.objects.get(pk=1) + + # Update all the headlines belonging to this Blog. + >>> Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same') + +The ``update()`` method does a bulk update and does not call any ``save()`` +methods on your models, nor does it emit the ``pre_save`` or ``post_save`` +signals (which are a consequence of calling ``save()``). + .. _field-lookups: Field lookups From 4241197b40c0facd38a672eaf7594a01a091edae Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 9 Oct 2010 10:23:48 +0000 Subject: [PATCH 278/902] [1.2.X] Improved example to account for environments where cStringIO is not available. Thanks to rubic for the report and niall for the patch. Backport of [14076] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14077 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/outputting-pdf.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/howto/outputting-pdf.txt b/docs/howto/outputting-pdf.txt index 32e38aebc675..67950d03f248 100644 --- a/docs/howto/outputting-pdf.txt +++ b/docs/howto/outputting-pdf.txt @@ -101,7 +101,11 @@ cStringIO_ library as a temporary holding place for your PDF file. The cStringIO library provides a file-like object interface that is particularly efficient. Here's the above "Hello World" example rewritten to use ``cStringIO``:: - from cStringIO import StringIO + # Fall back to StringIO in environments where cStringIO is not available + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO from reportlab.pdfgen import canvas from django.http import HttpResponse From e8d50382188b475ce4b0d9dd47566200fa494971 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 9 Oct 2010 14:47:04 +0000 Subject: [PATCH 279/902] [1.2.X] Ensure that the sitemaps test deactivates it's locale, so that subsequent tests aren't run in French. Backport of r14078 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14080 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sitemaps/tests/basic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django/contrib/sitemaps/tests/basic.py b/django/contrib/sitemaps/tests/basic.py index 80f336e50aaf..552fb3ca600b 100644 --- a/django/contrib/sitemaps/tests/basic.py +++ b/django/contrib/sitemaps/tests/basic.py @@ -5,7 +5,7 @@ from django.contrib.sites.models import Site from django.test import TestCase from django.utils.formats import localize -from django.utils.translation import activate +from django.utils.translation import activate, deactivate class SitemapTests(TestCase): @@ -44,6 +44,7 @@ def test_localized_priority(self): response = self.client.get('/simple/sitemap.xml') self.assertContains(response, '0.5') self.assertContains(response, '%s' % date.today().strftime('%Y-%m-%d')) + deactivate() def test_generic_sitemap(self): "A minimal generic sitemap can be rendered" From 935f6873dd9bfa1582575c0309f0f8dcab0c0102 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 9 Oct 2010 14:47:28 +0000 Subject: [PATCH 280/902] [1.2.X] Fixed #14427 -- Added --bisect and --pair flags to runtests.py, making it easier to find pairs of tests that fail when run together. Backport of r14079 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14081 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/runtests.py | 155 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 134 insertions(+), 21 deletions(-) diff --git a/tests/runtests.py b/tests/runtests.py index 794f0dca9491..95e3342b6ccc 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import os, sys, traceback +import os, subprocess, sys, traceback import unittest import django.contrib as contrib @@ -85,16 +85,17 @@ def runTest(self): self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected)) self.assert_(not missing, "Missing Errors: " + '\n'.join(missing)) -def django_tests(verbosity, interactive, failfast, test_labels): +def setup(verbosity, test_labels): from django.conf import settings - - old_installed_apps = settings.INSTALLED_APPS - old_root_urlconf = getattr(settings, "ROOT_URLCONF", "") - old_template_dirs = settings.TEMPLATE_DIRS - old_use_i18n = settings.USE_I18N - old_login_url = settings.LOGIN_URL - old_language_code = settings.LANGUAGE_CODE - old_middleware_classes = settings.MIDDLEWARE_CLASSES + state = { + 'INSTALLED_APPS': settings.INSTALLED_APPS, + 'ROOT_URLCONF': getattr(settings, "ROOT_URLCONF", ""), + 'TEMPLATE_DIRS': settings.TEMPLATE_DIRS, + 'USE_I18N': settings.USE_I18N, + 'LOGIN_URL': settings.LOGIN_URL, + 'LANGUAGE_CODE': settings.LANGUAGE_CODE, + 'MIDDLEWARE_CLASSES': settings.MIDDLEWARE_CLASSES, + } # Redirect some settings for the duration of these tests. settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS @@ -136,6 +137,18 @@ def django_tests(verbosity, interactive, failfast, test_labels): if model_label not in settings.INSTALLED_APPS: settings.INSTALLED_APPS.append(model_label) + return state + +def teardown(state): + from django.conf import settings + # Restore the old settings. + for key, value in state.items(): + setattr(settings, key, value) + +def django_tests(verbosity, interactive, failfast, test_labels): + from django.conf import settings + state = setup(verbosity, test_labels) + # Add tests for invalid models. extra_tests = [] for model_dir, model_name in get_invalid_models(): @@ -169,17 +182,105 @@ def django_tests(verbosity, interactive, failfast, test_labels): test_runner = TestRunner(verbosity=verbosity, interactive=interactive, failfast=failfast) failures = test_runner.run_tests(test_labels, extra_tests=extra_tests) - if failures: - sys.exit(bool(failures)) + teardown(state) + return failures - # Restore the old settings. - settings.INSTALLED_APPS = old_installed_apps - settings.ROOT_URLCONF = old_root_urlconf - settings.TEMPLATE_DIRS = old_template_dirs - settings.USE_I18N = old_use_i18n - settings.LANGUAGE_CODE = old_language_code - settings.LOGIN_URL = old_login_url - settings.MIDDLEWARE_CLASSES = old_middleware_classes + +def bisect_tests(bisection_label, options, test_labels): + state = setup(int(options.verbosity), test_labels) + + if not test_labels: + # Get the full list of test labels to use for bisection + from django.db.models.loading import get_apps + test_labels = [app.__name__.split('.')[-2] for app in get_apps()] + + print '***** Bisecting test suite:',' '.join(test_labels) + + # Make sure the bisection point isn't in the test list + # Also remove tests that need to be run in specific combinations + for label in [bisection_label, 'model_inheritance_same_model_name']: + try: + test_labels.remove(label) + except ValueError: + pass + + subprocess_args = ['python','runtests.py', '--settings=%s' % options.settings] + if options.failfast: + subprocess_args.append('--failfast') + if options.verbosity: + subprocess_args.append('--verbosity=%s' % options.verbosity) + if not options.interactive: + subprocess_args.append('--noinput') + + iteration = 1 + while len(test_labels) > 1: + midpoint = len(test_labels)/2 + test_labels_a = test_labels[:midpoint] + [bisection_label] + test_labels_b = test_labels[midpoint:] + [bisection_label] + print '***** Pass %da: Running the first half of the test suite' % iteration + print '***** Test labels:',' '.join(test_labels_a) + failures_a = subprocess.call(subprocess_args + test_labels_a) + + print '***** Pass %db: Running the second half of the test suite' % iteration + print '***** Test labels:',' '.join(test_labels_b) + print + failures_b = subprocess.call(subprocess_args + test_labels_b) + + if failures_a and not failures_b: + print "***** Problem found in first half. Bisecting again..." + iteration = iteration + 1 + test_labels = test_labels_a[:-1] + elif failures_b and not failures_a: + print "***** Problem found in second half. Bisecting again..." + iteration = iteration + 1 + test_labels = test_labels_b[:-1] + elif failures_a and failures_b: + print "***** Multiple sources of failure found" + break + else: + print "***** No source of failure found... try pair execution (--pair)" + break + + if len(test_labels) == 1: + print "***** Source of error:",test_labels[0] + teardown(state) + +def paired_tests(paired_test, options, test_labels): + state = setup(int(options.verbosity), test_labels) + + if not test_labels: + print "" + # Get the full list of test labels to use for bisection + from django.db.models.loading import get_apps + test_labels = [app.__name__.split('.')[-2] for app in get_apps()] + + print '***** Trying paired execution' + + # Make sure the bisection point isn't in the test list + # Also remove tests that need to be run in specific combinations + for label in [paired_test, 'model_inheritance_same_model_name']: + try: + test_labels.remove(label) + except ValueError: + pass + + subprocess_args = ['python','runtests.py', '--settings=%s' % options.settings] + if options.failfast: + subprocess_args.append('--failfast') + if options.verbosity: + subprocess_args.append('--verbosity=%s' % options.verbosity) + if not options.interactive: + subprocess_args.append('--noinput') + + for i, label in enumerate(test_labels): + print '***** %d of %d: Check test pairing with %s' % (i+1, len(test_labels), label) + failures = subprocess.call(subprocess_args + [label, paired_test]) + if failures: + print '***** Found problem pair with',label + return + + print '***** No problem pair found' + teardown(state) if __name__ == "__main__": from optparse import OptionParser @@ -194,10 +295,22 @@ def django_tests(verbosity, interactive, failfast, test_labels): help='Tells Django to stop running the test suite after first failed test.') parser.add_option('--settings', help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.') + parser.add_option('--bisect', action='store', dest='bisect', default=None, + help="Bisect the test suite to discover a test that causes a test failure when combined with the named test.") + parser.add_option('--pair', action='store', dest='pair', default=None, + help="Run the test suite in pairs with the named test to find problem pairs.") options, args = parser.parse_args() if options.settings: os.environ['DJANGO_SETTINGS_MODULE'] = options.settings elif "DJANGO_SETTINGS_MODULE" not in os.environ: parser.error("DJANGO_SETTINGS_MODULE is not set in the environment. " "Set it or use --settings.") - django_tests(int(options.verbosity), options.interactive, options.failfast, args) + + if options.bisect: + bisect_tests(options.bisect, options, args) + elif options.pair: + paired_tests(options.pair, options, args) + else: + failures = django_tests(int(options.verbosity), options.interactive, options.failfast, args) + if failures: + sys.exit(bool(failures)) From 2e5f0c228fb7bf65c83d8d83f3237db6a5e5ccb4 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 9 Oct 2010 16:27:19 +0000 Subject: [PATCH 281/902] [1.2.X]. Convert m2m_recursive tests to unittests. We have always been at war with doctests. Backport of [14082]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14083 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/m2m_recursive/models.py | 170 +-------------- tests/modeltests/m2m_recursive/tests.py | 253 +++++++++++++++++++++++ 2 files changed, 254 insertions(+), 169 deletions(-) create mode 100644 tests/modeltests/m2m_recursive/tests.py diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py index 23be6f31ba90..83c943ae6096 100644 --- a/tests/modeltests/m2m_recursive/models.py +++ b/tests/modeltests/m2m_recursive/models.py @@ -18,6 +18,7 @@ from django.db import models + class Person(models.Model): name = models.CharField(max_length=20) friends = models.ManyToManyField('self') @@ -25,172 +26,3 @@ class Person(models.Model): def __unicode__(self): return self.name - -__test__ = {'API_TESTS':""" ->>> a = Person(name='Anne') ->>> a.save() ->>> b = Person(name='Bill') ->>> b.save() ->>> c = Person(name='Chuck') ->>> c.save() ->>> d = Person(name='David') ->>> d.save() - -# Add some friends in the direction of field definition -# Anne is friends with Bill and Chuck ->>> a.friends.add(b,c) - -# David is friends with Anne and Chuck - add in reverse direction ->>> d.friends.add(a,c) - -# Who is friends with Anne? ->>> a.friends.all() -[, , ] - -# Who is friends with Bill? ->>> b.friends.all() -[] - -# Who is friends with Chuck? ->>> c.friends.all() -[, ] - -# Who is friends with David? ->>> d.friends.all() -[, ] - -# Bill is already friends with Anne - add Anne again, but in the reverse direction ->>> b.friends.add(a) - -# Who is friends with Anne? ->>> a.friends.all() -[, , ] - -# Who is friends with Bill? ->>> b.friends.all() -[] - -# Remove Anne from Bill's friends ->>> b.friends.remove(a) - -# Who is friends with Anne? ->>> a.friends.all() -[, ] - -# Who is friends with Bill? ->>> b.friends.all() -[] - -# Clear Anne's group of friends ->>> a.friends.clear() - -# Who is friends with Anne? ->>> a.friends.all() -[] - -# Reverse relationships should also be gone -# Who is friends with Chuck? ->>> c.friends.all() -[] - -# Who is friends with David? ->>> d.friends.all() -[] - - -# Add some idols in the direction of field definition -# Anne idolizes Bill and Chuck ->>> a.idols.add(b,c) - -# Bill idolizes Anne right back ->>> b.idols.add(a) - -# David is idolized by Anne and Chuck - add in reverse direction ->>> d.stalkers.add(a,c) - -# Who are Anne's idols? ->>> a.idols.all() -[, , ] - -# Who is stalking Anne? ->>> a.stalkers.all() -[] - -# Who are Bill's idols? ->>> b.idols.all() -[] - -# Who is stalking Bill? ->>> b.stalkers.all() -[] - -# Who are Chuck's idols? ->>> c.idols.all() -[] - -# Who is stalking Chuck? ->>> c.stalkers.all() -[] - -# Who are David's idols? ->>> d.idols.all() -[] - -# Who is stalking David ->>> d.stalkers.all() -[, ] - -# Bill is already being stalked by Anne - add Anne again, but in the reverse direction ->>> b.stalkers.add(a) - -# Who are Anne's idols? ->>> a.idols.all() -[, , ] - -# Who is stalking Anne? -[] - -# Who are Bill's idols ->>> b.idols.all() -[] - -# Who is stalking Bill? ->>> b.stalkers.all() -[] - -# Remove Anne from Bill's list of stalkers ->>> b.stalkers.remove(a) - -# Who are Anne's idols? ->>> a.idols.all() -[, ] - -# Who is stalking Anne? ->>> a.stalkers.all() -[] - -# Who are Bill's idols? ->>> b.idols.all() -[] - -# Who is stalking Bill? ->>> b.stalkers.all() -[] - -# Clear Anne's group of idols ->>> a.idols.clear() - -# Who are Anne's idols ->>> a.idols.all() -[] - -# Reverse relationships should also be gone -# Who is stalking Chuck? ->>> c.stalkers.all() -[] - -# Who is friends with David? ->>> d.stalkers.all() -[] - -"""} diff --git a/tests/modeltests/m2m_recursive/tests.py b/tests/modeltests/m2m_recursive/tests.py new file mode 100644 index 000000000000..42510280939d --- /dev/null +++ b/tests/modeltests/m2m_recursive/tests.py @@ -0,0 +1,253 @@ +from operator import attrgetter + +from django.test import TestCase + +from models import Person + + +class RecursiveM2MTests(TestCase): + def test_recursive_m2m(self): + a, b, c, d = [ + Person.objects.create(name=name) + for name in ["Anne", "Bill", "Chuck", "David"] + ] + + # Add some friends in the direction of field definition + # Anne is friends with Bill and Chuck + a.friends.add(b, c) + + # David is friends with Anne and Chuck - add in reverse direction + d.friends.add(a,c) + + # Who is friends with Anne? + self.assertQuerysetEqual( + a.friends.all(), [ + "Bill", + "Chuck", + "David" + ], + attrgetter("name") + ) + # Who is friends with Bill? + self.assertQuerysetEqual( + b.friends.all(), [ + "Anne", + ], + attrgetter("name") + ) + # Who is friends with Chuck? + self.assertQuerysetEqual( + c.friends.all(), [ + "Anne", + "David" + ], + attrgetter("name") + ) + # Who is friends with David? + self.assertQuerysetEqual( + d.friends.all(), [ + "Anne", + "Chuck", + ], + attrgetter("name") + ) + # Bill is already friends with Anne - add Anne again, but in the + # reverse direction + b.friends.add(a) + + # Who is friends with Anne? + self.assertQuerysetEqual( + a.friends.all(), [ + "Bill", + "Chuck", + "David", + ], + attrgetter("name") + ) + # Who is friends with Bill? + self.assertQuerysetEqual( + b.friends.all(), [ + "Anne", + ], + attrgetter("name") + ) + # Remove Anne from Bill's friends + b.friends.remove(a) + # Who is friends with Anne? + self.assertQuerysetEqual( + a.friends.all(), [ + "Chuck", + "David", + ], + attrgetter("name") + ) + # Who is friends with Bill? + self.assertQuerysetEqual( + b.friends.all(), [] + ) + + # Clear Anne's group of friends + a.friends.clear() + # Who is friends with Anne? + self.assertQuerysetEqual( + a.friends.all(), [] + ) + # Reverse relationships should also be gone + # Who is friends with Chuck? + self.assertQuerysetEqual( + c.friends.all(), [ + "David", + ], + attrgetter("name") + ) + # Who is friends with David? + self.assertQuerysetEqual( + d.friends.all(), [ + "Chuck", + ], + attrgetter("name") + ) + + # Add some idols in the direction of field definition + # Anne idolizes Bill and Chuck + a.idols.add(b, c) + # Bill idolizes Anne right back + b.idols.add(a) + # David is idolized by Anne and Chuck - add in reverse direction + d.stalkers.add(a, c) + + # Who are Anne's idols? + self.assertQuerysetEqual( + a.idols.all(), [ + "Bill", + "Chuck", + "David", + ], + attrgetter("name") + ) + # Who is stalking Anne? + self.assertQuerysetEqual( + a.stalkers.all(), [ + "Bill", + ], + attrgetter("name") + ) + # Who are Bill's idols? + self.assertQuerysetEqual( + b.idols.all(), [ + "Anne", + ], + attrgetter("name") + ) + # Who is stalking Bill? + self.assertQuerysetEqual( + b.stalkers.all(), [ + "Anne", + ], + attrgetter("name") + ) + # Who are Chuck's idols? + self.assertQuerysetEqual( + c.idols.all(), [ + "David", + ], + attrgetter("name"), + ) + # Who is stalking Chuck? + self.assertQuerysetEqual( + c.stalkers.all(), [ + "Anne", + ], + attrgetter("name") + ) + # Who are David's idols? + self.assertQuerysetEqual( + d.idols.all(), [] + ) + # Who is stalking David + self.assertQuerysetEqual( + d.stalkers.all(), [ + "Anne", + "Chuck", + ], + attrgetter("name") + ) + # Bill is already being stalked by Anne - add Anne again, but in the + # reverse direction + b.stalkers.add(a) + # Who are Anne's idols? + self.assertQuerysetEqual( + a.idols.all(), [ + "Bill", + "Chuck", + "David", + ], + attrgetter("name") + ) + # Who is stalking Anne? + self.assertQuerysetEqual( + a.stalkers.all(), [ + "Bill", + ], + attrgetter("name") + ) + # Who are Bill's idols + self.assertQuerysetEqual( + b.idols.all(), [ + "Anne", + ], + attrgetter("name") + ) + # Who is stalking Bill? + self.assertQuerysetEqual( + b.stalkers.all(), [ + "Anne", + ], + attrgetter("name"), + ) + # Remove Anne from Bill's list of stalkers + b.stalkers.remove(a) + # Who are Anne's idols? + self.assertQuerysetEqual( + a.idols.all(), [ + "Chuck", + "David", + ], + attrgetter("name") + ) + # Who is stalking Anne? + self.assertQuerysetEqual( + a.stalkers.all(), [ + "Bill", + ], + attrgetter("name") + ) + # Who are Bill's idols? + self.assertQuerysetEqual( + b.idols.all(), [ + "Anne", + ], + attrgetter("name") + ) + # Who is stalking Bill? + self.assertQuerysetEqual( + b.stalkers.all(), [] + ) + # Clear Anne's group of idols + a.idols.clear() + # Who are Anne's idols + self.assertQuerysetEqual( + a.idols.all(), [] + ) + # Reverse relationships should also be gone + # Who is stalking Chuck? + self.assertQuerysetEqual( + c.stalkers.all(), [] + ) + # Who is friends with David? + self.assertQuerysetEqual( + d.stalkers.all(), [ + "Chuck", + ], + attrgetter("name") + ) From bb66cb8463190a0d65b920f44f50c23da9f58bd7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 9 Oct 2010 16:46:25 +0000 Subject: [PATCH 282/902] [1.2.X] Fixed #14366 -- Model.objects.none().values() now correctly returns a QuerySet with no items, rather than raising an Exception. Thanks to Carl Meyer for the patch. Backport of [14084]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14085 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/query.py | 2 +- tests/regressiontests/queries/tests.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index def50934b73e..18c7db633357 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1021,7 +1021,7 @@ def delete(self): pass def _clone(self, klass=None, setup=False, **kwargs): - c = super(EmptyQuerySet, self)._clone(klass, **kwargs) + c = super(EmptyQuerySet, self)._clone(klass, setup=setup, **kwargs) c._result_cache = [] return c diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index 03c28b06a704..75653fc6602b 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -4,7 +4,7 @@ from django.db.models import Count from django.test import TestCase -from models import Tag, Annotation, DumbCategory, Note, ExtraInfo +from models import Tag, Annotation, DumbCategory, Note, ExtraInfo, Number class QuerysetOrderedTests(unittest.TestCase): """ @@ -81,3 +81,9 @@ def test_evaluated_queryset_as_argument(self): self.assertEquals(ExtraInfo.objects.filter(note__in=n_list)[0].info, 'good') except: self.fail('Query should be clonable') + + +class EmptyQuerySetTests(TestCase): + def test_emptyqueryset_values(self): + "#14366 -- calling .values() on an EmptyQuerySet and then cloning that should not cause an error" + self.assertEqual(list(Number.objects.none().values('num').order_by('num')), []) From b3e02d19ecf0be17fa18db553be3e7d4734df60e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 9 Oct 2010 16:51:50 +0000 Subject: [PATCH 283/902] [1.2.X] Fixed #14011 -- Doing a subquery with __in and an EmptyQuerySet no longer raises an Exception. This is actually just a test for this, it was fixed by [14085]. Thanks to skatei for the report and mk for the patch. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14087 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/queries/tests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index 75653fc6602b..ab5369246d0c 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -87,3 +87,8 @@ class EmptyQuerySetTests(TestCase): def test_emptyqueryset_values(self): "#14366 -- calling .values() on an EmptyQuerySet and then cloning that should not cause an error" self.assertEqual(list(Number.objects.none().values('num').order_by('num')), []) + + def test_values_subquery(self): + self.assertQuerysetEqual( + Number.objects.filter(pk__in=Number.objects.none().values("pk")), [] + ) From 50ce4573309c88e9f4805bc26cf84d572d894f6d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 9 Oct 2010 17:15:35 +0000 Subject: [PATCH 284/902] [1.2.X] Fixed #14356. Remove some dead imports from django.core.management.sql. Thanks to xiaket for the report and patch. Backport of [14088]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14089 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/sql.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 695473dfa5d6..17c145e92755 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -2,12 +2,9 @@ import re from django.conf import settings -from django.contrib.contenttypes import generic from django.core.management.base import CommandError -from django.dispatch import dispatcher from django.db import models from django.db.models import get_models -from django.db.backends.util import truncate_name def sql_create(app, style, connection): "Returns a list of the CREATE TABLE SQL statements for the given app." From 62dfe54d70892a50e867712eff465bdc30762906 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 9 Oct 2010 17:32:43 +0000 Subject: [PATCH 285/902] [1.2.X] Fixed a PendingDeprecationWarning coming from django.core.cache in Python 2.6. Backport of [14090]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14091 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/cache/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py index 680f724f94c7..334a932a9886 100644 --- a/django/core/cache/__init__.py +++ b/django/core/cache/__init__.py @@ -15,7 +15,11 @@ See docs/cache.txt for information on the public API. """ -from cgi import parse_qsl +try: + from urlparse import parse_qsl +except ImportError: + from cgi import parse_qsl + from django.conf import settings from django.core import signals from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning From b6223d302036ed46db80115906e2230411bc91f6 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 9 Oct 2010 20:10:00 +0000 Subject: [PATCH 286/902] [1.2.X] Added sanity-checking of annotation alias names. Backport of [14092] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14093 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/query.py | 11 +++++++++++ tests/regressiontests/aggregation_regress/tests.py | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/django/db/models/query.py b/django/db/models/query.py index 18c7db633357..7914e84d2269 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -619,8 +619,19 @@ def annotate(self, *args, **kwargs): with data aggregated from related fields. """ for arg in args: + if arg.default_alias in kwargs: + raise ValueError("The %s named annotation conflicts with the " + "default name for another annotation." + % arg.default_alias) kwargs[arg.default_alias] = arg + names = set([f.name for f in self.model._meta.fields]) + for aggregate in kwargs: + if aggregate in names: + raise ValueError("The %s annotation conflicts with a field on " + "the model." % aggregate) + + obj = self._clone() obj._setup_aggregate_query(kwargs.keys()) diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py index 51f439c5a19f..786431420d61 100644 --- a/tests/regressiontests/aggregation_regress/tests.py +++ b/tests/regressiontests/aggregation_regress/tests.py @@ -481,6 +481,14 @@ def test_more_more(self): lambda b: b.name ) + def test_duplicate_alias(self): + # Regression for #11256 - duplicating a default alias raises ValueError. + self.assertRaises(ValueError, Book.objects.all().annotate, Avg('authors__age'), authors__age__avg=Avg('authors__age')) + + def test_field_name_conflict(self): + # Regression for #11256 - providing an aggregate name that conflicts with a field name on the model raises ValueError + self.assertRaises(ValueError, Author.objects.annotate, age=Avg('friends__age')) + def test_pickle(self): # Regression for #10197 -- Queries with aggregates can be pickled. # First check that pickling is possible at all. No crash = success From ee8fc8d93af9d5dbba45dc8b0cdcc4658d71d161 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 9 Oct 2010 21:55:33 +0000 Subject: [PATCH 287/902] [1.2.X] Converted defer_regress tests from doctests to unittests. We have always been at war with doctests. Backport of [14094]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14095 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/defer_regress/models.py | 112 ------------- tests/regressiontests/defer_regress/tests.py | 157 +++++++++++++++++- 2 files changed, 156 insertions(+), 113 deletions(-) diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py index 8b4216589114..8db8c299600e 100644 --- a/tests/regressiontests/defer_regress/models.py +++ b/tests/regressiontests/defer_regress/models.py @@ -34,115 +34,3 @@ def __unicode__(self): class ResolveThis(models.Model): num = models.FloatField() name = models.CharField(max_length=16) - -__test__ = {"regression_tests": """ -Deferred fields should really be deferred and not accidentally use the field's -default value just because they aren't passed to __init__. - ->>> settings.DEBUG = True ->>> _ = Item.objects.create(name="first", value=42) ->>> obj = Item.objects.only("name", "other_value").get(name="first") - -# Accessing "name" doesn't trigger a new database query. Accessing "value" or -# "text" should. ->>> num = len(connection.queries) ->>> obj.name -u"first" ->>> obj.other_value -0 ->>> len(connection.queries) == num -True ->>> obj.value -42 ->>> len(connection.queries) == num + 1 # Effect of values lookup. -True ->>> obj.text -u"xyzzy" ->>> len(connection.queries) == num + 2 # Effect of text lookup. -True ->>> obj.text -u"xyzzy" ->>> len(connection.queries) == num + 2 -True - ->>> settings.DEBUG = False - -Regression test for #10695. Make sure different instances don't inadvertently -share data in the deferred descriptor objects. - ->>> i = Item.objects.create(name="no I'm first", value=37) ->>> items = Item.objects.only('value').order_by('-value') ->>> items[0].name -u'first' ->>> items[1].name -u"no I'm first" - ->>> _ = RelatedItem.objects.create(item=i) ->>> r = RelatedItem.objects.defer('item').get() ->>> r.item_id == i.id -True ->>> r.item == i -True - -Some further checks for select_related() and inherited model behaviour -(regression for #10710). - ->>> c1 = Child.objects.create(name="c1", value=42) ->>> c2 = Child.objects.create(name="c2", value=37) ->>> obj = Leaf.objects.create(name="l1", child=c1, second_child=c2) - ->>> obj = Leaf.objects.only("name", "child").select_related()[0] ->>> obj.child.name -u'c1' ->>> Leaf.objects.select_related().only("child__name", "second_child__name") -[] - -Models instances with deferred fields should still return the same content -types as their non-deferred versions (bug #10738). ->>> ctype = ContentType.objects.get_for_model ->>> c1 = ctype(Item.objects.all()[0]) ->>> c2 = ctype(Item.objects.defer("name")[0]) ->>> c3 = ctype(Item.objects.only("name")[0]) ->>> c1 is c2 is c3 -True - -# Regression for #10733 - only() can be used on a model with two foreign keys. ->>> results = Leaf.objects.all().only('name', 'child', 'second_child').select_related() ->>> results[0].child.name -u'c1' ->>> results[0].second_child.name -u'c2' - ->>> results = Leaf.objects.all().only('name', 'child', 'second_child', 'child__name', 'second_child__name').select_related() ->>> results[0].child.name -u'c1' ->>> results[0].second_child.name -u'c2' - -# Test for #12163 - Pickling error saving session with unsaved model instances. ->>> from django.contrib.sessions.backends.db import SessionStore ->>> SESSION_KEY = '2b1189a188b44ad18c35e1baac6ceead' ->>> item = Item() ->>> item._deferred -False ->>> s = SessionStore(SESSION_KEY) ->>> s.clear() ->>> s['item'] = item ->>> s.save() ->>> s = SessionStore(SESSION_KEY) ->>> s.modified = True ->>> s.save() ->>> i2 = s['item'] ->>> i2._deferred # Item must still be non-deferred -False - -# Regression for #11936 - loading.get_models should not return deferred models by default. ->>> from django.db.models.loading import get_models ->>> sorted(get_models(models.get_app('defer_regress')), key=lambda obj: obj._meta.object_name) -[, , , , ] - ->>> sorted(get_models(models.get_app('defer_regress'), include_deferred=True), key=lambda obj: obj._meta.object_name) -[, , , , , , , , , , , , , , , , , ] -""" -} - diff --git a/tests/regressiontests/defer_regress/tests.py b/tests/regressiontests/defer_regress/tests.py index 860c7f8e316c..affb0e2405e9 100644 --- a/tests/regressiontests/defer_regress/tests.py +++ b/tests/regressiontests/defer_regress/tests.py @@ -1,7 +1,162 @@ +from operator import attrgetter + +from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.contrib.sessions.backends.db import SessionStore +from django.db import connection +from django.db.models.loading import cache from django.test import TestCase -from models import ResolveThis + +from models import ResolveThis, Item, RelatedItem, Child, Leaf + class DeferRegressionTest(TestCase): + def assert_num_queries(self, n, func, *args, **kwargs): + old_DEBUG = settings.DEBUG + settings.DEBUG = True + starting_queries = len(connection.queries) + try: + func(*args, **kwargs) + finally: + settings.DEBUG = old_DEBUG + self.assertEqual(starting_queries + n, len(connection.queries)) + + + def test_basic(self): + # Deferred fields should really be deferred and not accidentally use + # the field's default value just because they aren't passed to __init__ + + Item.objects.create(name="first", value=42) + obj = Item.objects.only("name", "other_value").get(name="first") + # Accessing "name" doesn't trigger a new database query. Accessing + # "value" or "text" should. + def test(): + self.assertEqual(obj.name, "first") + self.assertEqual(obj.other_value, 0) + self.assert_num_queries(0, test) + + def test(): + self.assertEqual(obj.value, 42) + self.assert_num_queries(1, test) + + def test(): + self.assertEqual(obj.text, "xyzzy") + self.assert_num_queries(1, test) + + def test(): + self.assertEqual(obj.text, "xyzzy") + self.assert_num_queries(0, test) + + # Regression test for #10695. Make sure different instances don't + # inadvertently share data in the deferred descriptor objects. + i = Item.objects.create(name="no I'm first", value=37) + items = Item.objects.only("value").order_by("-value") + self.assertEqual(items[0].name, "first") + self.assertEqual(items[1].name, "no I'm first") + + RelatedItem.objects.create(item=i) + r = RelatedItem.objects.defer("item").get() + self.assertEqual(r.item_id, i.id) + self.assertEqual(r.item, i) + + # Some further checks for select_related() and inherited model + # behaviour (regression for #10710). + c1 = Child.objects.create(name="c1", value=42) + c2 = Child.objects.create(name="c2", value=37) + Leaf.objects.create(name="l1", child=c1, second_child=c2) + + obj = Leaf.objects.only("name", "child").select_related()[0] + self.assertEqual(obj.child.name, "c1") + + self.assertQuerysetEqual( + Leaf.objects.select_related().only("child__name", "second_child__name"), [ + "l1", + ], + attrgetter("name") + ) + + # Models instances with deferred fields should still return the same + # content types as their non-deferred versions (bug #10738). + ctype = ContentType.objects.get_for_model + c1 = ctype(Item.objects.all()[0]) + c2 = ctype(Item.objects.defer("name")[0]) + c3 = ctype(Item.objects.only("name")[0]) + self.assertTrue(c1 is c2 is c3) + + # Regression for #10733 - only() can be used on a model with two + # foreign keys. + results = Leaf.objects.only("name", "child", "second_child").select_related() + self.assertEqual(results[0].child.name, "c1") + self.assertEqual(results[0].second_child.name, "c2") + + results = Leaf.objects.only("name", "child", "second_child", "child__name", "second_child__name").select_related() + self.assertEqual(results[0].child.name, "c1") + self.assertEqual(results[0].second_child.name, "c2") + + # Test for #12163 - Pickling error saving session with unsaved model + # instances. + SESSION_KEY = '2b1189a188b44ad18c35e1baac6ceead' + + item = Item() + item._deferred = False + s = SessionStore(SESSION_KEY) + s.clear() + s["item"] = item + s.save() + + s = SessionStore(SESSION_KEY) + s.modified = True + s.save() + + i2 = s["item"] + self.assertFalse(i2._deferred) + + # Regression for #11936 - loading.get_models should not return deferred + # models by default. + klasses = sorted( + cache.get_models(cache.get_app("defer_regress")), + key=lambda klass: klass.__name__ + ) + self.assertEqual( + klasses, [ + Child, + Item, + Leaf, + RelatedItem, + ResolveThis, + ] + ) + + klasses = sorted( + map( + attrgetter("__name__"), + cache.get_models( + cache.get_app("defer_regress"), include_deferred=True + ), + ) + ) + self.assertEqual( + klasses, [ + "Child", + "Child_Deferred_value", + "Item", + "Item_Deferred_name", + "Item_Deferred_name_other_value_text", + "Item_Deferred_name_other_value_value", + "Item_Deferred_other_value_text_value", + "Item_Deferred_text_value", + "Leaf", + "Leaf_Deferred_child_id_second_child_id_value", + "Leaf_Deferred_name_value", + "Leaf_Deferred_second_child_value", + "Leaf_Deferred_value", + "RelatedItem", + "RelatedItem_Deferred_", + "RelatedItem_Deferred_item_id", + "ResolveThis", + ] + ) + def test_resolve_columns(self): rt = ResolveThis.objects.create(num=5.0, name='Foobar') qs = ResolveThis.objects.defer('num') From 846febeb7abc935b986d168432323c49a3ee6735 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sat, 9 Oct 2010 22:42:01 +0000 Subject: [PATCH 288/902] [1.2.X] Fixed misspelling in model fields docs. Backport of [14096] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14097 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/fields.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index a390b4b843a6..e107f41454ed 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -925,8 +925,8 @@ define the details of how the relation works. `; and when you do so :ref:`some special syntax ` is available. - If you wish to supress the provision of a backwards relation, you may - simply provide a ``related_name`` which ends with a '+' character. + If you wish to suppress the provision of a backwards relation, you may + simply provide a ``related_name`` which ends with a ``'+'`` character. For example:: user = models.ForeignKey(User, related_name='+') From 5d9140c137fa369e7c033f08685751d85cae28ff Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 9 Oct 2010 23:05:09 +0000 Subject: [PATCH 289/902] [1.2.X] Fixed a typo in the comments tests, as well as a dependency on CPython's reference counting semantics. Backport of [14098]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14099 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../comment_tests/tests/comment_view_tests.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/regressiontests/comment_tests/tests/comment_view_tests.py b/tests/regressiontests/comment_tests/tests/comment_view_tests.py index f02dca4a8982..49c6450e1507 100644 --- a/tests/regressiontests/comment_tests/tests/comment_view_tests.py +++ b/tests/regressiontests/comment_tests/tests/comment_view_tests.py @@ -160,13 +160,18 @@ def receive(sender, **kwargs): # Connect signals and keep track of handled ones received_signals = [] - excepted_signals = [signals.comment_will_be_posted, signals.comment_was_posted] - for signal in excepted_signals: + expected_signals = [ + signals.comment_will_be_posted, signals.comment_was_posted + ] + for signal in expected_signals: signal.connect(receive) # Post a comment and check the signals self.testCreateValidComment() - self.assertEqual(received_signals, excepted_signals) + self.assertEqual(received_signals, expected_signals) + + for signal in expected_signals: + signal.disconnect(receive) def testWillBePostedSignal(self): """ @@ -251,4 +256,3 @@ def testCommentPostRedirectWithInvalidIntegerPK(self): broken_location = location + u"\ufffd" response = self.client.get(broken_location) self.assertEqual(response.status_code, 200) - From cf2e9f66ed1aad7537b8cd6fed5c4975b4de00d5 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sun, 10 Oct 2010 01:09:20 +0000 Subject: [PATCH 290/902] [1.2.X] Converted contrib/auth/tokens doctests to unittests. We've always said "no more" to doctests. Backport of [14100] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14102 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/tests/__init__.py | 6 +- django/contrib/auth/tests/tokens.py | 89 ++++++++++++++++----------- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py index bc67d16d8663..98061a157a83 100644 --- a/django/contrib/auth/tests/__init__.py +++ b/django/contrib/auth/tests/__init__.py @@ -5,12 +5,8 @@ from django.contrib.auth.tests.remote_user \ import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest from django.contrib.auth.tests.models import ProfileTestCase -from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS +from django.contrib.auth.tests.tokens import TokenGeneratorTest from django.contrib.auth.tests.views \ import PasswordResetTest, ChangePasswordTest, LoginTest, LogoutTest # The password for the fixture data users is 'password' - -__test__ = { - 'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS, -} diff --git a/django/contrib/auth/tests/tokens.py b/django/contrib/auth/tests/tokens.py index 03cc1e3c1141..e9e90493be21 100644 --- a/django/contrib/auth/tests/tokens.py +++ b/django/contrib/auth/tests/tokens.py @@ -1,37 +1,52 @@ -TOKEN_GENERATOR_TESTS = """ ->>> from django.contrib.auth.models import User, AnonymousUser ->>> from django.contrib.auth.tokens import PasswordResetTokenGenerator ->>> from django.conf import settings ->>> u = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw') ->>> p0 = PasswordResetTokenGenerator() ->>> tk1 = p0.make_token(u) ->>> p0.check_token(u, tk1) -True - ->>> u = User.objects.create_user('comebackkid', 'test3@example.com', 'testpw') ->>> p0 = PasswordResetTokenGenerator() ->>> tk1 = p0.make_token(u) ->>> reload = User.objects.get(username='comebackkid') ->>> tk2 = p0.make_token(reload) ->>> tk1 == tk2 -True - -Tests to ensure we can use the token after n days, but no greater. -Use a mocked version of PasswordResetTokenGenerator so we can change -the value of 'today' - ->>> class Mocked(PasswordResetTokenGenerator): -... def __init__(self, today): -... self._today_val = today -... def _today(self): -... return self._today_val - ->>> from datetime import date, timedelta ->>> p1 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS)) ->>> p1.check_token(u, tk1) -True ->>> p2 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1)) ->>> p2.check_token(u, tk1) -False - -""" +from datetime import date, timedelta + +from django.conf import settings +from django.contrib.auth.models import User, AnonymousUser +from django.contrib.auth.tokens import PasswordResetTokenGenerator +from django.test import TestCase + + +class TokenGeneratorTest(TestCase): + + def test_make_token(self): + """ + Ensure that we can make a token and that it is valid + """ + user = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw') + p0 = PasswordResetTokenGenerator() + tk1 = p0.make_token(user) + self.assertTrue(p0.check_token(user, tk1)) + + def test_10265(self): + """ + Ensure that the token generated for a user created in the same request + will work correctly. + """ + # See ticket #10265 + user = User.objects.create_user('comebackkid', 'test3@example.com', 'testpw') + p0 = PasswordResetTokenGenerator() + tk1 = p0.make_token(user) + reload = User.objects.get(username='comebackkid') + tk2 = p0.make_token(reload) + self.assertEqual(tk1, tk2) + + def test_timeout(self): + """ + Ensure we can use the token after n days, but no greater. + """ + # Uses a mocked version of PasswordResetTokenGenerator so we can change + # the value of 'today' + class Mocked(PasswordResetTokenGenerator): + def __init__(self, today): + self._today_val = today + def _today(self): + return self._today_val + + user = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw') + p0 = PasswordResetTokenGenerator() + tk1 = p0.make_token(user) + p1 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS)) + self.assertTrue(p1.check_token(user, tk1)) + + p2 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1)) + self.assertFalse(p2.check_token(user, tk1)) From a92da6e80b840ba7874e6faebd54d50be85ab1ae Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Sun, 10 Oct 2010 01:09:34 +0000 Subject: [PATCH 291/902] [1.2.X] Converted contrib/webdesign doctests to unittests. We thoroughly deplore those doctests. Backport of [14101] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14103 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/webdesign/tests.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/django/contrib/webdesign/tests.py b/django/contrib/webdesign/tests.py index d155620902a7..8907ea3ba7bc 100644 --- a/django/contrib/webdesign/tests.py +++ b/django/contrib/webdesign/tests.py @@ -1,20 +1,21 @@ # -*- coding: utf-8 -*- -r""" ->>> words(7) -u'lorem ipsum dolor sit amet consectetur adipisicing' +import unittest ->>> paragraphs(1) -['Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'] +from django.contrib.webdesign.lorem_ipsum import * +from django.template import loader, Context ->>> from django.template import loader, Context ->>> t = loader.get_template_from_string("{% load webdesign %}{% lorem 3 w %}") ->>> t.render(Context({})) -u'lorem ipsum dolor' -""" -from django.contrib.webdesign.lorem_ipsum import * +class WebdesignTest(unittest.TestCase): + + def test_words(self): + self.assertEqual(words(7), u'lorem ipsum dolor sit amet consectetur adipisicing') + + def test_paragraphs(self): + self.assertEqual(paragraphs(1), + ['Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.']) -if __name__ == '__main__': - import doctest - doctest.testmod() + def test_lorem_tag(self): + t = loader.get_template_from_string("{% load webdesign %}{% lorem 3 w %}") + self.assertEqual(t.render(Context({})), + u'lorem ipsum dolor') From 4d70df8b7830d32dd06728a12a39df14e07e42be Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 10 Oct 2010 01:59:17 +0000 Subject: [PATCH 292/902] [1.2.X] Fixed #12650 -- Don't generate invalid XHTML in the admin, databrowse apps when the i18n context processor is active. Thanks to Rob Hudson for the report and fix suggestion. Backport of [14104] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14105 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../contrib/admin/templates/admin/base.html | 2 +- .../databrowse/templates/databrowse/base.html | 2 +- tests/regressiontests/admin_views/tests.py | 30 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index 4221e2d1a701..30a4e494face 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -1,5 +1,5 @@ - + {% block title %}{% endblock %} diff --git a/django/contrib/databrowse/templates/databrowse/base.html b/django/contrib/databrowse/templates/databrowse/base.html index a3419851c43a..33cac486014d 100644 --- a/django/contrib/databrowse/templates/databrowse/base.html +++ b/django/contrib/databrowse/templates/databrowse/base.html @@ -1,5 +1,5 @@ - + {% block title %}{% endblock %} {% block style %} diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 6a4d3975b5b6..99aa888201f7 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -18,6 +18,7 @@ from django.utils.encoding import iri_to_uri from django.utils.html import escape from django.utils.translation import get_date_formats, activate, deactivate +import django.template.context # local test models from models import Article, BarAccount, CustomArticle, EmptyModel, \ @@ -2232,3 +2233,32 @@ def test_filters(self): except ImportError: pass + +class ValidXHTMLTests(TestCase): + fixtures = ['admin-views-users.xml'] + urlbit = 'admin' + + def setUp(self): + self._context_processors = None + self._use_i18n, settings.USE_I18N = settings.USE_I18N, False + if 'django.core.context_processors.i18n' in settings.TEMPLATE_CONTEXT_PROCESSORS: + self._context_processors = settings.TEMPLATE_CONTEXT_PROCESSORS + cp = list(settings.TEMPLATE_CONTEXT_PROCESSORS) + cp.remove('django.core.context_processors.i18n') + settings.TEMPLATE_CONTEXT_PROCESSORS = tuple(cp) + # Force re-evaluation of the contex processor list + django.template.context._standard_context_processors = None + self.client.login(username='super', password='secret') + + def tearDown(self): + self.client.logout() + if self._context_processors is not None: + settings.TEMPLATE_CONTEXT_PROCESSORS = self._context_processors + # Force re-evaluation of the contex processor list + django.template.context._standard_context_processors = None + settings.USE_I18N = self._use_i18n + + def testLangNamePresent(self): + response = self.client.get('/test_admin/%s/admin_views/' % self.urlbit) + self.failIf(' lang=""' in response.content) + self.failIf(' xml:lang=""' in response.content) From 7de577093c0c510926c0ee4bf6b1452c250f3441 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 10 Oct 2010 06:07:25 +0000 Subject: [PATCH 293/902] [1.2.X] Advises that the Ubuntu package manager drops the .py extension from django-admin.py. Thanks to islands for the report and d0ugal for the patch. Backport of [14108] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14109 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial01.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index a68afea83d67..1ecfa109ebb1 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -39,6 +39,12 @@ From the command line, ``cd`` into a directory where you'd like to store your code, then run the command ``django-admin.py startproject mysite``. This will create a ``mysite`` directory in your current directory. +.. admonition:: Script name differs on Ubuntu + + If you installed Django using the Ubuntu package manager (e.g. apt-get) + ``django-admin.py`` has been renamed to ``django-admin``. You may continue + through 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 From 95d0ffbb0f96c3f77a64459a2922739d44819896 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 10 Oct 2010 06:37:19 +0000 Subject: [PATCH 294/902] [1.2.X] Added additional information on what the APPEND_SLASH setting does. Thanks to ttencate for the report and draft text. Backport of [14110] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14111 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index b5556deac8a7..d5425a3221a8 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -96,9 +96,14 @@ APPEND_SLASH Default: ``True`` -Whether to append trailing slashes to URLs. This is only used if -``CommonMiddleware`` is installed (see :doc:`/topics/http/middleware`). See also -``PREPEND_WWW``. +When set to ``True``, if the request URL does not match any of the patterns +in the URLconf and it doesn't end in a slash, an HTTP redirect is issued to the +same URL with a slash appended. Note that the redirect may cause any data +submitted in a POST request to be lost. + +The ``APPEND_SLASH`` setting is only used if +:class:`~django.middleware.common.CommonMiddleware` is installed +(see :doc:`/topics/http/middleware`). See also :setting:`PREPEND_WWW`. .. setting:: AUTHENTICATION_BACKENDS @@ -1180,8 +1185,8 @@ PREPEND_WWW Default: ``False`` Whether to prepend the "www." subdomain to URLs that don't have it. This is only -used if ``CommonMiddleware`` is installed (see :doc:`/topics/http/middleware`). -See also ``APPEND_SLASH``. +used if :class:`~django.middleware.common.CommonMiddleware` is installed +(see :doc:`/topics/http/middleware`). See also :setting:`APPEND_SLASH`. .. setting:: PROFANITIES_LIST From 13229684091072db5e8c5b4b6c06c489ef33c9b0 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 10 Oct 2010 07:09:57 +0000 Subject: [PATCH 295/902] [1.2.X] Makes links to form and field validation (and form reference docs in general) more readily available from form topic overview. Thanks to john_fries for the report. Backport of [14112] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14113 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/forms/index.txt | 2 +- docs/topics/forms/index.txt | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/ref/forms/index.txt b/docs/ref/forms/index.txt index 610416a363fe..866afed6dcd2 100644 --- a/docs/ref/forms/index.txt +++ b/docs/ref/forms/index.txt @@ -5,7 +5,7 @@ Forms Detailed form API reference. For introductory material, see :doc:`/topics/forms/index`. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 api fields diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index f845586ab416..30b09c00a92c 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -5,8 +5,9 @@ Working with forms .. admonition:: About this document This document provides an introduction to Django's form handling features. - For a more detailed look at the forms API, see :doc:`/ref/forms/api`. For - documentation of the available field types, see :doc:`/ref/forms/fields`. + For a more detailed look at specific areas of the forms API, see + :doc:`/ref/forms/api`, :doc:`/ref/forms/fields`, and + :doc:`/ref/forms/validation`. .. highlightlang:: html+django @@ -388,7 +389,7 @@ Further topics This covers the basics, but forms can do a whole lot more: .. toctree:: - :maxdepth: 1 + :maxdepth: 2 modelforms formsets @@ -396,4 +397,6 @@ This covers the basics, but forms can do a whole lot more: .. seealso:: - The :doc:`form API reference `. + :doc:`The Forms Reference ` + Covers the full API reference, including form fields, form widgets, + and form and field validation. From 4395b6575b4b18f287ce7eeea81a40205c1c5a40 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 10 Oct 2010 07:59:49 +0000 Subject: [PATCH 296/902] [1.2.X] Clarified information on OPTIONS setting (for adding additional parameters when connecting to a database) and linked to Database Backend docs. Thanks to chris@cwroofs for the report. Backport of [14114] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14115 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index d5425a3221a8..b2540a2edde0 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -314,8 +314,12 @@ OPTIONS Default: ``{}`` (Empty dictionary) -Extra parameters to use when connecting to the database. Consult backend -module's document for available keywords. +Extra parameters to use when connecting to the database. Available parameters +vary depending on your database backend. + +Some information on available parameters can be found in the +:doc:`Database Backends ` documentation. For more information, +consult your backend module's own documentation. .. setting:: PASSWORD From 700c5a3dfe46155ae367345eb4e7a04013bb6548 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sun, 10 Oct 2010 08:06:39 +0000 Subject: [PATCH 297/902] [1.2.X] Refs #11256 -- Extended the annotation field name conflict check to cover m2ms and reverse related descriptors as well. This is needed to actually cover the case raised by #14373. Backport of [14116]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14117 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/query.py | 2 +- tests/regressiontests/aggregation_regress/tests.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 7914e84d2269..2c679b04a644 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -625,7 +625,7 @@ def annotate(self, *args, **kwargs): % arg.default_alias) kwargs[arg.default_alias] = arg - names = set([f.name for f in self.model._meta.fields]) + names = set(self.model._meta.get_all_field_names()) for aggregate in kwargs: if aggregate in names: raise ValueError("The %s annotation conflicts with a field on " diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py index 786431420d61..58d0b2748ff6 100644 --- a/tests/regressiontests/aggregation_regress/tests.py +++ b/tests/regressiontests/aggregation_regress/tests.py @@ -489,6 +489,14 @@ def test_field_name_conflict(self): # Regression for #11256 - providing an aggregate name that conflicts with a field name on the model raises ValueError self.assertRaises(ValueError, Author.objects.annotate, age=Avg('friends__age')) + def test_m2m_name_conflict(self): + # Regression for #11256 - providing an aggregate name that conflicts with an m2m name on the model raises ValueError + self.assertRaises(ValueError, Author.objects.annotate, friends=Count('friends')) + + def test_reverse_relation_name_conflict(self): + # Regression for #11256 - providing an aggregate name that conflicts with a reverse-related name on the model raises ValueError + self.assertRaises(ValueError, Author.objects.annotate, book_contact_set=Avg('friends__age')) + def test_pickle(self): # Regression for #10197 -- Queries with aggregates can be pickled. # First check that pickling is possible at all. No crash = success From a23b42924a5b6e7dde03785288bec2840ec710bb Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Sun, 10 Oct 2010 09:38:55 +0000 Subject: [PATCH 298/902] [1.2.X] Add myself to committers. Backport of [14118] git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14119 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 2 +- docs/internals/committers.txt | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 11beb1bc13e8..3f1ff28801fd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -22,6 +22,7 @@ The PRIMARY AUTHORS are (and/or have been): * Andrew Godwin * Carl Meyer * Ramiro Morales + * Chris Beaven More information on the main contributors to Django can be found in docs/internals/committers.txt. @@ -69,7 +70,6 @@ answer newbie questions, and generally made Django that much better: Ned Batchelder batiste@dosimple.ch Batman - Chris Beaven Brian Beck Shannon -jj Behrens Esdras Beleza diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index d3d95e099b1f..b94f31316720 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -256,6 +256,18 @@ Ramiro Morales Ramiro lives in Córdoba, Argentina. +`Chris Beaven`_ + Chris has been submitting patches and suggesting crazy ideas for Django + since early 2006. An advocate for community involement and a long-term + triager, he is still often found answering questions in the #django IRC + channel. + + Chris lives in Napier, New Zealand (adding to the pool of Oceanic core + developers). He works remotely as a developer for `Lincoln Loop`_. + +.. _Chris Beaven: http://smileychris.com/ +.. _Lincoln Loop: http://lincolnloop.com/ + Specialists ----------- From c4956d1b9efd0996f40d5f34af01f14613414858 Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Sun, 10 Oct 2010 09:40:38 +0000 Subject: [PATCH 299/902] [1.2.X] Fixed #14391 - Updated django-admin.1 man page to include newer commands. Thanks to laurentluce for the patch. Backport of [14120] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14121 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/man/django-admin.1 | 42 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 index a931d419dab0..016c80f78f43 100644 --- a/docs/man/django-admin.1 +++ b/docs/man/django-admin.1 @@ -27,6 +27,9 @@ Compiles .po files to .mo files for use with builtin gettext support. .BI "createcachetable [" "tablename" "]" Creates the table needed to use the SQL cache backend .TP +.BI "createsuperuser [" "\-\-username=USERNAME" "] [" "\-\-email=EMAIL" "]" +Creates a superuser account (a user who has all permissions). +.TP .B dbshell Runs the command\-line client for the specified .BI database ENGINE. @@ -37,10 +40,21 @@ Displays differences between the current and Django's default settings. Settings that don't appear in the defaults are followed by "###". .TP +.BI "dumpdata [" "\-\-all" "] [" "\-\-format=FMT" "] [" "\-\-indent=NUM" "] [" "\-\-natural=NATURAL" "] [" "appname appname appname.Model ..." "]" +Outputs to standard output all data in the database associated with the named +application(s). +.TP +.BI flush +Returns the database to the state it was in immediately after syncdb was +executed. +.TP .B inspectdb Introspects the database tables in the database specified in settings.py and outputs a Django model module. .TP +.BI "loaddata [" "fixture fixture ..." "]" +Searches for and loads the contents of the named fixture into the database. +.TP .BI "install [" "appname ..." "]" Executes .B sqlall @@ -81,6 +95,13 @@ given model module name(s). .BI "sqlclear [" "appname ..." "]" Prints the DROP TABLE SQL statements for the given app name(s). .TP +.BI "sqlcustom [" "appname ..." "]" +Prints the custom SQL statements for the given app name(s). +.TP +.BI "sqlflush [" "appname ..." "]" +Prints the SQL statements that would be executed for the "flush" +command. +.TP .BI "sqlindexes [" "appname ..." "]" Prints the CREATE INDEX SQL statements for the given model module name(s). .TP @@ -107,7 +128,11 @@ in the current directory. Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created. .TP -.BI "test [" "\-\-verbosity" "] [" "appname ..." "]" +.BI "test [" "\-\-verbosity" "] [" "\-\-failfast" "] [" "appname ..." "]" +Runs the test suite for the specified applications, or the entire project if +no apps are specified +.TP +.BI "testserver [" "\-\-addrport=ipaddr|port" "] [" "fixture fixture ..." "]" Runs the test suite for the specified applications, or the entire project if no apps are specified .TP @@ -145,6 +170,11 @@ Verbosity level: 0=minimal output, 1=normal output, 2=all output. .I \-\-adminmedia=ADMIN_MEDIA_PATH Specifies the directory from which to serve admin media when using the development server. .TP +.I \-\-traceback +By default, django-admin.py will show a simple error message whenever an +error occurs. If you specify this option, django-admin.py will +output a full stack trace whenever an exception is raised. +.TP .I \-l, \-\-locale=LOCALE The locale to process when using makemessages or compilemessages. .TP @@ -155,15 +185,15 @@ The domain of the message files (default: "django") when using makemessages. The file extension(s) to examine (default: ".html", separate multiple extensions with commas, or use -e multiple times). .TP -.I \-e, \-\-symlinks +.I \-s, \-\-symlinks Follows symlinks to directories when examining source code and templates for translation strings. .TP -.I \-e, \-\-ignore=PATTERN +.I \-i, \-\-ignore=PATTERN Ignore files or directories matching this glob-style pattern. Use multiple times to ignore more. .TP -.I \-e, \-\-no\-default\-ignore +.I \-\-no\-default\-ignore Don't ignore the common private glob-style patterns 'CVS', '.*' and '*~'. .TP .I \-a, \-\-all @@ -174,6 +204,10 @@ In the absence of the .BI \-\-settings option, this environment variable defines the settings module to be read. It should be in Python-import form, e.g. "myproject.settings". +.I \-\-database=DB +Used to specify the database on which a command will operate. If not +specified, this option will default to an alias of "default". +.TP .SH "SEE ALSO" Full descriptions of all these options, with examples, as well as documentation From be190d1913b3e95cf13bc99d7674facfcfaec41c Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Sun, 10 Oct 2010 09:58:24 +0000 Subject: [PATCH 300/902] [1.2.X] Fixed #14193: prepopulated_fields javascript now concatenates in correct order. Thanks to bmihelac for the patch. Backport of [14122] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14123 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/media/js/prepopulate.js | 14 +++++++------- django/contrib/admin/media/js/prepopulate.min.js | 2 +- .../admin/templates/admin/edit_inline/stacked.html | 5 ++++- .../admin/templates/admin/edit_inline/tabular.html | 5 ++++- .../templates/admin/prepopulated_fields_js.html | 2 +- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/django/contrib/admin/media/js/prepopulate.js b/django/contrib/admin/media/js/prepopulate.js index 09483bbf04de..24f24f95a8bb 100644 --- a/django/contrib/admin/media/js/prepopulate.js +++ b/django/contrib/admin/media/js/prepopulate.js @@ -4,7 +4,7 @@ Depends on urlify.js Populates a selected field with the values of the dependent fields, URLifies and shortens the string. - dependencies - selected jQuery object of dependent fields + dependencies - array of dependent fields id's maxLength - maximum length of the URLify'd string */ return this.each(function() { @@ -20,15 +20,15 @@ if (field.data('_changed') == true) return; var values = []; - dependencies.each(function() { - if ($(this).val().length > 0) { - values.push($(this).val()); - } - }); + $.each(dependencies, function(i, field) { + if ($(field).val().length > 0) { + values.push($(field).val()); + } + }) field.val(URLify(values.join(' '), maxLength)); }; - dependencies.keyup(populate).change(populate).focus(populate); + $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); }); }; })(django.jQuery); diff --git a/django/contrib/admin/media/js/prepopulate.min.js b/django/contrib/admin/media/js/prepopulate.min.js index f1ca297b8050..98fdc93d2fa5 100644 --- a/django/contrib/admin/media/js/prepopulate.min.js +++ b/django/contrib/admin/media/js/prepopulate.min.js @@ -1 +1 @@ -(function(b){b.fn.prepopulate=function(d,f){return this.each(function(){var a=b(this);a.data("_changed",false);a.change(function(){a.data("_changed",true)});var c=function(){if(a.data("_changed")!=true){var e=[];d.each(function(){b(this).val().length>0&&e.push(b(this).val())});a.val(URLify(e.join(" "),f))}};d.keyup(c).change(c).focus(c)})}})(django.jQuery); +(function(a){a.fn.prepopulate=function(d,g){return this.each(function(){var b=a(this);b.data("_changed",false);b.change(function(){b.data("_changed",true)});var c=function(){if(b.data("_changed")!=true){var e=[];a.each(d,function(h,f){a(f).val().length>0&&e.push(a(f).val())});b.val(URLify(e.join(" "),g))}};a(d.join(",")).keyup(c).change(c).focus(c)})}})(django.jQuery); \ No newline at end of file diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index 1ecb790b5e71..f1e338f9a679 100644 --- a/django/contrib/admin/templates/admin/edit_inline/stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -53,7 +53,10 @@

        {{ inline_admin_formset.opts.verbose_name|title }}: {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

        var field = $(this); var input = field.find('input, select, textarea'); var dependency_list = input.data('dependency_list') || []; - var dependencies = row.find(dependency_list.join(',')).find('input, select, textarea'); + var dependencies = []; + $.each(dependency_list, function(i, field_name) { + dependencies.push('#' + row.find(field_name).find('input, select, textarea').attr('id')); + }); if (dependencies.length) { input.prepopulate(dependencies, input.attr('maxlength')); } diff --git a/django/contrib/admin/templates/admin/prepopulated_fields_js.html b/django/contrib/admin/templates/admin/prepopulated_fields_js.html index 4aa638031366..43ef5ba0717b 100644 --- a/django/contrib/admin/templates/admin/prepopulated_fields_js.html +++ b/django/contrib/admin/templates/admin/prepopulated_fields_js.html @@ -17,7 +17,7 @@ $('.empty-form .{{ field.field.name }}').addClass('prepopulated_field'); $(field.id).data('dependency_list', field['dependency_list']) - .prepopulate($(field['dependency_ids'].join(',')), field.maxLength); + .prepopulate(field['dependency_ids'], field.maxLength); {% endfor %} })(django.jQuery); From fee4aa31d7c8b08b58dab46c502e3c736d845539 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Sun, 10 Oct 2010 10:02:18 +0000 Subject: [PATCH 301/902] [1.2.X] Fixed #11907 -- EmailField now runs strip() on its input. This means mistakenly including leading or trailing spaces will not cause a validation error, and clean() will remove those spaces. Thanks, krisneuharth and djansoft. Backport of [13997]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14124 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/forms/fields.py | 4 ++++ tests/regressiontests/forms/fields.py | 1 + 2 files changed, 5 insertions(+) diff --git a/django/forms/fields.py b/django/forms/fields.py index de14a5c8a8bc..25ba5f5bdd22 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -433,6 +433,10 @@ class EmailField(CharField): } default_validators = [validators.validate_email] + def clean(self, value): + value = self.to_python(value).strip() + return super(EmailField, self).clean(value) + class FileField(Field): widget = FileInput default_error_messages = { diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index e4f2c261c91d..258d644ff085 100644 --- a/tests/regressiontests/forms/fields.py +++ b/tests/regressiontests/forms/fields.py @@ -426,6 +426,7 @@ def test_emailfield_33(self): self.assertEqual(u'', f.clean('')) self.assertEqual(u'', f.clean(None)) self.assertEqual(u'person@example.com', f.clean('person@example.com')) + self.assertEqual(u'example@example.com', f.clean(' example@example.com \t \t ')) self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo') self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@') self.assertRaisesErrorWithMessage(ValidationError, "[u'Enter a valid e-mail address.']", f.clean, 'foo@bar') From 46812b4c2bdbfd791af18be066a2fa60718a3a97 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 10 Oct 2010 16:45:06 +0000 Subject: [PATCH 302/902] [1.2.X] Fixed #6073 -- Made compilemessages 18n management command reject PO files with BOM. Backport of [14125] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14126 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../management/commands/compilemessages.py | 19 +++++++++++++++---- docs/topics/i18n/localization.txt | 6 ++++++ tests/regressiontests/makemessages/tests.py | 3 +++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/django/core/management/commands/compilemessages.py b/django/core/management/commands/compilemessages.py index 7d600e0fe985..b5eaeb1f5263 100644 --- a/django/core/management/commands/compilemessages.py +++ b/django/core/management/commands/compilemessages.py @@ -1,9 +1,17 @@ +import codecs import os import sys from optparse import make_option from django.core.management.base import BaseCommand, CommandError -def compile_messages(locale=None): +def has_bom(fn): + f = open(fn, 'r') + sample = f.read(4) + return sample[:3] == '\xef\xbb\xbf' or \ + sample.startswith(codecs.BOM_UTF16_LE) or \ + sample.startswith(codecs.BOM_UTF16_BE) + +def compile_messages(stderr, locale=None): basedirs = [os.path.join('conf', 'locale'), 'locale'] if os.environ.get('DJANGO_SETTINGS_MODULE'): from django.conf import settings @@ -21,8 +29,11 @@ def compile_messages(locale=None): for dirpath, dirnames, filenames in os.walk(basedir): for f in filenames: if f.endswith('.po'): - sys.stderr.write('processing file %s in %s\n' % (f, dirpath)) - pf = os.path.splitext(os.path.join(dirpath, f))[0] + stderr.write('processing file %s in %s\n' % (f, dirpath)) + fn = os.path.join(dirpath, f) + if has_bom(fn): + raise CommandError("The %s file has a BOM (Byte Order Mark). Django only supports .po files encoded in UTF-8 and without any BOM." % fn) + pf = os.path.splitext(fn)[0] # Store the names of the .mo and .po files in an environment # variable, rather than doing a string replacement into the # command, so that we can take advantage of shell quoting, to @@ -49,4 +60,4 @@ class Command(BaseCommand): def handle(self, **options): locale = options.get('locale') - compile_messages(locale) + compile_messages(self.stderr, locale=locale) diff --git a/docs/topics/i18n/localization.txt b/docs/topics/i18n/localization.txt index 8ba1e1ecdc90..38d74e68e301 100644 --- a/docs/topics/i18n/localization.txt +++ b/docs/topics/i18n/localization.txt @@ -188,6 +188,12 @@ That's it. Your translations are ready for use. ``django-admin compilemessages`` works see :ref:`gettext_on_windows` for more information. +.. admonition:: .po files: Encoding and BOM usage. + + Django only supports ``.po`` files encoded in UTF-8 and without any BOM + (Byte Order Mark) so if your text editor adds such marks to the beginning of + files by default then you will need to reconfigure it. + .. _creating-message-files-from-js-code: Creating message files from JavaScript source code diff --git a/tests/regressiontests/makemessages/tests.py b/tests/regressiontests/makemessages/tests.py index 5798e671b035..c3be2178e4bb 100644 --- a/tests/regressiontests/makemessages/tests.py +++ b/tests/regressiontests/makemessages/tests.py @@ -38,3 +38,6 @@ def find_command(cmd, path=None, pathext=None): if xversion >= (0, 15): from extraction import * del p + +if find_command('msgfmt'): + from compilation import * From e226a113a6861b370770166ce7e13224e82d970e Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Sun, 10 Oct 2010 18:17:32 +0000 Subject: [PATCH 303/902] [1.2.X] Fixed typo in tutorial 01. Backport of [14129]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14130 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial01.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 1ecfa109ebb1..785218ba04fe 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -43,7 +43,7 @@ create a ``mysite`` directory in your current directory. If you installed Django using the Ubuntu package manager (e.g. apt-get) ``django-admin.py`` has been renamed to ``django-admin``. You may continue - through through this documentation by omitting ``.py`` from each command. + through this documentation by omitting ``.py`` from each command. .. admonition:: Mac OS X permissions From b014c81fa73c3ac613835a650da441ab06e8c6ee Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 10 Oct 2010 18:28:53 +0000 Subject: [PATCH 304/902] [1.2.X] Converted signals tests from doctests to unittests. We have always been at war with doctests. Backport of [14131]. Conflicts: tests/modeltests/signals/models.py git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14132 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/signals/models.py | 106 +----------------------- tests/modeltests/signals/tests.py | 126 ++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 108 deletions(-) diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py index ea8137f657a8..f1250b42fef1 100644 --- a/tests/modeltests/signals/models.py +++ b/tests/modeltests/signals/models.py @@ -4,114 +4,10 @@ from django.db import models + class Person(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) def __unicode__(self): return u"%s %s" % (self.first_name, self.last_name) - -def pre_save_test(signal, sender, instance, **kwargs): - print 'pre_save signal,', instance - if kwargs.get('raw'): - print 'Is raw' - -def post_save_test(signal, sender, instance, **kwargs): - print 'post_save signal,', instance - if 'created' in kwargs: - if kwargs['created']: - print 'Is created' - else: - print 'Is updated' - if kwargs.get('raw'): - print 'Is raw' - -def pre_delete_test(signal, sender, instance, **kwargs): - print 'pre_delete signal,', instance - print 'instance.id is not None: %s' % (instance.id != None) - -# #8285: signals can be any callable -class PostDeleteHandler(object): - def __call__(self, signal, sender, instance, **kwargs): - print 'post_delete signal,', instance - print 'instance.id is None: %s' % (instance.id == None) - -post_delete_test = PostDeleteHandler() - -__test__ = {'API_TESTS':""" - -# Save up the number of connected signals so that we can check at the end -# that all the signals we register get properly unregistered (#9989) ->>> pre_signals = (len(models.signals.pre_save.receivers), -... len(models.signals.post_save.receivers), -... len(models.signals.pre_delete.receivers), -... len(models.signals.post_delete.receivers)) - ->>> models.signals.pre_save.connect(pre_save_test) ->>> models.signals.post_save.connect(post_save_test) ->>> models.signals.pre_delete.connect(pre_delete_test) ->>> models.signals.post_delete.connect(post_delete_test) - ->>> p1 = Person(first_name='John', last_name='Smith') ->>> p1.save() -pre_save signal, John Smith -post_save signal, John Smith -Is created - ->>> p1.first_name = 'Tom' ->>> p1.save() -pre_save signal, Tom Smith -post_save signal, Tom Smith -Is updated - -# Calling an internal method purely so that we can trigger a "raw" save. ->>> p1.save_base(raw=True) -pre_save signal, Tom Smith -Is raw -post_save signal, Tom Smith -Is updated -Is raw - ->>> p1.delete() -pre_delete signal, Tom Smith -instance.id is not None: True -post_delete signal, Tom Smith -instance.id is None: False - ->>> p2 = Person(first_name='James', last_name='Jones') ->>> p2.id = 99999 ->>> p2.save() -pre_save signal, James Jones -post_save signal, James Jones -Is created - ->>> p2.id = 99998 ->>> p2.save() -pre_save signal, James Jones -post_save signal, James Jones -Is created - ->>> p2.delete() -pre_delete signal, James Jones -instance.id is not None: True -post_delete signal, James Jones -instance.id is None: False - ->>> Person.objects.all() -[] - ->>> models.signals.post_delete.disconnect(post_delete_test) ->>> models.signals.pre_delete.disconnect(pre_delete_test) ->>> models.signals.post_save.disconnect(post_save_test) ->>> models.signals.pre_save.disconnect(pre_save_test) - -# Check that all our signals got disconnected properly. ->>> post_signals = (len(models.signals.pre_save.receivers), -... len(models.signals.post_save.receivers), -... len(models.signals.pre_delete.receivers), -... len(models.signals.post_delete.receivers)) - ->>> pre_signals == post_signals -True - -"""} diff --git a/tests/modeltests/signals/tests.py b/tests/modeltests/signals/tests.py index 329636c30665..ec601ae56663 100644 --- a/tests/modeltests/signals/tests.py +++ b/tests/modeltests/signals/tests.py @@ -1,6 +1,18 @@ from django.db.models import signals from django.test import TestCase -from modeltests.signals.models import Person + +from models import Person + + +# #8285: signals can be any callable +class PostDeleteHandler(object): + def __init__(self, data): + self.data = data + + def __call__(self, signal, sender, instance, **kwargs): + self.data.append( + (instance, instance.id is None) + ) class MyReceiver(object): def __init__(self, param): @@ -12,6 +24,115 @@ def __call__(self, signal, sender, **kwargs): signal.disconnect(receiver=self, sender=sender) class SignalTests(TestCase): + def test_basic(self): + # Save up the number of connected signals so that we can check at the + # end that all the signals we register get properly unregistered (#9989) + pre_signals = ( + len(signals.pre_save.receivers), + len(signals.post_save.receivers), + len(signals.pre_delete.receivers), + len(signals.post_delete.receivers), + ) + + data = [] + + def pre_save_test(signal, sender, instance, **kwargs): + data.append( + (instance, kwargs.get("raw", False)) + ) + signals.pre_save.connect(pre_save_test) + + def post_save_test(signal, sender, instance, **kwargs): + data.append( + (instance, kwargs.get("created"), kwargs.get("raw", False)) + ) + signals.post_save.connect(post_save_test) + + def pre_delete_test(signal, sender, instance, **kwargs): + data.append( + (instance, instance.id is None) + ) + signals.pre_delete.connect(pre_delete_test) + + post_delete_test = PostDeleteHandler(data) + signals.post_delete.connect(post_delete_test) + + p1 = Person(first_name="John", last_name="Smith") + self.assertEqual(data, []) + p1.save() + self.assertEqual(data, [ + (p1, False), + (p1, True, False), + ]) + data[:] = [] + + p1.first_name = "Tom" + p1.save() + self.assertEqual(data, [ + (p1, False), + (p1, False, False), + ]) + data[:] = [] + + # Calling an internal method purely so that we can trigger a "raw" save. + p1.save_base(raw=True) + self.assertEqual(data, [ + (p1, True), + (p1, False, True), + ]) + data[:] = [] + + p1.delete() + self.assertEqual(data, [ + (p1, False), + (p1, False), + ]) + data[:] = [] + + p2 = Person(first_name="James", last_name="Jones") + p2.id = 99999 + p2.save() + self.assertEqual(data, [ + (p2, False), + (p2, True, False), + ]) + data[:] = [] + + p2.id = 99998 + p2.save() + self.assertEqual(data, [ + (p2, False), + (p2, True, False), + ]) + data[:] = [] + + p2.delete() + self.assertEqual(data, [ + (p2, False), + (p2, False) + ]) + + self.assertQuerysetEqual( + Person.objects.all(), [ + "James Jones", + ], + unicode + ) + + signals.post_delete.disconnect(post_delete_test) + signals.pre_delete.disconnect(pre_delete_test) + signals.post_save.disconnect(post_save_test) + signals.pre_save.disconnect(pre_save_test) + + # Check that all our signals got disconnected properly. + post_signals = ( + len(signals.pre_save.receivers), + len(signals.post_save.receivers), + len(signals.pre_delete.receivers), + len(signals.post_delete.receivers), + ) + self.assertEqual(pre_signals, post_signals) + def test_disconnect_in_dispatch(self): """ Test that signals that disconnect when being called don't mess future @@ -21,8 +142,7 @@ def test_disconnect_in_dispatch(self): signals.post_save.connect(sender=Person, receiver=a) signals.post_save.connect(sender=Person, receiver=b) p = Person.objects.create(first_name='John', last_name='Smith') - + self.failUnless(a._run) self.failUnless(b._run) self.assertEqual(signals.post_save.receivers, []) - From 1b7d73848c7260e87c3e4c5a0548a36bcd1f520b Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 10 Oct 2010 22:41:51 +0000 Subject: [PATCH 305/902] [1.2.X] Changed ModelForm.fields and ModelForm.exclude examples to use tuples instead of lists since they were used inconsistently throughout the page (it wasn't hurting anything, but consistency is nice). Thanks to lspcity for the report and gruszczy for the patch. Backport of [14134] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14135 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/forms/modelforms.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 2cdd2bfa7471..203639db770e 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -387,7 +387,7 @@ widget:: class AuthorForm(ModelForm): class Meta: model = Author - fields = ['name', 'title', 'birth_date'] + fields = ('name', 'title', 'birth_date') widgets = { 'name': Textarea(attrs={'cols': 80, 'rows': 20}), } @@ -471,7 +471,7 @@ to be rendered first, we could specify the following ``ModelForm``:: >>> class BookForm(ModelForm): ... class Meta: ... model = Book - ... fields = ['title', 'author'] + ... fields = ('title', 'author') .. _overriding-modelform-clean-method: @@ -514,7 +514,7 @@ the ``Meta.fields`` or ``Meta.excludes`` lists:: >>> class RestrictedArticleForm(EnhancedArticleForm): ... class Meta(ArticleForm.Meta): - ... exclude = ['body'] + ... exclude = ('body',) This adds the extra method from the ``EnhancedArticleForm`` and modifies the original ``ArticleForm.Meta`` to remove one field. From 1d9336bb6c59b9b75c14674c41cc831af8b28429 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 11 Oct 2010 03:40:26 +0000 Subject: [PATCH 306/902] [1.2.X] Fixed #14279 -- Corrected a typo in the sitemaps tests (didn't affect the passage of the test). Thanks to jamesodo for the report and patch. Backport of [14136]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14137 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sitemaps/tests/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/sitemaps/tests/basic.py b/django/contrib/sitemaps/tests/basic.py index 552fb3ca600b..9d514623e31e 100644 --- a/django/contrib/sitemaps/tests/basic.py +++ b/django/contrib/sitemaps/tests/basic.py @@ -68,7 +68,7 @@ def test_flatpage_sitemap(self): public.sites.add(settings.SITE_ID) private = FlatPage.objects.create( url=u'/private/', - title=u'Public Page', + title=u'Private Page', enable_comments=True, registration_required=True ) From 1534af4b44e91276cb3ea0a76f1e9cab4fb4eb1b Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Mon, 11 Oct 2010 14:36:20 +0000 Subject: [PATCH 307/902] [1.2.X] Fixed #14433 - replaced a thread-unsafe solution to #10235 introduced in [13980] This patch also addresses sitemap code found in contrib/gis, which [13980] did not. Thanks to gabrielhurley for the initial patch. Refs #10235, #14386 Backport of [14141] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14142 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/sitemaps/georss.py | 4 ++-- django/contrib/gis/sitemaps/kml.py | 4 ++-- django/contrib/gis/sitemaps/views.py | 5 +++-- django/contrib/sitemaps/__init__.py | 24 +++++++++++++++--------- django/contrib/sitemaps/tests/basic.py | 21 +++++++++++++++++++++ django/contrib/sitemaps/views.py | 6 +++--- 6 files changed, 46 insertions(+), 18 deletions(-) diff --git a/django/contrib/gis/sitemaps/georss.py b/django/contrib/gis/sitemaps/georss.py index aca53a43c963..f75cf804ba54 100644 --- a/django/contrib/gis/sitemaps/georss.py +++ b/django/contrib/gis/sitemaps/georss.py @@ -36,12 +36,12 @@ def __init__(self, feed_dict, slug_dict=None): else: self.locations.append(section) - def get_urls(self, page=1): + def get_urls(self, page=1, site=None): """ This method is overrridden so the appropriate `geo_format` attribute is placed on each URL element. """ - urls = Sitemap.get_urls(self, page=page) + urls = Sitemap.get_urls(self, page=page, site=site) for url in urls: url['geo_format'] = 'georss' return urls diff --git a/django/contrib/gis/sitemaps/kml.py b/django/contrib/gis/sitemaps/kml.py index d85744f0f918..db30606b04e8 100644 --- a/django/contrib/gis/sitemaps/kml.py +++ b/django/contrib/gis/sitemaps/kml.py @@ -40,12 +40,12 @@ def _build_kml_sources(self, sources): raise TypeError('KML Sources must be a model or a 3-tuple.') return kml_sources - def get_urls(self, page=1): + def get_urls(self, page=1, site=None): """ This method is overrridden so the appropriate `geo_format` attribute is placed on each URL element. """ - urls = Sitemap.get_urls(self, page=page) + urls = Sitemap.get_urls(self, page=page, site=site) for url in urls: url['geo_format'] = self.geo_format return urls diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py index c7bd22e5d88e..02a0fc02ab6e 100644 --- a/django/contrib/gis/sitemaps/views.py +++ b/django/contrib/gis/sitemaps/views.py @@ -46,12 +46,13 @@ def sitemap(request, sitemaps, section=None): maps = sitemaps.values() page = request.GET.get("p", 1) + current_site = get_current_site(request) for site in maps: try: if callable(site): - urls.extend(site().get_urls(page)) + urls.extend(site().get_urls(page=page, site=current_site)) else: - urls.extend(site.get_urls(page)) + urls.extend(site.get_urls(page=page, site=current_site)) except EmptyPage: raise Http404("Page %s empty" % page) except PageNotAnInteger: diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py index 114a791572d6..6b8d5a03d1c6 100644 --- a/django/contrib/sitemaps/__init__.py +++ b/django/contrib/sitemaps/__init__.py @@ -1,5 +1,6 @@ -from django.contrib.sites.models import get_current_site +from django.contrib.sites.models import Site, get_current_site from django.core import urlresolvers, paginator +from django.core.exceptions import ImproperlyConfigured import urllib PING_URL = "http://www.google.com/webmasters/tools/ping" @@ -60,11 +61,19 @@ def _get_paginator(self): return self._paginator paginator = property(_get_paginator) - def get_urls(self, page=1): - current_site = get_current_site(self.request) + def get_urls(self, page=1, site=None): + if site is None: + if Site._meta.installed: + try: + site = Site.objects.get_current() + except Site.DoesNotExist: + pass + if site is None: + raise ImproperlyConfigured("In order to use Sitemaps you must either use the sites framework or pass in a Site or RequestSite object in your view code.") + urls = [] for item in self.paginator.page(page).object_list: - loc = "http://%s%s" % (current_site.domain, self.__get('location', item)) + loc = "http://%s%s" % (site.domain, self.__get('location', item)) priority = self.__get('priority', item, None) url_info = { 'location': loc, @@ -77,11 +86,8 @@ def get_urls(self, page=1): class FlatPageSitemap(Sitemap): def items(self): - current_site = get_current_site(self.request) - if hasattr(current_site, "flatpage_set"): - return current_site.flatpage_set.filter(registration_required=False) - else: - return () + current_site = Site.objects.get_current() + return current_site.flatpage_set.filter(registration_required=False) class GenericSitemap(Sitemap): priority = None diff --git a/django/contrib/sitemaps/tests/basic.py b/django/contrib/sitemaps/tests/basic.py index 9d514623e31e..b2de75f09833 100644 --- a/django/contrib/sitemaps/tests/basic.py +++ b/django/contrib/sitemaps/tests/basic.py @@ -2,7 +2,9 @@ from django.conf import settings from django.contrib.auth.models import User from django.contrib.flatpages.models import FlatPage +from django.contrib.sitemaps import Sitemap from django.contrib.sites.models import Site +from django.core.exceptions import ImproperlyConfigured from django.test import TestCase from django.utils.formats import localize from django.utils.translation import activate, deactivate @@ -92,3 +94,22 @@ def test_requestsite_sitemap(self): http://testserver/location/%snever0.5 """ % date.today().strftime('%Y-%m-%d')) + + def test_sitemap_get_urls_no_site_1(self): + """ + Check we get ImproperlyConfigured if we don't pass a site object to + Sitemap.get_urls and no Site objects exist + """ + Site._meta.installed = True + Site.objects.all().delete() + self.assertRaises(ImproperlyConfigured, Sitemap().get_urls) + + def test_sitemap_get_urls_no_site_2(self): + """ + Check we get ImproperlyConfigured when we don't pass a site object to + Sitemap.get_urls if Site objects exists, but the sites framework is not + actually installed. + """ + Site.objects.get_current() + Site._meta.installed = False + self.assertRaises(ImproperlyConfigured, Sitemap().get_urls) diff --git a/django/contrib/sitemaps/views.py b/django/contrib/sitemaps/views.py index 1058cec5014f..b7a96e12aafd 100644 --- a/django/contrib/sitemaps/views.py +++ b/django/contrib/sitemaps/views.py @@ -32,13 +32,13 @@ def sitemap(request, sitemaps, section=None): else: maps = sitemaps.values() page = request.GET.get("p", 1) + current_site = get_current_site(request) for site in maps: - site.request = request try: if callable(site): - urls.extend(site().get_urls(page)) + urls.extend(site().get_urls(page=page, site=current_site)) else: - urls.extend(site.get_urls(page)) + urls.extend(site.get_urls(page=page, site=current_site)) except EmptyPage: raise Http404("Page %s empty" % page) except PageNotAnInteger: From 69d40b4a6f859cd6b08c41abcc8d6a0dc2a803fa Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Mon, 11 Oct 2010 15:18:34 +0000 Subject: [PATCH 308/902] [1.2.X] Fixed #14440 - Converted mail doctests to unittests. Thanks to Rob Hudson for the patch and also to andialbrecht who filed a similar patch that I didn't use. Backport of r14143 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14144 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/mail/tests.py | 734 ++++++++++++---------------- 1 file changed, 316 insertions(+), 418 deletions(-) diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py index 84be585bfd87..4585945b7ae5 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/regressiontests/mail/tests.py @@ -1,420 +1,318 @@ # coding: utf-8 +import email +import os +import shutil +import sys +import tempfile +from StringIO import StringIO +from django.conf import settings +from django.core import mail +from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives +from django.core.mail import send_mail, send_mass_mail +from django.core.mail.backends.base import BaseEmailBackend +from django.core.mail.backends import console, dummy, locmem, filebased, smtp +from django.core.mail.message import BadHeaderError +from django.test import TestCase +from django.utils.translation import ugettext_lazy + +class MailTests(TestCase): + + def test_ascii(self): + email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com']) + message = email.message() + self.assertEqual(message['Subject'].encode(), 'Subject') + self.assertEqual(message.get_payload(), 'Content') + self.assertEqual(message['From'], 'from@example.com') + self.assertEqual(message['To'], 'to@example.com') + + def test_multiple_recipients(self): + email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com','other@example.com']) + message = email.message() + self.assertEqual(message['Subject'].encode(), 'Subject') + self.assertEqual(message.get_payload(), 'Content') + self.assertEqual(message['From'], 'from@example.com') + self.assertEqual(message['To'], 'to@example.com, other@example.com') + + def test_header_injection(self): + email = EmailMessage('Subject\nInjection Test', 'Content', 'from@example.com', ['to@example.com']) + self.assertRaises(BadHeaderError, email.message) + email = EmailMessage(ugettext_lazy('Subject\nInjection Test'), 'Content', 'from@example.com', ['to@example.com']) + self.assertRaises(BadHeaderError, email.message) + + def test_space_continuation(self): + """ + Test for space continuation character in long (ascii) subject headers (#7747) + """ + email = EmailMessage('Long subject lines that get wrapped should use a space continuation character to get expected behaviour in Outlook and Thunderbird', 'Content', 'from@example.com', ['to@example.com']) + message = email.message() + self.assertEqual(message['Subject'], 'Long subject lines that get wrapped should use a space continuation\n character to get expected behaviour in Outlook and Thunderbird') + + def test_message_header_overrides(self): + """ + Specifying dates or message-ids in the extra headers overrides the + default values (#9233) + """ + headers = {"date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} + email = EmailMessage('subject', 'content', 'from@example.com', ['to@example.com'], headers=headers) + self.assertEqual(email.message().as_string(), 'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: subject\nFrom: from@example.com\nTo: to@example.com\ndate: Fri, 09 Nov 2001 01:08:47 -0000\nMessage-ID: foo\n\ncontent') + + def test_empty_admins(self): + """ + Test that mail_admins/mail_managers doesn't connect to the mail server + if there are no recipients (#9383) + """ + old_admins = settings.ADMINS + old_managers = settings.MANAGERS + + settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')] + mail.outbox = [] + mail_admins('hi', 'there') + self.assertEqual(len(mail.outbox), 1) + mail.outbox = [] + mail_managers('hi', 'there') + self.assertEqual(len(mail.outbox), 1) + + settings.ADMINS = settings.MANAGERS = [] + mail.outbox = [] + mail_admins('hi', 'there') + self.assertEqual(len(mail.outbox), 0) + mail.outbox = [] + mail_managers('hi', 'there') + self.assertEqual(len(mail.outbox), 0) + + settings.ADMINS = old_admins + settings.MANAGERS = old_managers + + def test_from_header(self): + """ + Make sure we can manually set the From header (#9214) + """ + email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) + message = email.message() + self.assertEqual(message['From'], 'from@example.com') + + def test_multiple_message_call(self): + """ + Regression for #13259 - Make sure that headers are not changed when + calling EmailMessage.message() + """ + email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) + message = email.message() + self.assertEqual(message['From'], 'from@example.com') + message = email.message() + self.assertEqual(message['From'], 'from@example.com') + + def test_unicode_header(self): + """ + Regression for #11144 - When a to/from/cc header contains unicode, + make sure the email addresses are parsed correctly (especially with + regards to commas) + """ + email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Firstname Sürname" ','other@example.com']) + self.assertEqual(email.message()['To'], '=?utf-8?q?Firstname_S=C3=BCrname?= , other@example.com') + email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Sürname, Firstname" ','other@example.com']) + self.assertEqual(email.message()['To'], '=?utf-8?q?S=C3=BCrname=2C_Firstname?= , other@example.com') + + def test_safe_mime_multipart(self): + """ + Make sure headers can be set with a different encoding than utf-8 in + SafeMIMEMultipart as well + """ + headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} + subject, from_email, to = 'hello', 'from@example.com', '"Sürname, Firstname" ' + text_content = 'This is an important message.' + html_content = '

        This is an important message.

        ' + msg = EmailMultiAlternatives('Message from Firstname Sürname', text_content, from_email, [to], headers=headers) + msg.attach_alternative(html_content, "text/html") + msg.encoding = 'iso-8859-1' + self.assertEqual(msg.message()['To'], '=?iso-8859-1?q?S=FCrname=2C_Firstname?= ') + self.assertEqual(msg.message()['Subject'].encode(), u'=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=') + + def test_encoding(self): + """ + Regression for #12791 - Encode body correctly with other encodings + than utf-8 + """ + email = EmailMessage('Subject', 'Firstname Sürname is a great guy.', 'from@example.com', ['other@example.com']) + email.encoding = 'iso-8859-1' + message = email.message() + self.assertTrue(message.as_string().startswith('Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: other@example.com')) + self.assertEqual(message.get_payload(), 'Firstname S=FCrname is a great guy.') + + # Make sure MIME attachments also works correctly with other encodings than utf-8 + text_content = 'Firstname Sürname is a great guy.' + html_content = '

        Firstname Sürname is a great guy.

        ' + msg = EmailMultiAlternatives('Subject', text_content, 'from@example.com', ['to@example.com']) + msg.encoding = 'iso-8859-1' + msg.attach_alternative(html_content, "text/html") + self.assertEqual(msg.message().get_payload(0).as_string(), 'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.') + self.assertEqual(msg.message().get_payload(1).as_string(), 'Content-Type: text/html; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\n

        Firstname S=FCrname is a great guy.

        ') + + def test_attachments(self): + """Regression test for #9367""" + headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} + subject, from_email, to = 'hello', 'from@example.com', 'to@example.com' + text_content = 'This is an important message.' + html_content = '

        This is an important message.

        ' + msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers=headers) + msg.attach_alternative(html_content, "text/html") + msg.attach("an attachment.pdf", "%PDF-1.4.%...", mimetype="application/pdf") + msg_str = msg.message().as_string() + message = email.message_from_string(msg_str) + self.assertTrue(message.is_multipart()) + self.assertEqual(message.get_content_type(), 'multipart/mixed') + self.assertEqual(message.get_default_type(), 'text/plain') + payload = message.get_payload() + self.assertEqual(payload[0].get_content_type(), 'multipart/alternative') + self.assertEqual(payload[1].get_content_type(), 'application/pdf') + + def test_arbitrary_stream(self): + """ + Test that the console backend can be pointed at an arbitrary stream. + """ + s = StringIO() + connection = mail.get_connection('django.core.mail.backends.console.EmailBackend', stream=s) + send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) + self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: ')) + + def test_stdout(self): + """Make sure that the console backend writes to stdout by default""" + old_stdout = sys.stdout + sys.stdout = StringIO() + connection = console.EmailBackend() + email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) + connection.send_messages([email]) + self.assertTrue(sys.stdout.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: ')) + sys.stdout = old_stdout + + def test_dummy(self): + """ + Make sure that dummy backends returns correct number of sent messages + """ + connection = dummy.EmailBackend() + email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) + self.assertEqual(connection.send_messages([email, email, email]), 3) + + def test_locmem(self): + """ + Make sure that the locmen backend populates the outbox. + """ + mail.outbox = [] + connection = locmem.EmailBackend() + email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) + email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) + connection.send_messages([email1, email2]) + self.assertEqual(len(mail.outbox), 2) + self.assertEqual(mail.outbox[0].subject, 'Subject') + self.assertEqual(mail.outbox[1].subject, 'Subject 2') + + # Make sure that multiple locmem connections share mail.outbox + mail.outbox = [] + connection2 = locmem.EmailBackend() + email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) + connection.send_messages([email]) + connection2.send_messages([email]) + self.assertEqual(len(mail.outbox), 2) + + def test_file_backend(self): + tmp_dir = tempfile.mkdtemp() + connection = filebased.EmailBackend(file_path=tmp_dir) + email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) + connection.send_messages([email1]) + self.assertEqual(len(os.listdir(tmp_dir)), 1) + message = email.message_from_file(open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0]))) + self.assertEqual(message.get_content_type(), 'text/plain') + self.assertEqual(message.get('subject'), 'Subject') + self.assertEqual(message.get('from'), 'from@example.com') + self.assertEqual(message.get('to'), 'to@example.com') + connection2 = filebased.EmailBackend(file_path=tmp_dir) + connection2.send_messages([email1]) + self.assertEqual(len(os.listdir(tmp_dir)), 2) + connection.send_messages([email1]) + self.assertEqual(len(os.listdir(tmp_dir)), 2) + email1.connection = filebased.EmailBackend(file_path=tmp_dir) + connection_created = connection.open() + email1.send() + self.assertEqual(len(os.listdir(tmp_dir)), 3) + email1.send() + self.assertEqual(len(os.listdir(tmp_dir)), 3) + connection.close() + shutil.rmtree(tmp_dir) + + def test_arbitrary_keyword(self): + """ + Make sure that get_connection() accepts arbitrary keyword that might be + used with custom backends. + """ + c = mail.get_connection(fail_silently=True, foo='bar') + self.assertTrue(c.fail_silently) + + def test_custom_backend(self): + """Test custom backend defined in this suite.""" + conn = mail.get_connection('regressiontests.mail.custombackend.EmailBackend') + self.assertTrue(hasattr(conn, 'test_outbox')) + email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) + conn.send_messages([email]) + self.assertEqual(len(conn.test_outbox), 1) + + def test_backend_arg(self): + """Test backend argument of mail.get_connection()""" + self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.smtp.EmailBackend'), smtp.EmailBackend)) + self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.locmem.EmailBackend'), locmem.EmailBackend)) + self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.dummy.EmailBackend'), dummy.EmailBackend)) + self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.console.EmailBackend'), console.EmailBackend)) + tmp_dir = tempfile.mkdtemp() + self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.filebased.EmailBackend', file_path=tmp_dir), filebased.EmailBackend)) + shutil.rmtree(tmp_dir) + self.assertTrue(isinstance(mail.get_connection(), locmem.EmailBackend)) + + def test_connection_arg(self): + """Test connection argument to send_mail(), et. al.""" + connection = mail.get_connection('django.core.mail.backends.locmem.EmailBackend') + + mail.outbox = [] + send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) + self.assertEqual(len(mail.outbox), 1) + message = mail.outbox[0] + self.assertEqual(message.subject, 'Subject') + self.assertEqual(message.from_email, 'from@example.com') + self.assertEqual(message.to, ['to@example.com']) + + mail.outbox = [] + send_mass_mail([ + ('Subject1', 'Content1', 'from1@example.com', ['to1@example.com']), + ('Subject2', 'Content2', 'from2@example.com', ['to2@example.com']) + ], connection=connection) + self.assertEqual(len(mail.outbox), 2) + message = mail.outbox[0] + self.assertEqual(message.subject, 'Subject1') + self.assertEqual(message.from_email, 'from1@example.com') + self.assertEqual(message.to, ['to1@example.com']) + message = mail.outbox[1] + self.assertEqual(message.subject, 'Subject2') + self.assertEqual(message.from_email, 'from2@example.com') + self.assertEqual(message.to, ['to2@example.com']) + + old_admins = settings.ADMINS + old_managers = settings.MANAGERS + settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')] + + mail.outbox = [] + mail_admins('Subject', 'Content', connection=connection) + self.assertEqual(len(mail.outbox), 1) + message = mail.outbox[0] + self.assertEqual(message.subject, '[Django] Subject') + self.assertEqual(message.from_email, 'root@localhost') + self.assertEqual(message.to, ['nobody@example.com']) + + mail.outbox = [] + mail_managers('Subject', 'Content', connection=connection) + self.assertEqual(len(mail.outbox), 1) + message = mail.outbox[0] + self.assertEqual(message.subject, '[Django] Subject') + self.assertEqual(message.from_email, 'root@localhost') + self.assertEqual(message.to, ['nobody@example.com']) + + settings.ADMINS = old_admins + settings.MANAGERS = old_managers -r""" -# Tests for the django.core.mail. - ->>> import os ->>> import shutil ->>> import tempfile ->>> from StringIO import StringIO ->>> from django.conf import settings ->>> from django.core import mail ->>> from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives ->>> from django.core.mail import send_mail, send_mass_mail ->>> from django.core.mail.backends.base import BaseEmailBackend ->>> from django.core.mail.backends import console, dummy, locmem, filebased, smtp ->>> from django.utils.translation import ugettext_lazy - -# Test normal ascii character case: - ->>> email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com']) ->>> message = email.message() ->>> message['Subject'].encode() -'Subject' ->>> message.get_payload() -'Content' ->>> message['From'] -'from@example.com' ->>> message['To'] -'to@example.com' - -# Test multiple-recipient case - ->>> email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com','other@example.com']) ->>> message = email.message() ->>> message['Subject'].encode() -'Subject' ->>> message.get_payload() -'Content' ->>> message['From'] -'from@example.com' ->>> message['To'] -'to@example.com, other@example.com' - -# Test for header injection - ->>> email = EmailMessage('Subject\nInjection Test', 'Content', 'from@example.com', ['to@example.com']) ->>> message = email.message() -Traceback (most recent call last): - ... -BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection Test' for header 'Subject') - ->>> email = EmailMessage(ugettext_lazy('Subject\nInjection Test'), 'Content', 'from@example.com', ['to@example.com']) ->>> message = email.message() -Traceback (most recent call last): - ... -BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection Test' for header 'Subject') - -# Test for space continuation character in long (ascii) subject headers (#7747) - ->>> email = EmailMessage('Long subject lines that get wrapped should use a space continuation character to get expected behaviour in Outlook and Thunderbird', 'Content', 'from@example.com', ['to@example.com']) ->>> message = email.message() ->>> message.as_string() -'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Long subject lines that get wrapped should use a space continuation\n character to get expected behaviour in Outlook and Thunderbird\nFrom: from@example.com\nTo: to@example.com\nDate: ...\nMessage-ID: <...>\n\nContent' - -# Specifying dates or message-ids in the extra headers overrides the defaul -# values (#9233). - ->>> headers = {"date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} ->>> email = EmailMessage('subject', 'content', 'from@example.com', ['to@example.com'], headers=headers) ->>> email.message().as_string() -'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: subject\nFrom: from@example.com\nTo: to@example.com\ndate: Fri, 09 Nov 2001 01:08:47 -0000\nMessage-ID: foo\n\ncontent' - -# Test that mail_admins/mail_managers doesn't connect to the mail server if there are no recipients (#9383) - ->>> old_admins = settings.ADMINS ->>> old_managers = settings.MANAGERS ->>> settings.ADMINS = [] ->>> settings.MANAGERS = [] ->>> mail.outbox = [] ->>> mail_admins('hi','there') ->>> len(mail.outbox) -0 ->>> mail.outbox = [] ->>> mail_managers('hi','there') ->>> len(mail.outbox) -0 ->>> settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')] ->>> mail.outbox = [] ->>> mail_admins('hi','there') ->>> len(mail.outbox) -1 ->>> mail.outbox = [] ->>> mail_managers('hi','there') ->>> len(mail.outbox) -1 - -# Make sure we can manually set the From header (#9214) - ->>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) ->>> message = email.message() ->>> message['From'] -'from@example.com' - -# Regression for #13259 - Make sure that headers are not changed -# when calling EmailMessage.message() ->>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) ->>> message = email.message() ->>> message['From'] -'from@example.com' ->>> message = email.message() ->>> message['From'] -'from@example.com' - -# Regression for #11144 - When a to/from/cc header contains unicode, -# make sure the email addresses are parsed correctly (especially -# with regards to commas) ->>> email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Firstname Sürname" ','other@example.com']) ->>> email.message()['To'] -'=?utf-8?q?Firstname_S=C3=BCrname?= , other@example.com' - ->>> email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Sürname, Firstname" ','other@example.com']) ->>> email.message()['To'] -'=?utf-8?q?S=C3=BCrname=2C_Firstname?= , other@example.com' - -# Regression for #6918 - When a header contains unicode, -# make sure headers can be set with a different encoding than utf-8 ->>> email = EmailMessage('Message from Firstname Sürname', 'Content', 'from@example.com', ['"Sürname, Firstname" ','other@example.com']) ->>> email.encoding = 'iso-8859-1' ->>> email.message()['To'] -'=?iso-8859-1?q?S=FCrname=2C_Firstname?= , other@example.com' ->>> email.message()['Subject'].encode() -u'=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=' - -# Make sure headers can be set with a different encoding than utf-8 in SafeMIMEMultipart as well ->>> headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} ->>> subject, from_email, to = 'hello', 'from@example.com', '"Sürname, Firstname" ' ->>> text_content = 'This is an important message.' ->>> html_content = '

        This is an important message.

        ' ->>> msg = EmailMultiAlternatives('Message from Firstname Sürname', text_content, from_email, [to], headers=headers) ->>> msg.attach_alternative(html_content, "text/html") ->>> msg.encoding = 'iso-8859-1' ->>> msg.message()['To'] -'=?iso-8859-1?q?S=FCrname=2C_Firstname?= ' ->>> msg.message()['Subject'].encode() -u'=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=' - -# Regression for #12791 - Encode body correctly with other encodings than utf-8 ->>> email = EmailMessage('Subject', 'Firstname Sürname is a great guy.', 'from@example.com', ['other@example.com']) ->>> email.encoding = 'iso-8859-1' ->>> message = email.message() ->>> message.as_string() -'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: other@example.com\nDate: ...\nMessage-ID: <...>\n\nFirstname S=FCrname is a great guy.' - -# Make sure MIME attachments also works correctly with other encodings than utf-8 ->>> text_content = 'Firstname Sürname is a great guy.' ->>> html_content = '

        Firstname Sürname is a great guy.

        ' ->>> msg = EmailMultiAlternatives('Subject', text_content, 'from@example.com', ['to@example.com']) ->>> msg.encoding = 'iso-8859-1' ->>> msg.attach_alternative(html_content, "text/html") ->>> msg.message().get_payload(0).as_string() -'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.' ->>> msg.message().get_payload(1).as_string() -'Content-Type: text/html; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\n

        Firstname S=FCrname is a great guy.

        ' - -# Handle attachments within an multipart/alternative mail correctly (#9367) -# (test is not as precise/clear as it could be w.r.t. email tree structure, -# but it's good enough.) ->>> headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} ->>> subject, from_email, to = 'hello', 'from@example.com', 'to@example.com' ->>> text_content = 'This is an important message.' ->>> html_content = '

        This is an important message.

        ' ->>> msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers=headers) ->>> msg.attach_alternative(html_content, "text/html") ->>> msg.attach("an attachment.pdf", "%PDF-1.4.%...", mimetype="application/pdf") ->>> print msg.message().as_string() -Content-Type: multipart/mixed; boundary="..." -MIME-Version: 1.0 -Subject: hello -From: from@example.com -To: to@example.com -Date: Fri, 09 Nov 2001 01:08:47 -0000 -Message-ID: foo -... -Content-Type: multipart/alternative;... -... -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -... -This is an important message. -... -Content-Type: text/html; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -... -

        This is an important message.

        -... -... -Content-Type: application/pdf -MIME-Version: 1.0 -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; filename="an attachment.pdf" -... -JVBERi0xLjQuJS4uLg== -... - -# Make sure that the console backend writes to stdout by default ->>> connection = console.EmailBackend() ->>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) ->>> connection.send_messages([email]) -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -Subject: Subject -From: from@example.com -To: to@example.com -Date: ... -Message-ID: ... - -Content -------------------------------------------------------------------------------- -1 - -# Test that the console backend can be pointed at an arbitrary stream ->>> s = StringIO() ->>> connection = mail.get_connection('django.core.mail.backends.console.EmailBackend', stream=s) ->>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) -1 ->>> print s.getvalue() -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -Subject: Subject -From: from@example.com -To: to@example.com -Date: ... -Message-ID: ... - -Content -------------------------------------------------------------------------------- - -# Make sure that dummy backends returns correct number of sent messages ->>> connection = dummy.EmailBackend() ->>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) ->>> connection.send_messages([email, email, email]) -3 - -# Make sure that locmen backend populates the outbox ->>> mail.outbox = [] ->>> connection = locmem.EmailBackend() ->>> email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) ->>> email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) ->>> connection.send_messages([email1, email2]) -2 ->>> len(mail.outbox) -2 ->>> mail.outbox[0].subject -'Subject' ->>> mail.outbox[1].subject -'Subject 2' - -# Make sure that multiple locmem connections share mail.outbox ->>> mail.outbox = [] ->>> connection1 = locmem.EmailBackend() ->>> connection2 = locmem.EmailBackend() ->>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) ->>> connection1.send_messages([email]) -1 ->>> connection2.send_messages([email]) -1 ->>> len(mail.outbox) -2 - -# Make sure that the file backend write to the right location ->>> tmp_dir = tempfile.mkdtemp() ->>> connection = filebased.EmailBackend(file_path=tmp_dir) ->>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) ->>> connection.send_messages([email]) -1 ->>> len(os.listdir(tmp_dir)) -1 ->>> print open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0])).read() -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -Subject: Subject -From: from@example.com -To: to@example.com -Date: ... -Message-ID: ... - -Content -------------------------------------------------------------------------------- - ->>> connection2 = filebased.EmailBackend(file_path=tmp_dir) ->>> connection2.send_messages([email]) -1 ->>> len(os.listdir(tmp_dir)) -2 ->>> connection.send_messages([email]) -1 ->>> len(os.listdir(tmp_dir)) -2 ->>> email.connection = filebased.EmailBackend(file_path=tmp_dir) ->>> connection_created = connection.open() ->>> num_sent = email.send() ->>> len(os.listdir(tmp_dir)) -3 ->>> num_sent = email.send() ->>> len(os.listdir(tmp_dir)) -3 ->>> connection.close() ->>> shutil.rmtree(tmp_dir) - -# Make sure that get_connection() accepts arbitrary keyword that might be -# used with custom backends. ->>> c = mail.get_connection(fail_silently=True, foo='bar') ->>> c.fail_silently -True - -# Test custom backend defined in this suite. ->>> conn = mail.get_connection('regressiontests.mail.custombackend.EmailBackend') ->>> hasattr(conn, 'test_outbox') -True ->>> email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) ->>> conn.send_messages([email]) -1 ->>> len(conn.test_outbox) -1 - -# Test backend argument of mail.get_connection() ->>> isinstance(mail.get_connection('django.core.mail.backends.smtp.EmailBackend'), smtp.EmailBackend) -True ->>> isinstance(mail.get_connection('django.core.mail.backends.locmem.EmailBackend'), locmem.EmailBackend) -True ->>> isinstance(mail.get_connection('django.core.mail.backends.dummy.EmailBackend'), dummy.EmailBackend) -True ->>> isinstance(mail.get_connection('django.core.mail.backends.console.EmailBackend'), console.EmailBackend) -True ->>> tmp_dir = tempfile.mkdtemp() ->>> isinstance(mail.get_connection('django.core.mail.backends.filebased.EmailBackend', file_path=tmp_dir), filebased.EmailBackend) -True ->>> shutil.rmtree(tmp_dir) ->>> isinstance(mail.get_connection(), locmem.EmailBackend) -True - -# Test connection argument of send_mail() et al ->>> connection = mail.get_connection('django.core.mail.backends.console.EmailBackend') ->>> send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -Subject: Subject -From: from@example.com -To: to@example.com -Date: ... -Message-ID: ... - -Content -------------------------------------------------------------------------------- -1 - ->>> send_mass_mail([ -... ('Subject1', 'Content1', 'from1@example.com', ['to1@example.com']), -... ('Subject2', 'Content2', 'from2@example.com', ['to2@example.com']) -... ], connection=connection) -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -Subject: Subject1 -From: from1@example.com -To: to1@example.com -Date: ... -Message-ID: ... - -Content1 -------------------------------------------------------------------------------- -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -Subject: Subject2 -From: from2@example.com -To: to2@example.com -Date: ... -Message-ID: ... - -Content2 -------------------------------------------------------------------------------- -2 - ->>> mail_admins('Subject', 'Content', connection=connection) -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -Subject: [Django] Subject -From: root@localhost -To: nobody@example.com -Date: ... -Message-ID: ... - -Content -------------------------------------------------------------------------------- - ->>> mail_managers('Subject', 'Content', connection=connection) -Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: quoted-printable -Subject: [Django] Subject -From: root@localhost -To: nobody@example.com -Date: ... -Message-ID: ... - -Content -------------------------------------------------------------------------------- - ->>> settings.ADMINS = old_admins ->>> settings.MANAGERS = old_managers - -""" From 6d34d308aa5efb5492a032d6b9d1042a0145bdea Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 11 Oct 2010 17:41:59 +0000 Subject: [PATCH 309/902] [1.2.X] Converted save_delete_hooks tests from doctests to unittests. We have always been at war with doctests. Backport of [14145]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14146 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/save_delete_hooks/models.py | 37 +++++++------------- tests/modeltests/save_delete_hooks/tests.py | 30 ++++++++++++++++ 2 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 tests/modeltests/save_delete_hooks/tests.py diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py index 54c9defd8b40..515c7f6c917e 100644 --- a/tests/modeltests/save_delete_hooks/models.py +++ b/tests/modeltests/save_delete_hooks/models.py @@ -7,37 +7,26 @@ from django.db import models + class Person(models.Model): first_name = models.CharField(max_length=20) last_name = models.CharField(max_length=20) + def __init__(self, *args, **kwargs): + super(Person, self).__init__(*args, **kwargs) + self.data = [] + def __unicode__(self): return u"%s %s" % (self.first_name, self.last_name) - def save(self, force_insert=False, force_update=False): - print "Before save" + def save(self, *args, **kwargs): + self.data.append("Before save") # Call the "real" save() method - super(Person, self).save(force_insert, force_update) - print "After save" + super(Person, self).save(*args, **kwargs) + self.data.append("After save") def delete(self): - print "Before deletion" - super(Person, self).delete() # Call the "real" delete() method - print "After deletion" - -__test__ = {'API_TESTS':""" ->>> p1 = Person(first_name='John', last_name='Smith') ->>> p1.save() -Before save -After save - ->>> Person.objects.all() -[] - ->>> p1.delete() -Before deletion -After deletion - ->>> Person.objects.all() -[] -"""} + self.data.append("Before deletion") + # Call the "real" delete() method + super(Person, self).delete() + self.data.append("After deletion") diff --git a/tests/modeltests/save_delete_hooks/tests.py b/tests/modeltests/save_delete_hooks/tests.py new file mode 100644 index 000000000000..dc7b8ee12ab8 --- /dev/null +++ b/tests/modeltests/save_delete_hooks/tests.py @@ -0,0 +1,30 @@ +from django.test import TestCase + +from models import Person + + +class SaveDeleteHookTests(TestCase): + def test_basic(self): + p = Person(first_name="John", last_name="Smith") + self.assertEqual(p.data, []) + p.save() + self.assertEqual(p.data, [ + "Before save", + "After save", + ]) + + self.assertQuerysetEqual( + Person.objects.all(), [ + "John Smith", + ], + unicode + ) + + p.delete() + self.assertEqual(p.data, [ + "Before save", + "After save", + "Before deletion", + "After deletion", + ]) + self.assertQuerysetEqual(Person.objects.all(), []) From efc752423677bec795a5314e060e3a9ecce9fedc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 11 Oct 2010 18:18:12 +0000 Subject: [PATCH 310/902] [1.2.X] Converted ordering tests from doctests to unittests. We have always been at war with doctests. Backport of [14147]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14148 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/ordering/models.py | 65 +------------ tests/modeltests/ordering/tests.py | 137 ++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 64 deletions(-) create mode 100644 tests/modeltests/ordering/tests.py diff --git a/tests/modeltests/ordering/models.py b/tests/modeltests/ordering/models.py index a53d93c33002..25d3c2c90f1f 100644 --- a/tests/modeltests/ordering/models.py +++ b/tests/modeltests/ordering/models.py @@ -15,6 +15,7 @@ from django.db import models + class Article(models.Model): headline = models.CharField(max_length=100) pub_date = models.DateTimeField() @@ -23,67 +24,3 @@ class Meta: def __unicode__(self): return self.headline - -__test__ = {'API_TESTS':""" -# Create a couple of Articles. ->>> from datetime import datetime ->>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26)) ->>> a1.save() ->>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27)) ->>> a2.save() ->>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27)) ->>> a3.save() ->>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28)) ->>> a4.save() - -# By default, Article.objects.all() orders by pub_date descending, then -# headline ascending. ->>> Article.objects.all() -[, , , ] - -# Override ordering with order_by, which is in the same format as the ordering -# attribute in models. ->>> Article.objects.order_by('headline') -[, , , ] ->>> Article.objects.order_by('pub_date', '-headline') -[, , , ] - -# Only the last order_by has any effect (since they each override any previous -# ordering). ->>> Article.objects.order_by('id') -[, , , ] ->>> Article.objects.order_by('id').order_by('-headline') -[, , , ] - -# Use the 'stop' part of slicing notation to limit the results. ->>> Article.objects.order_by('headline')[:2] -[, ] - -# Use the 'stop' and 'start' parts of slicing notation to offset the result list. ->>> Article.objects.order_by('headline')[1:3] -[, ] - -# Getting a single item should work too: ->>> Article.objects.all()[0] - - -# Use '?' to order randomly. (We're using [...] in the output to indicate we -# don't know what order the output will be in. ->>> Article.objects.order_by('?') -[...] - -# Ordering can be reversed using the reverse() method on a queryset. This -# allows you to extract things like "the last two items" (reverse and then -# take the first two). ->>> Article.objects.all().reverse()[:2] -[, ] - -# Ordering can be based on fields included from an 'extra' clause ->>> Article.objects.extra(select={'foo': 'pub_date'}, order_by=['foo', 'headline']) -[, , , ] - -# If the extra clause uses an SQL keyword for a name, it will be protected by quoting. ->>> Article.objects.extra(select={'order': 'pub_date'}, order_by=['order', 'headline']) -[, , , ] - -"""} diff --git a/tests/modeltests/ordering/tests.py b/tests/modeltests/ordering/tests.py new file mode 100644 index 000000000000..77862c528cb0 --- /dev/null +++ b/tests/modeltests/ordering/tests.py @@ -0,0 +1,137 @@ +from datetime import datetime +from operator import attrgetter + +from django.test import TestCase + +from models import Article + + +class OrderingTests(TestCase): + def test_basic(self): + a1 = Article.objects.create( + headline="Article 1", pub_date=datetime(2005, 7, 26) + ) + a2 = Article.objects.create( + headline="Article 2", pub_date=datetime(2005, 7, 27) + ) + a3 = Article.objects.create( + headline="Article 3", pub_date=datetime(2005, 7, 27) + ) + a4 = Article.objects.create( + headline="Article 4", pub_date=datetime(2005, 7, 28) + ) + + # By default, Article.objects.all() orders by pub_date descending, then + # headline ascending. + self.assertQuerysetEqual( + Article.objects.all(), [ + "Article 4", + "Article 2", + "Article 3", + "Article 1", + ], + attrgetter("headline") + ) + + # Override ordering with order_by, which is in the same format as the + # ordering attribute in models. + self.assertQuerysetEqual( + Article.objects.order_by("headline"), [ + "Article 1", + "Article 2", + "Article 3", + "Article 4", + ], + attrgetter("headline") + ) + self.assertQuerysetEqual( + Article.objects.order_by("pub_date", "-headline"), [ + "Article 1", + "Article 3", + "Article 2", + "Article 4", + ], + attrgetter("headline") + ) + + # Only the last order_by has any effect (since they each override any + # previous ordering). + self.assertQuerysetEqual( + Article.objects.order_by("id"), [ + "Article 1", + "Article 2", + "Article 3", + "Article 4", + ], + attrgetter("headline") + ) + self.assertQuerysetEqual( + Article.objects.order_by("id").order_by("-headline"), [ + "Article 4", + "Article 3", + "Article 2", + "Article 1", + ], + attrgetter("headline") + ) + + # Use the 'stop' part of slicing notation to limit the results. + self.assertQuerysetEqual( + Article.objects.order_by("headline")[:2], [ + "Article 1", + "Article 2", + ], + attrgetter("headline") + ) + + # Use the 'stop' and 'start' parts of slicing notation to offset the + # result list. + self.assertQuerysetEqual( + Article.objects.order_by("headline")[1:3], [ + "Article 2", + "Article 3", + ], + attrgetter("headline") + ) + + # Getting a single item should work too: + self.assertEqual(Article.objects.all()[0], a4) + + # Use '?' to order randomly. + self.assertEqual( + len(list(Article.objects.order_by("?"))), 4 + ) + + # Ordering can be reversed using the reverse() method on a queryset. + # This allows you to extract things like "the last two items" (reverse + # and then take the first two). + self.assertQuerysetEqual( + Article.objects.all().reverse()[:2], [ + "Article 1", + "Article 3", + ], + attrgetter("headline") + ) + + # Ordering can be based on fields included from an 'extra' clause + self.assertQuerysetEqual( + Article.objects.extra(select={"foo": "pub_date"}, order_by=["foo", "headline"]), [ + "Article 1", + "Article 2", + "Article 3", + "Article 4", + ], + attrgetter("headline") + ) + + # If the extra clause uses an SQL keyword for a name, it will be + # protected by quoting. + self.assertQuerysetEqual( + Article.objects.extra(select={"order": "pub_date"}, order_by=["order", "headline"]), [ + "Article 1", + "Article 2", + "Article 3", + "Article 4", + ], + attrgetter("headline") + ) From 01d1dd6935f59a16516d4b481b6dd13edfd7b033 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 11 Oct 2010 20:38:29 +0000 Subject: [PATCH 311/902] [1.2.X] Fixed #14444 -- Convert the pagination doctests to unittests. We have always been at war with doctests. Thanks to Gabriel Hurley for the patch. Backport of [14152]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14153 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/pagination/models.py | 165 +------------------------- tests/modeltests/pagination/tests.py | 132 +++++++++++++++++++++ 2 files changed, 133 insertions(+), 164 deletions(-) create mode 100644 tests/modeltests/pagination/tests.py diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py index 9b79a6a74e19..48484dd59baa 100644 --- a/tests/modeltests/pagination/models.py +++ b/tests/modeltests/pagination/models.py @@ -8,173 +8,10 @@ from django.db import models + class Article(models.Model): headline = models.CharField(max_length=100, default='Default headline') pub_date = models.DateTimeField() def __unicode__(self): return self.headline - -__test__ = {'API_TESTS':""" -# Prepare a list of objects for pagination. ->>> from datetime import datetime ->>> for x in range(1, 10): -... a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29)) -... a.save() - -################## -# Paginator/Page # -################## - ->>> from django.core.paginator import Paginator ->>> paginator = Paginator(Article.objects.all(), 5) ->>> paginator.count -9 ->>> paginator.num_pages -2 ->>> paginator.page_range -[1, 2] - -# Get the first page. ->>> p = paginator.page(1) ->>> p - ->>> p.object_list -[, , , , ] ->>> p.has_next() -True ->>> p.has_previous() -False ->>> p.has_other_pages() -True ->>> p.next_page_number() -2 ->>> p.previous_page_number() -0 ->>> p.start_index() -1 ->>> p.end_index() -5 - -# Get the second page. ->>> p = paginator.page(2) ->>> p - ->>> p.object_list -[, , , ] ->>> p.has_next() -False ->>> p.has_previous() -True ->>> p.has_other_pages() -True ->>> p.next_page_number() -3 ->>> p.previous_page_number() -1 ->>> p.start_index() -6 ->>> p.end_index() -9 - -# Empty pages raise EmptyPage. ->>> paginator.page(0) -Traceback (most recent call last): -... -EmptyPage: ... ->>> paginator.page(3) -Traceback (most recent call last): -... -EmptyPage: ... - -# Empty paginators with allow_empty_first_page=True. ->>> paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=True) ->>> paginator.count -0 ->>> paginator.num_pages -1 ->>> paginator.page_range -[1] - -# Empty paginators with allow_empty_first_page=False. ->>> paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=False) ->>> paginator.count -0 ->>> paginator.num_pages -0 ->>> paginator.page_range -[] - -# Paginators work with regular lists/tuples, too -- not just with QuerySets. ->>> paginator = Paginator([1, 2, 3, 4, 5, 6, 7, 8, 9], 5) ->>> paginator.count -9 ->>> paginator.num_pages -2 ->>> paginator.page_range -[1, 2] - -# Get the first page. ->>> p = paginator.page(1) ->>> p - ->>> p.object_list -[1, 2, 3, 4, 5] ->>> p.has_next() -True ->>> p.has_previous() -False ->>> p.has_other_pages() -True ->>> p.next_page_number() -2 ->>> p.previous_page_number() -0 ->>> p.start_index() -1 ->>> p.end_index() -5 - -# Paginator can be passed other objects with a count() method. ->>> class CountContainer: -... def count(self): -... return 42 ->>> paginator = Paginator(CountContainer(), 10) ->>> paginator.count -42 ->>> paginator.num_pages -5 ->>> paginator.page_range -[1, 2, 3, 4, 5] - -# Paginator can be passed other objects that implement __len__. ->>> class LenContainer: -... def __len__(self): -... return 42 ->>> paginator = Paginator(LenContainer(), 10) ->>> paginator.count -42 ->>> paginator.num_pages -5 ->>> paginator.page_range -[1, 2, 3, 4, 5] - - -################## -# Orphan support # -################## - -# Add a few more records to test out the orphans feature. ->>> for x in range(10, 13): -... Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save() - -# With orphans set to 3 and 10 items per page, we should get all 12 items on a single page. ->>> paginator = Paginator(Article.objects.all(), 10, orphans=3) ->>> paginator.num_pages -1 - -# With orphans only set to 1, we should get two pages. ->>> paginator = Paginator(Article.objects.all(), 10, orphans=1) ->>> paginator.num_pages -2 -"""} diff --git a/tests/modeltests/pagination/tests.py b/tests/modeltests/pagination/tests.py new file mode 100644 index 000000000000..eaee46691326 --- /dev/null +++ b/tests/modeltests/pagination/tests.py @@ -0,0 +1,132 @@ +from datetime import datetime +from operator import attrgetter + +from django.core.paginator import Paginator, InvalidPage, EmptyPage +from django.test import TestCase + +from models import Article + + +class CountContainer(object): + def count(self): + return 42 + +class LenContainer(object): + def __len__(self): + return 42 + +class PaginationTests(TestCase): + def setUp(self): + # Prepare a list of objects for pagination. + for x in range(1, 10): + a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29)) + a.save() + + def test_paginator(self): + paginator = Paginator(Article.objects.all(), 5) + self.assertEqual(9, paginator.count) + self.assertEqual(2, paginator.num_pages) + self.assertEqual([1, 2], paginator.page_range) + + def test_first_page(self): + paginator = Paginator(Article.objects.all(), 5) + p = paginator.page(1) + self.assertEqual(u"", unicode(p)) + self.assertQuerysetEqual(p.object_list, [ + "", + "", + "", + "", + "" + ] + ) + self.assertTrue(p.has_next()) + self.assertFalse(p.has_previous()) + self.assertTrue(p.has_other_pages()) + self.assertEqual(2, p.next_page_number()) + self.assertEqual(0, p.previous_page_number()) + self.assertEqual(1, p.start_index()) + self.assertEqual(5, p.end_index()) + + def test_last_page(self): + paginator = Paginator(Article.objects.all(), 5) + p = paginator.page(2) + self.assertEqual(u"", unicode(p)) + self.assertQuerysetEqual(p.object_list, [ + "", + "", + "", + "" + ] + ) + self.assertFalse(p.has_next()) + self.assertTrue(p.has_previous()) + self.assertTrue(p.has_other_pages()) + self.assertEqual(3, p.next_page_number()) + self.assertEqual(1, p.previous_page_number()) + self.assertEqual(6, p.start_index()) + self.assertEqual(9, p.end_index()) + + def test_empty_page(self): + paginator = Paginator(Article.objects.all(), 5) + self.assertRaises(EmptyPage, paginator.page, 0) + self.assertRaises(EmptyPage, paginator.page, 3) + + # Empty paginators with allow_empty_first_page=True. + paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=True) + self.assertEqual(0, paginator.count) + self.assertEqual(1, paginator.num_pages) + self.assertEqual([1], paginator.page_range) + + # Empty paginators with allow_empty_first_page=False. + paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=False) + self.assertEqual(0, paginator.count) + self.assertEqual(0, paginator.num_pages) + self.assertEqual([], paginator.page_range) + + def test_invalid_page(self): + paginator = Paginator(Article.objects.all(), 5) + self.assertRaises(InvalidPage, paginator.page, 7) + + def test_orphans(self): + # Add a few more records to test out the orphans feature. + for x in range(10, 13): + Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save() + + # With orphans set to 3 and 10 items per page, we should get all 12 items on a single page. + paginator = Paginator(Article.objects.all(), 10, orphans=3) + self.assertEqual(1, paginator.num_pages) + + # With orphans only set to 1, we should get two pages. + paginator = Paginator(Article.objects.all(), 10, orphans=1) + self.assertEqual(2, paginator.num_pages) + + def test_paginate_list(self): + # Paginators work with regular lists/tuples, too -- not just with QuerySets. + paginator = Paginator([1, 2, 3, 4, 5, 6, 7, 8, 9], 5) + self.assertEqual(9, paginator.count) + self.assertEqual(2, paginator.num_pages) + self.assertEqual([1, 2], paginator.page_range) + p = paginator.page(1) + self.assertEqual(u"", unicode(p)) + self.assertEqual([1, 2, 3, 4, 5], p.object_list) + self.assertTrue(p.has_next()) + self.assertFalse(p.has_previous()) + self.assertTrue(p.has_other_pages()) + self.assertEqual(2, p.next_page_number()) + self.assertEqual(0, p.previous_page_number()) + self.assertEqual(1, p.start_index()) + self.assertEqual(5, p.end_index()) + + def test_paginate_misc_classes(self): + # Paginator can be passed other objects with a count() method. + paginator = Paginator(CountContainer(), 10) + self.assertEqual(42, paginator.count) + self.assertEqual(5, paginator.num_pages) + self.assertEqual([1, 2, 3, 4, 5], paginator.page_range) + + # Paginator can be passed other objects that implement __len__. + paginator = Paginator(LenContainer(), 10) + self.assertEqual(42, paginator.count) + self.assertEqual(5, paginator.num_pages) + self.assertEqual([1, 2, 3, 4, 5], paginator.page_range) From d2724d883e1913f34f473c6567675fb1d0f26663 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Mon, 11 Oct 2010 21:58:46 +0000 Subject: [PATCH 312/902] [1.2.X] Added information about the "easy-pickings" keyword to the contributing docs. Thanks to Russ for the report and cmheisel for the patch. Backport of [14154] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14155 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/contributing.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/internals/contributing.txt b/docs/internals/contributing.txt index 6310562cf68d..162a9d3c5269 100644 --- a/docs/internals/contributing.txt +++ b/docs/internals/contributing.txt @@ -15,7 +15,9 @@ of the community, so there are many ways you can help Django's development: served up. * Submit patches for new and/or fixed behavior. Please read `Submitting - patches`_, below, for details on how to submit a patch. + patches`_, below, for details on how to submit a patch. If you're looking + for an easy way to start contributing to Django have a look at the + `easy-pickings`_ tickets. * Join the `django-developers`_ mailing list and share your ideas for how to improve Django. We're always open to suggestions, although we're @@ -354,6 +356,9 @@ members can do to help the triage process. In particular, you can help out by: * Correcting the "Needs tests", "Needs documentation", or "Has patch" flags for tickets where they are incorrectly set. + * Adding the `easy-pickings`_ keyword to tickets that are small and + relatively straightforward. + * Checking that old tickets are still valid. If a ticket hasn't seen any activity in a long time, it's possible that the problem has been fixed but the ticket hasn't yet been closed. @@ -1282,3 +1287,4 @@ requests for commit access are potential flame-war starters, and will be ignored .. _pep8.py: http://pypi.python.org/pypi/pep8/ .. _i18n branch: http://code.djangoproject.com/browser/django/branches/i18n .. _`tags/releases`: http://code.djangoproject.com/browser/django/tags/releases +.. _`easy-pickings`: http://code.djangoproject.com/query?status=new&status=assigned&status=reopened&keywords=~easy-pickings&order=priority From cc1a41f3de96713956215e6ba691d3f33f7716b3 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Mon, 11 Oct 2010 22:24:59 +0000 Subject: [PATCH 313/902] [1.2.X] Updated version of PostGIS in GeoDjango install docs. Backport of r14150 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14156 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/gis/install.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index 90191dcec605..a62e6aaf8032 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -240,9 +240,9 @@ installed prior to building PostGIS. First download the source archive, and extract:: - $ wget http://postgis.refractions.net/download/postgis-1.5.1.tar.gz - $ tar xzf postgis-1.5.1.tar.gz - $ cd postgis-1.5.1 + $ wget http://postgis.refractions.net/download/postgis-1.5.2.tar.gz + $ tar xzf postgis-1.5.2.tar.gz + $ cd postgis-1.5.2 Next, configure, make and install PostGIS:: From cf6b26d7a2e7fd3384e01a68102f6649e6ad6abc Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 11 Oct 2010 22:35:24 +0000 Subject: [PATCH 314/902] [1.2.X] Fixed links to the date formats choices in the global settings. Thanks, Russell. Backport from trunk (r14032). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14158 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/global_settings.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 2714bfb9ab4b..e86f1055f544 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -292,34 +292,34 @@ FORMAT_MODULE_PATH = None # Default formatting for date objects. See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now +# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATE_FORMAT = 'N j, Y' # Default formatting for datetime objects. See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now +# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date DATETIME_FORMAT = 'N j, Y, P' # Default formatting for time objects. See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now +# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date TIME_FORMAT = 'P' # Default formatting for date objects when only the year and month are relevant. # See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now +# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date YEAR_MONTH_FORMAT = 'F Y' # Default formatting for date objects when only the month and day are relevant. # See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now +# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date MONTH_DAY_FORMAT = 'F j' # Default short formatting for date objects. See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now +# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date SHORT_DATE_FORMAT = 'm/d/Y' # Default short formatting for datetime objects. # See all available format strings here: -# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now +# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date SHORT_DATETIME_FORMAT = 'm/d/Y P' # Default formats to be used when parsing dates from input boxes, in order From d327611b241e222922cd1161fbbebe4a23f8e485 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 11 Oct 2010 22:36:17 +0000 Subject: [PATCH 315/902] [1.2.X] Fixed #13494 -- Correctly concat an email subject prefix with a translation string. Thanks, hcarvalhoalves and Andi Albrecht. Backport from trunk (r14157). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14159 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/mail/__init__.py | 4 ++-- tests/regressiontests/mail/tests.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/django/core/mail/__init__.py b/django/core/mail/__init__.py index f9d1210791a6..8a2d9bf096f9 100644 --- a/django/core/mail/__init__.py +++ b/django/core/mail/__init__.py @@ -87,7 +87,7 @@ def mail_admins(subject, message, fail_silently=False, connection=None): """Sends a message to the admins, as defined by the ADMINS setting.""" if not settings.ADMINS: return - EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, + EmailMessage(u'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message, settings.SERVER_EMAIL, [a[1] for a in settings.ADMINS], connection=connection).send(fail_silently=fail_silently) @@ -96,7 +96,7 @@ def mail_managers(subject, message, fail_silently=False, connection=None): """Sends a message to the managers, as defined by the MANAGERS setting.""" if not settings.MANAGERS: return - EmailMessage(settings.EMAIL_SUBJECT_PREFIX + subject, message, + EmailMessage(u'%s%s' % (settings.EMAIL_SUBJECT_PREFIX, subject), message, settings.SERVER_EMAIL, [a[1] for a in settings.MANAGERS], connection=connection).send(fail_silently=fail_silently) diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py index 4585945b7ae5..f13d280bd685 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/regressiontests/mail/tests.py @@ -316,3 +316,23 @@ def test_connection_arg(self): settings.ADMINS = old_admins settings.MANAGERS = old_managers + def test_mail_prefix(self): + """Test prefix argument in manager/admin mail.""" + # Regression for #13494. + old_admins = settings.ADMINS + old_managers = settings.MANAGERS + settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')] + + mail_managers(ugettext_lazy('Subject'), 'Content') + self.assertEqual(len(mail.outbox), 1) + message = mail.outbox[0] + self.assertEqual(message.subject, '[Django] Subject') + + mail.outbox = [] + mail_admins(ugettext_lazy('Subject'), 'Content') + self.assertEqual(len(mail.outbox), 1) + message = mail.outbox[0] + self.assertEqual(message.subject, '[Django] Subject') + + settings.ADMINS = old_admins + settings.MANAGERS = old_managers From b12428de91faa8975dca04f6d7e4c5db76a1fd99 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 12 Oct 2010 01:00:58 +0000 Subject: [PATCH 316/902] [1.2.X] Migrated m2o_recursive and m2o_recursive2 tests, merging them into a single package. Thanks to George Sakkis for the patches. Backport of r14163 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14169 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/m2o_recursive/models.py | 26 ++++--------- tests/modeltests/m2o_recursive/tests.py | 38 ++++++++++++++++++ tests/modeltests/m2o_recursive2/__init__.py | 0 tests/modeltests/m2o_recursive2/models.py | 43 --------------------- 4 files changed, 45 insertions(+), 62 deletions(-) create mode 100644 tests/modeltests/m2o_recursive/tests.py delete mode 100644 tests/modeltests/m2o_recursive2/__init__.py delete mode 100644 tests/modeltests/m2o_recursive2/models.py diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/modeltests/m2o_recursive/models.py index f58f7fe0f59a..ed9945a6c551 100644 --- a/tests/modeltests/m2o_recursive/models.py +++ b/tests/modeltests/m2o_recursive/models.py @@ -19,22 +19,10 @@ class Category(models.Model): def __unicode__(self): return self.name -__test__ = {'API_TESTS':""" -# Create a few Category objects. ->>> r = Category(id=None, name='Root category', parent=None) ->>> r.save() ->>> c = Category(id=None, name='Child category', parent=r) ->>> c.save() - ->>> r.child_set.all() -[] ->>> r.child_set.get(name__startswith='Child') - ->>> print r.parent -None - ->>> c.child_set.all() -[] ->>> c.parent - -"""} +class Person(models.Model): + full_name = models.CharField(max_length=20) + mother = models.ForeignKey('self', null=True, related_name='mothers_child_set') + father = models.ForeignKey('self', null=True, related_name='fathers_child_set') + + def __unicode__(self): + return self.full_name diff --git a/tests/modeltests/m2o_recursive/tests.py b/tests/modeltests/m2o_recursive/tests.py new file mode 100644 index 000000000000..79dde8b5ea97 --- /dev/null +++ b/tests/modeltests/m2o_recursive/tests.py @@ -0,0 +1,38 @@ +from django.test import TestCase +from models import Category, Person + +class ManyToOneRecursiveTests(TestCase): + + def setUp(self): + self.r = Category(id=None, name='Root category', parent=None) + self.r.save() + self.c = Category(id=None, name='Child category', parent=self.r) + self.c.save() + + def test_m2o_recursive(self): + self.assertQuerysetEqual(self.r.child_set.all(), + ['']) + self.assertEqual(self.r.child_set.get(name__startswith='Child').id, self.c.id) + self.assertEqual(self.r.parent, None) + self.assertQuerysetEqual(self.c.child_set.all(), []) + self.assertEqual(self.c.parent.id, self.r.id) + +class MultipleManyToOneRecursiveTests(TestCase): + + def setUp(self): + self.dad = Person(full_name='John Smith Senior', mother=None, father=None) + self.dad.save() + self.mom = Person(full_name='Jane Smith', mother=None, father=None) + self.mom.save() + self.kid = Person(full_name='John Smith Junior', mother=self.mom, father=self.dad) + self.kid.save() + + def test_m2o_recursive2(self): + self.assertEqual(self.kid.mother.id, self.mom.id) + self.assertEqual(self.kid.father.id, self.dad.id) + self.assertQuerysetEqual(self.dad.fathers_child_set.all(), + ['']) + self.assertQuerysetEqual(self.mom.mothers_child_set.all(), + ['']) + self.assertQuerysetEqual(self.kid.mothers_child_set.all(), []) + self.assertQuerysetEqual(self.kid.fathers_child_set.all(), []) diff --git a/tests/modeltests/m2o_recursive2/__init__.py b/tests/modeltests/m2o_recursive2/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/tests/modeltests/m2o_recursive2/models.py b/tests/modeltests/m2o_recursive2/models.py deleted file mode 100644 index 47af18ba0eba..000000000000 --- a/tests/modeltests/m2o_recursive2/models.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -12. Relating a model to another model more than once - -In this example, a ``Person`` can have a ``mother`` and ``father`` -- both of -which are other ``Person`` objects. - -Set ``related_name`` to designate what the reverse relationship is called. -""" - -from django.db import models - -class Person(models.Model): - full_name = models.CharField(max_length=20) - mother = models.ForeignKey('self', null=True, related_name='mothers_child_set') - father = models.ForeignKey('self', null=True, related_name='fathers_child_set') - - def __unicode__(self): - return self.full_name - -__test__ = {'API_TESTS':""" -# Create two Person objects -- the mom and dad in our family. ->>> dad = Person(full_name='John Smith Senior', mother=None, father=None) ->>> dad.save() ->>> mom = Person(full_name='Jane Smith', mother=None, father=None) ->>> mom.save() - -# Give mom and dad a kid. ->>> kid = Person(full_name='John Smith Junior', mother=mom, father=dad) ->>> kid.save() - ->>> kid.mother - ->>> kid.father - ->>> dad.fathers_child_set.all() -[] ->>> mom.mothers_child_set.all() -[] ->>> kid.mothers_child_set.all() -[] ->>> kid.fathers_child_set.all() -[] -"""} From d83af03c595772f7570d9169f8f80e064b68b8ea Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 12 Oct 2010 01:01:25 +0000 Subject: [PATCH 317/902] [1.2.X] Migrated the mutually_referential doctests. Thanks to George Sakkis for the patch. Backport of r14164 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14170 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../modeltests/mutually_referential/models.py | 21 ++----------------- .../modeltests/mutually_referential/tests.py | 20 ++++++++++++++++++ 2 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 tests/modeltests/mutually_referential/tests.py diff --git a/tests/modeltests/mutually_referential/models.py b/tests/modeltests/mutually_referential/models.py index 2cbaa4b50b9b..db05cbc8a3f0 100644 --- a/tests/modeltests/mutually_referential/models.py +++ b/tests/modeltests/mutually_referential/models.py @@ -8,29 +8,12 @@ class Parent(Model): name = CharField(max_length=100) - + # Use a simple string for forward declarations. bestchild = ForeignKey("Child", null=True, related_name="favoured_by") class Child(Model): name = CharField(max_length=100) - + # You can also explicitally specify the related app. parent = ForeignKey("mutually_referential.Parent") - -__test__ = {'API_TESTS':""" -# Create a Parent ->>> q = Parent(name='Elizabeth') ->>> q.save() - -# Create some children ->>> c = q.child_set.create(name='Charles') ->>> e = q.child_set.create(name='Edward') - -# Set the best child ->>> q.bestchild = c ->>> q.save() - ->>> q.delete() - -"""} \ No newline at end of file diff --git a/tests/modeltests/mutually_referential/tests.py b/tests/modeltests/mutually_referential/tests.py new file mode 100644 index 000000000000..101d67cfa610 --- /dev/null +++ b/tests/modeltests/mutually_referential/tests.py @@ -0,0 +1,20 @@ +from django.test import TestCase +from models import Parent, Child + +class MutuallyReferentialTests(TestCase): + + def test_mutually_referential(self): + # Create a Parent + q = Parent(name='Elizabeth') + q.save() + + # Create some children + c = q.child_set.create(name='Charles') + e = q.child_set.create(name='Edward') + + # Set the best child + # No assertion require here; if basic assignment and + # deletion works, the test passes. + q.bestchild = c + q.save() + q.delete() From 5ecb496d6cc304c8cbe077f04244560d7e345f60 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 12 Oct 2010 01:01:49 +0000 Subject: [PATCH 318/902] [1.2.X] Migrated properties doctests. Thanks to George Sakkis for the patch. Backport of r14165 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14171 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/properties/models.py | 19 ------------------- tests/modeltests/properties/tests.py | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 19 deletions(-) create mode 100644 tests/modeltests/properties/tests.py diff --git a/tests/modeltests/properties/models.py b/tests/modeltests/properties/models.py index 5326e4ec5fce..390efe3a22ea 100644 --- a/tests/modeltests/properties/models.py +++ b/tests/modeltests/properties/models.py @@ -19,22 +19,3 @@ def _set_full_name(self, combined_name): full_name = property(_get_full_name) full_name_2 = property(_get_full_name, _set_full_name) - -__test__ = {'API_TESTS':""" ->>> a = Person(first_name='John', last_name='Lennon') ->>> a.save() ->>> a.full_name -'John Lennon' - -# The "full_name" property hasn't provided a "set" method. ->>> a.full_name = 'Paul McCartney' -Traceback (most recent call last): - ... -AttributeError: can't set attribute - -# But "full_name_2" has, and it can be used to initialise the class. ->>> a2 = Person(full_name_2 = 'Paul McCartney') ->>> a2.save() ->>> a2.first_name -'Paul' -"""} diff --git a/tests/modeltests/properties/tests.py b/tests/modeltests/properties/tests.py new file mode 100644 index 000000000000..e31ac58d4d46 --- /dev/null +++ b/tests/modeltests/properties/tests.py @@ -0,0 +1,20 @@ +from django.test import TestCase +from models import Person + +class PropertyTests(TestCase): + + def setUp(self): + self.a = Person(first_name='John', last_name='Lennon') + self.a.save() + + def test_getter(self): + self.assertEqual(self.a.full_name, 'John Lennon') + + def test_setter(self): + # The "full_name" property hasn't provided a "set" method. + self.assertRaises(AttributeError, setattr, self.a, 'full_name', 'Paul McCartney') + + # But "full_name_2" has, and it can be used to initialise the class. + a2 = Person(full_name_2 = 'Paul McCartney') + a2.save() + self.assertEqual(a2.first_name, 'Paul') From 6b1a45289b9605af219111b4cd0a3cb81e876f27 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 12 Oct 2010 01:02:13 +0000 Subject: [PATCH 319/902] [1.2.X] Migrated many_to_one_null doctests. Thanks to George Sakkis for the patch. Backport of r14166 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14172 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/many_to_one_null/models.py | 105 -------------------- tests/modeltests/many_to_one_null/tests.py | 84 ++++++++++++++++ 2 files changed, 84 insertions(+), 105 deletions(-) create mode 100644 tests/modeltests/many_to_one_null/tests.py diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py index cee0e21a72ee..5f824b4d21b0 100644 --- a/tests/modeltests/many_to_one_null/models.py +++ b/tests/modeltests/many_to_one_null/models.py @@ -22,108 +22,3 @@ class Meta: def __unicode__(self): return self.headline - -__test__ = {'API_TESTS':""" -# Create a Reporter. ->>> r = Reporter(name='John Smith') ->>> r.save() - -# Create an Article. ->>> a = Article(headline="First", reporter=r) ->>> a.save() - ->>> a.reporter.id -1 - ->>> a.reporter - - -# Article objects have access to their related Reporter objects. ->>> r = a.reporter - -# Create an Article via the Reporter object. ->>> a2 = r.article_set.create(headline="Second") ->>> a2 - ->>> a2.reporter.id -1 - -# Reporter objects have access to their related Article objects. ->>> r.article_set.all() -[, ] ->>> r.article_set.filter(headline__startswith='Fir') -[] ->>> r.article_set.count() -2 - -# Create an Article with no Reporter by passing "reporter=None". ->>> a3 = Article(headline="Third", reporter=None) ->>> a3.save() ->>> a3.id -3 ->>> print a3.reporter -None - -# Need to reget a3 to refresh the cache ->>> a3 = Article.objects.get(pk=3) ->>> print a3.reporter.id -Traceback (most recent call last): - ... -AttributeError: 'NoneType' object has no attribute 'id' - -# Accessing an article's 'reporter' attribute returns None -# if the reporter is set to None. ->>> print a3.reporter -None - -# To retrieve the articles with no reporters set, use "reporter__isnull=True". ->>> Article.objects.filter(reporter__isnull=True) -[] - -# We can achieve the same thing by filtering for the case where the reporter is -# None. ->>> Article.objects.filter(reporter=None) -[] - -# Set the reporter for the Third article ->>> r.article_set.add(a3) ->>> r.article_set.all() -[, , ] - -# Remove an article from the set, and check that it was removed. ->>> r.article_set.remove(a3) ->>> r.article_set.all() -[, ] ->>> Article.objects.filter(reporter__isnull=True) -[] - -# Create another article and reporter ->>> r2 = Reporter(name='Paul Jones') ->>> r2.save() ->>> a4 = r2.article_set.create(headline='Fourth') ->>> r2.article_set.all() -[] - -# Try to remove a4 from a set it does not belong to ->>> r.article_set.remove(a4) -Traceback (most recent call last): -... -DoesNotExist: is not related to . - ->>> r2.article_set.all() -[] - -# Use descriptor assignment to allocate ForeignKey. Null is legal, so -# existing members of set that are not in the assignment set are set null ->>> r2.article_set = [a2, a3] ->>> r2.article_set.all() -[, ] - -# Clear the rest of the set ->>> r.article_set.clear() ->>> r.article_set.all() -[] ->>> Article.objects.filter(reporter__isnull=True) -[, ] - -"""} diff --git a/tests/modeltests/many_to_one_null/tests.py b/tests/modeltests/many_to_one_null/tests.py new file mode 100644 index 000000000000..6b7b0a741323 --- /dev/null +++ b/tests/modeltests/many_to_one_null/tests.py @@ -0,0 +1,84 @@ +from django.test import TestCase +from models import Reporter, Article + +class ManyToOneNullTests(TestCase): + + def setUp(self): + # Create a Reporter. + self.r = Reporter(name='John Smith') + self.r.save() + # Create an Article. + self.a = Article(headline="First", reporter=self.r) + self.a.save() + # Create an Article via the Reporter object. + self.a2 = self.r.article_set.create(headline="Second") + # Create an Article with no Reporter by passing "reporter=None". + self.a3 = Article(headline="Third", reporter=None) + self.a3.save() + # Create another article and reporter + self.r2 = Reporter(name='Paul Jones') + self.r2.save() + self.a4 = self.r2.article_set.create(headline='Fourth') + + def test_get_related(self): + self.assertEqual(self.a.reporter.id, self.r.id) + # Article objects have access to their related Reporter objects. + r = self.a.reporter + self.assertEqual(r.id, self.r.id) + + def test_created_via_related_set(self): + self.assertEqual(self.a2.reporter.id, self.r.id) + + def test_related_set(self): + # Reporter objects have access to their related Article objects. + self.assertQuerysetEqual(self.r.article_set.all(), + ['', '']) + self.assertQuerysetEqual(self.r.article_set.filter(headline__startswith='Fir'), + ['']) + self.assertEqual(self.r.article_set.count(), 2) + + def test_created_without_related(self): + self.assertEqual(self.a3.reporter, None) + # Need to reget a3 to refresh the cache + a3 = Article.objects.get(pk=3) + self.assertRaises(AttributeError, getattr, a3.reporter, 'id') + # Accessing an article's 'reporter' attribute returns None + # if the reporter is set to None. + self.assertEqual(a3.reporter, None) + # To retrieve the articles with no reporters set, use "reporter__isnull=True". + self.assertQuerysetEqual(Article.objects.filter(reporter__isnull=True), + ['']) + # We can achieve the same thing by filtering for the case where the + # reporter is None. + self.assertQuerysetEqual(Article.objects.filter(reporter=None), + ['']) + # Set the reporter for the Third article + self.assertQuerysetEqual(self.r.article_set.all(), + ['', '']) + self.r.article_set.add(a3) + self.assertQuerysetEqual(self.r.article_set.all(), + ['', '', '']) + # Remove an article from the set, and check that it was removed. + self.r.article_set.remove(a3) + self.assertQuerysetEqual(self.r.article_set.all(), + ['', '']) + self.assertQuerysetEqual(Article.objects.filter(reporter__isnull=True), + ['']) + + def test_remove_from_wrong_set(self): + self.assertQuerysetEqual(self.r2.article_set.all(), ['']) + # Try to remove a4 from a set it does not belong to + self.assertRaises(Reporter.DoesNotExist, self.r.article_set.remove, self.a4) + self.assertQuerysetEqual(self.r2.article_set.all(), ['']) + + def test_assign_clear_related_set(self): + # Use descriptor assignment to allocate ForeignKey. Null is legal, so + # existing members of set that are not in the assignment set are set null + self.r2.article_set = [self.a2, self.a3] + self.assertQuerysetEqual(self.r2.article_set.all(), + ['', '']) + # Clear the rest of the set + self.r.article_set.clear() + self.assertQuerysetEqual(self.r.article_set.all(), []) + self.assertQuerysetEqual(Article.objects.filter(reporter__isnull=True), + ['', '']) From 6f7f1f651b45b3cac20ccfa17acb0ec71ef6a671 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 12 Oct 2010 01:02:37 +0000 Subject: [PATCH 320/902] [1.2.X] Migrated one_to_one doctests. Thanks to George Sakkis for the patch. (We have always been at war with doctests) Backport of r14167 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14173 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/one_to_one/models.py | 147 -------------------------- tests/modeltests/one_to_one/tests.py | 119 +++++++++++++++++++++ 2 files changed, 119 insertions(+), 147 deletions(-) create mode 100644 tests/modeltests/one_to_one/tests.py diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py index e25e33bcc0a5..f2637350cadd 100644 --- a/tests/modeltests/one_to_one/models.py +++ b/tests/modeltests/one_to_one/models.py @@ -45,150 +45,3 @@ class MultiModel(models.Model): def __unicode__(self): return u"Multimodel %s" % self.name - -__test__ = {'API_TESTS':""" -# Create a couple of Places. ->>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton') ->>> p1.save() ->>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland') ->>> p2.save() - -# Create a Restaurant. Pass the ID of the "parent" object as this object's ID. ->>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False) ->>> r.save() - -# A Restaurant can access its place. ->>> r.place - - -# A Place can access its restaurant, if available. ->>> p1.restaurant - - -# p2 doesn't have an associated restaurant. ->>> p2.restaurant -Traceback (most recent call last): - ... -DoesNotExist: Restaurant matching query does not exist. - -# Set the place using assignment notation. Because place is the primary key on -# Restaurant, the save will create a new restaurant ->>> r.place = p2 ->>> r.save() ->>> p2.restaurant - ->>> r.place - ->>> p2.id -2 - -# Set the place back again, using assignment in the reverse direction. ->>> p1.restaurant = r ->>> p1.restaurant - - ->>> r = Restaurant.objects.get(pk=1) ->>> r.place - - -# Restaurant.objects.all() just returns the Restaurants, not the Places. -# Note that there are two restaurants - Ace Hardware the Restaurant was created -# in the call to r.place = p2. ->>> Restaurant.objects.all() -[, ] - -# Place.objects.all() returns all Places, regardless of whether they have -# Restaurants. ->>> Place.objects.order_by('name') -[, ] - ->>> Restaurant.objects.get(place__id__exact=1) - ->>> Restaurant.objects.get(pk=1) - ->>> Restaurant.objects.get(place__exact=1) - ->>> Restaurant.objects.get(place__exact=p1) - ->>> Restaurant.objects.get(place=1) - ->>> Restaurant.objects.get(place=p1) - ->>> Restaurant.objects.get(place__pk=1) - ->>> Restaurant.objects.get(place__name__startswith="Demon") - - ->>> Place.objects.get(id__exact=1) - ->>> Place.objects.get(pk=1) - ->>> Place.objects.get(restaurant__place__exact=1) - ->>> Place.objects.get(restaurant__place__exact=p1) - ->>> Place.objects.get(restaurant__pk=1) - ->>> Place.objects.get(restaurant=1) - ->>> Place.objects.get(restaurant=r) - ->>> Place.objects.get(restaurant__exact=1) - ->>> Place.objects.get(restaurant__exact=r) - - -# Add a Waiter to the Restaurant. ->>> w = r.waiter_set.create(name='Joe') ->>> w.save() ->>> w - - -# Query the waiters ->>> Waiter.objects.filter(restaurant__place__pk=1) -[] ->>> Waiter.objects.filter(restaurant__place__exact=1) -[] ->>> Waiter.objects.filter(restaurant__place__exact=p1) -[] ->>> Waiter.objects.filter(restaurant__pk=1) -[] ->>> Waiter.objects.filter(id__exact=1) -[] ->>> Waiter.objects.filter(pk=1) -[] ->>> Waiter.objects.filter(restaurant=1) -[] ->>> Waiter.objects.filter(restaurant=r) -[] - -# Delete the restaurant; the waiter should also be removed ->>> r = Restaurant.objects.get(pk=1) ->>> r.delete() - -# One-to-one fields still work if you create your own primary key ->>> o1 = ManualPrimaryKey(primary_key="abc123", name="primary") ->>> o1.save() ->>> o2 = RelatedModel(link=o1, name="secondary") ->>> o2.save() - -# You can have multiple one-to-one fields on a model, too. ->>> x1 = MultiModel(link1=p1, link2=o1, name="x1") ->>> x1.save() ->>> o1.multimodel - - -# This will fail because each one-to-one field must be unique (and link2=o1 was -# used for x1, above). ->>> sid = transaction.savepoint() ->>> try: -... MultiModel(link1=p2, link2=o1, name="x1").save() -... except Exception, e: -... if isinstance(e, IntegrityError): -... print "Pass" -... else: -... print "Fail with %s" % type(e) -Pass ->>> transaction.savepoint_rollback(sid) - -"""} diff --git a/tests/modeltests/one_to_one/tests.py b/tests/modeltests/one_to_one/tests.py new file mode 100644 index 000000000000..c3e170445217 --- /dev/null +++ b/tests/modeltests/one_to_one/tests.py @@ -0,0 +1,119 @@ +from django.test import TestCase +from django.db import transaction, IntegrityError +from models import Place, Restaurant, Waiter, ManualPrimaryKey, RelatedModel, MultiModel + +class OneToOneTests(TestCase): + + def setUp(self): + self.p1 = Place(name='Demon Dogs', address='944 W. Fullerton') + self.p1.save() + self.p2 = Place(name='Ace Hardware', address='1013 N. Ashland') + self.p2.save() + self.r = Restaurant(place=self.p1, serves_hot_dogs=True, serves_pizza=False) + self.r.save() + + def test_getter(self): + # A Restaurant can access its place. + self.assertEqual(repr(self.r.place), '') + # A Place can access its restaurant, if available. + self.assertEqual(repr(self.p1.restaurant), '') + # p2 doesn't have an associated restaurant. + self.assertRaises(Restaurant.DoesNotExist, getattr, self.p2, 'restaurant') + + def test_setter(self): + # Set the place using assignment notation. Because place is the primary + # key on Restaurant, the save will create a new restaurant + self.r.place = self.p2 + self.r.save() + self.assertEqual(repr(self.p2.restaurant), '') + self.assertEqual(repr(self.r.place), '') + self.assertEqual(self.p2.pk, self.r.pk) + # Set the place back again, using assignment in the reverse direction. + self.p1.restaurant = self.r + self.assertEqual(repr(self.p1.restaurant), '') + r = Restaurant.objects.get(pk=self.p1.id) + self.assertEqual(repr(r.place), '') + + def test_manager_all(self): + # Restaurant.objects.all() just returns the Restaurants, not the Places. + self.assertQuerysetEqual(Restaurant.objects.all(), [ + '', + ]) + # Place.objects.all() returns all Places, regardless of whether they + # have Restaurants. + self.assertQuerysetEqual(Place.objects.order_by('name'), [ + '', + '', + ]) + + def test_manager_get(self): + def assert_get_restaurant(**params): + self.assertEqual(repr(Restaurant.objects.get(**params)), + '') + assert_get_restaurant(place__id__exact=self.p1.pk) + assert_get_restaurant(place__id=self.p1.pk) + assert_get_restaurant(place__exact=self.p1.pk) + assert_get_restaurant(place__exact=self.p1) + assert_get_restaurant(place=self.p1.pk) + assert_get_restaurant(place=self.p1) + assert_get_restaurant(pk=self.p1.pk) + assert_get_restaurant(place__pk__exact=self.p1.pk) + assert_get_restaurant(place__pk=self.p1.pk) + assert_get_restaurant(place__name__startswith="Demon") + + def assert_get_place(**params): + self.assertEqual(repr(Place.objects.get(**params)), + '') + assert_get_place(restaurant__place__exact=self.p1.pk) + assert_get_place(restaurant__place__exact=self.p1) + assert_get_place(restaurant__place__pk=self.p1.pk) + assert_get_place(restaurant__exact=self.p1.pk) + assert_get_place(restaurant__exact=self.r) + assert_get_place(restaurant__pk=self.p1.pk) + assert_get_place(restaurant=self.p1.pk) + assert_get_place(restaurant=self.r) + assert_get_place(id__exact=self.p1.pk) + assert_get_place(pk=self.p1.pk) + + def test_foreign_key(self): + # Add a Waiter to the Restaurant. + w = self.r.waiter_set.create(name='Joe') + w.save() + self.assertEqual(repr(w), '') + # Query the waiters + def assert_filter_waiters(**params): + self.assertQuerysetEqual(Waiter.objects.filter(**params), [ + '' + ]) + assert_filter_waiters(restaurant__place__exact=self.p1.pk) + assert_filter_waiters(restaurant__place__exact=self.p1) + assert_filter_waiters(restaurant__place__pk=self.p1.pk) + assert_filter_waiters(restaurant__exact=self.p1.pk) + assert_filter_waiters(restaurant__exact=self.p1) + assert_filter_waiters(restaurant__pk=self.p1.pk) + assert_filter_waiters(restaurant=self.p1.pk) + assert_filter_waiters(restaurant=self.r) + assert_filter_waiters(id__exact=self.p1.pk) + assert_filter_waiters(pk=self.p1.pk) + # Delete the restaurant; the waiter should also be removed + r = Restaurant.objects.get(pk=self.p1.pk) + r.delete() + self.assertEqual(Waiter.objects.count(), 0) + + def test_multiple_o2o(self): + # One-to-one fields still work if you create your own primary key + o1 = ManualPrimaryKey(primary_key="abc123", name="primary") + o1.save() + o2 = RelatedModel(link=o1, name="secondary") + o2.save() + + # You can have multiple one-to-one fields on a model, too. + x1 = MultiModel(link1=self.p1, link2=o1, name="x1") + x1.save() + self.assertEqual(repr(o1.multimodel), '') + # This will fail because each one-to-one field must be unique (and + # link2=o1 was used for x1, above). + sid = transaction.savepoint() + mm = MultiModel(link1=self.p2, link2=o1, name="x1") + self.assertRaises(IntegrityError, mm.save) + transaction.savepoint_rollback(sid) From 5f8332af049efc878a4c906e75e90708e90830b3 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 12 Oct 2010 01:02:57 +0000 Subject: [PATCH 321/902] [1.2.X] Tweak to many_to_one_null doctest to avoid primary key assumptions (causing breakage on PostgreSQL). Backport of r14168 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14174 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/many_to_one_null/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modeltests/many_to_one_null/tests.py b/tests/modeltests/many_to_one_null/tests.py index 6b7b0a741323..c78f980871ab 100644 --- a/tests/modeltests/many_to_one_null/tests.py +++ b/tests/modeltests/many_to_one_null/tests.py @@ -40,7 +40,7 @@ def test_related_set(self): def test_created_without_related(self): self.assertEqual(self.a3.reporter, None) # Need to reget a3 to refresh the cache - a3 = Article.objects.get(pk=3) + a3 = Article.objects.get(pk=self.a3.pk) self.assertRaises(AttributeError, getattr, a3.reporter, 'id') # Accessing an article's 'reporter' attribute returns None # if the reporter is set to None. From de8ff6b672fcf0010a4584d98cc845eb2ab8fc6c Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 12 Oct 2010 01:54:48 +0000 Subject: [PATCH 322/902] [1.2.X] Converted get_or_create_regress tests from doctests to unittests. We have always been at war with doctests. Backport of [14177]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14178 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../get_or_create_regress/models.py | 80 +------------------ .../get_or_create_regress/tests.py | 53 ++++++++++++ 2 files changed, 54 insertions(+), 79 deletions(-) create mode 100644 tests/regressiontests/get_or_create_regress/tests.py diff --git a/tests/regressiontests/get_or_create_regress/models.py b/tests/regressiontests/get_or_create_regress/models.py index 1b7b00a69a0d..292d96c98096 100644 --- a/tests/regressiontests/get_or_create_regress/models.py +++ b/tests/regressiontests/get_or_create_regress/models.py @@ -1,5 +1,6 @@ from django.db import models + class Publisher(models.Model): name = models.CharField(max_length=100) @@ -10,82 +11,3 @@ class Book(models.Model): name = models.CharField(max_length=100) authors = models.ManyToManyField(Author, related_name='books') publisher = models.ForeignKey(Publisher, related_name='books') - - -__test__ = {'one':""" -# -# RelatedManager -# - -# First create a Publisher. ->>> p = Publisher.objects.create(name='Acme Publishing') - -# Create a book through the publisher. ->>> book, created = p.books.get_or_create(name='The Book of Ed & Fred') ->>> created -True - -# The publisher should have one book. ->>> p.books.count() -1 - -# Try get_or_create again, this time nothing should be created. ->>> book, created = p.books.get_or_create(name='The Book of Ed & Fred') ->>> created -False - -# And the publisher should still have one book. ->>> p.books.count() -1 - -# -# ManyRelatedManager -# - -# Add an author to the book. ->>> ed, created = book.authors.get_or_create(name='Ed') ->>> created -True - -# Book should have one author. ->>> book.authors.count() -1 - -# Try get_or_create again, this time nothing should be created. ->>> ed, created = book.authors.get_or_create(name='Ed') ->>> created -False - -# And the book should still have one author. ->>> book.authors.count() -1 - -# Add a second author to the book. ->>> fred, created = book.authors.get_or_create(name='Fred') ->>> created -True - -# The book should have two authors now. ->>> book.authors.count() -2 - -# Create an Author not tied to any books. ->>> Author.objects.create(name='Ted') - - -# There should be three Authors in total. The book object should have two. ->>> Author.objects.count() -3 ->>> book.authors.count() -2 - -# Try creating a book through an author. ->>> ed.books.get_or_create(name="Ed's Recipies", publisher=p) -(, True) - -# Now Ed has two Books, Fred just one. ->>> ed.books.count() -2 ->>> fred.books.count() -1 -"""} diff --git a/tests/regressiontests/get_or_create_regress/tests.py b/tests/regressiontests/get_or_create_regress/tests.py new file mode 100644 index 000000000000..87057dab3fef --- /dev/null +++ b/tests/regressiontests/get_or_create_regress/tests.py @@ -0,0 +1,53 @@ +from django.test import TestCase + +from models import Author, Publisher + + +class GetOrCreateTests(TestCase): + def test_related(self): + p = Publisher.objects.create(name="Acme Publishing") + # Create a book through the publisher. + book, created = p.books.get_or_create(name="The Book of Ed & Fred") + self.assertTrue(created) + # The publisher should have one book. + self.assertEqual(p.books.count(), 1) + + # Try get_or_create again, this time nothing should be created. + book, created = p.books.get_or_create(name="The Book of Ed & Fred") + self.assertFalse(created) + # And the publisher should still have one book. + self.assertEqual(p.books.count(), 1) + + # Add an author to the book. + ed, created = book.authors.get_or_create(name="Ed") + self.assertTrue(created) + # Book should have one author. + self.assertEqual(book.authors.count(), 1) + + # Try get_or_create again, this time nothing should be created. + ed, created = book.authors.get_or_create(name="Ed") + self.assertFalse(created) + # And the book should still have one author. + self.assertEqual(book.authors.count(), 1) + + # Add a second author to the book. + fred, created = book.authors.get_or_create(name="Fred") + self.assertTrue(created) + + # The book should have two authors now. + self.assertEqual(book.authors.count(), 2) + + # Create an Author not tied to any books. + Author.objects.create(name="Ted") + + # There should be three Authors in total. The book object should have two. + self.assertEqual(Author.objects.count(), 3) + self.assertEqual(book.authors.count(), 2) + + # Try creating a book through an author. + _, created = ed.books.get_or_create(name="Ed's Recipes", publisher=p) + self.assertTrue(created) + + # Now Ed has two Books, Fred just one. + self.assertEqual(ed.books.count(), 2) + self.assertEqual(fred.books.count(), 1) From 9ab383b394c70ebb550536f3b7ba91a5cfc3b912 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 12 Oct 2010 01:59:46 +0000 Subject: [PATCH 323/902] [1.2.X] Converted initial_sql_regress tests from doctests (sort of...) to unittests. We have always been at war with doctests. Backport of [14179]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14180 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/initial_sql_regress/models.py | 2 -- tests/regressiontests/initial_sql_regress/tests.py | 8 ++++++++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 tests/regressiontests/initial_sql_regress/tests.py diff --git a/tests/regressiontests/initial_sql_regress/models.py b/tests/regressiontests/initial_sql_regress/models.py index 7cf725991ad6..9f91802ec7ec 100644 --- a/tests/regressiontests/initial_sql_regress/models.py +++ b/tests/regressiontests/initial_sql_regress/models.py @@ -7,7 +7,5 @@ class Simple(models.Model): name = models.CharField(max_length = 50) -__test__ = {'API_TESTS':""} - # NOTE: The format of the included SQL file for this test suite is important. # It must end with a trailing newline in order to test the fix for #2161. diff --git a/tests/regressiontests/initial_sql_regress/tests.py b/tests/regressiontests/initial_sql_regress/tests.py new file mode 100644 index 000000000000..2b3ca91f91a1 --- /dev/null +++ b/tests/regressiontests/initial_sql_regress/tests.py @@ -0,0 +1,8 @@ +from django.test import TestCase + +from models import Simple + + +class InitialSQLTests(TestCase): + def test_initial_sql(self): + self.assertEqual(Simple.objects.count(), 7) From 9b077ee4b651b0b2023ac673b0348e5a612335de Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 12 Oct 2010 02:09:35 +0000 Subject: [PATCH 324/902] [1.2.X] Converted model_inheritance_select_related tests from doctests to unittests. We have always been at war with doctests. Backport of [14181]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14182 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../models.py | 18 ------------ .../model_inheritance_select_related/tests.py | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 tests/regressiontests/model_inheritance_select_related/tests.py diff --git a/tests/regressiontests/model_inheritance_select_related/models.py b/tests/regressiontests/model_inheritance_select_related/models.py index a97ecaadeab0..5851e467ce6f 100644 --- a/tests/regressiontests/model_inheritance_select_related/models.py +++ b/tests/regressiontests/model_inheritance_select_related/models.py @@ -27,21 +27,3 @@ class Person(models.Model): def __unicode__(self): return self.name - -__test__ = {'API_TESTS':""" -Regression test for #7246 - ->>> r1 = Restaurant.objects.create(name="Nobu", serves_sushi=True, serves_steak=False) ->>> r2 = Restaurant.objects.create(name="Craft", serves_sushi=False, serves_steak=True) ->>> p1 = Person.objects.create(name="John", favorite_restaurant=r1) ->>> p2 = Person.objects.create(name="Jane", favorite_restaurant=r2) - ->>> Person.objects.order_by('name').select_related() -[, ] - ->>> jane = Person.objects.order_by('name').select_related('favorite_restaurant')[0] ->>> jane.favorite_restaurant.name -u'Craft' - -"""} - diff --git a/tests/regressiontests/model_inheritance_select_related/tests.py b/tests/regressiontests/model_inheritance_select_related/tests.py new file mode 100644 index 000000000000..e1ed609396df --- /dev/null +++ b/tests/regressiontests/model_inheritance_select_related/tests.py @@ -0,0 +1,29 @@ +from operator import attrgetter + +from django.test import TestCase + +from models import Restaurant, Person + + +class ModelInheritanceSelectRelatedTests(TestCase): + def test_inherited_select_related(self): + # Regression test for #7246 + r1 = Restaurant.objects.create( + name="Nobu", serves_sushi=True, serves_steak=False + ) + r2 = Restaurant.objects.create( + name="Craft", serves_sushi=False, serves_steak=True + ) + p1 = Person.objects.create(name="John", favorite_restaurant=r1) + p2 = Person.objects.create(name="Jane", favorite_restaurant=r2) + + self.assertQuerysetEqual( + Person.objects.order_by("name").select_related(), [ + "Jane", + "John", + ], + attrgetter("name") + ) + + jane = Person.objects.order_by("name").select_related("favorite_restaurant")[0] + self.assertEqual(jane.favorite_restaurant.name, "Craft") From 23ecf3cf6c6c1b426a5a192cee8f14ed8a7860c0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 12 Oct 2010 07:27:49 +0000 Subject: [PATCH 325/902] [1.2.X] Modified the sitemaps tests to remove an assumption about the environment in which the tests are run. Thanks to Gabriel Hurley for the report and patch. Backport of r14184 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14185 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sitemaps/tests/basic.py | 60 +++++++++++++++----------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/django/contrib/sitemaps/tests/basic.py b/django/contrib/sitemaps/tests/basic.py index b2de75f09833..70e28c2f74b0 100644 --- a/django/contrib/sitemaps/tests/basic.py +++ b/django/contrib/sitemaps/tests/basic.py @@ -1,7 +1,6 @@ from datetime import date from django.conf import settings from django.contrib.auth.models import User -from django.contrib.flatpages.models import FlatPage from django.contrib.sitemaps import Sitemap from django.contrib.sites.models import Site from django.core.exceptions import ImproperlyConfigured @@ -52,40 +51,51 @@ def test_generic_sitemap(self): "A minimal generic sitemap can be rendered" # Retrieve the sitemap. response = self.client.get('/generic/sitemap.xml') + + expected = '' + for username in User.objects.values_list("username", flat=True): + expected += "http://example.com/users/%s/" %username # Check for all the important bits: self.assertEquals(response.content, """ -http://example.com/users/testuser/ +%s -""") +""" %expected) + + if "django.contrib.flatpages" in settings.INSTALLED_APPS: + def test_flatpage_sitemap(self): + "Basic FlatPage sitemap test" + + # Import FlatPage inside the test so that when django.contrib.flatpages + # is not installed we don't get problems trying to delete Site + # objects (FlatPage has an M2M to Site, Site.delete() tries to + # delete related objects, but the M2M table doesn't exist. + from django.contrib.flatpages.models import FlatPage - def test_flatpage_sitemap(self): - "Basic FlatPage sitemap test" - public = FlatPage.objects.create( - url=u'/public/', - title=u'Public Page', - enable_comments=True, - registration_required=False, - ) - public.sites.add(settings.SITE_ID) - private = FlatPage.objects.create( - url=u'/private/', - title=u'Private Page', - enable_comments=True, - registration_required=True - ) - private.sites.add(settings.SITE_ID) - response = self.client.get('/flatpages/sitemap.xml') - # Public flatpage should be in the sitemap - self.assertContains(response, 'http://example.com%s' % public.url) - # Private flatpage should not be in the sitemap - self.assertNotContains(response, 'http://example.com%s' % private.url) + public = FlatPage.objects.create( + url=u'/public/', + title=u'Public Page', + enable_comments=True, + registration_required=False, + ) + public.sites.add(settings.SITE_ID) + private = FlatPage.objects.create( + url=u'/private/', + title=u'Private Page', + enable_comments=True, + registration_required=True + ) + private.sites.add(settings.SITE_ID) + response = self.client.get('/flatpages/sitemap.xml') + # Public flatpage should be in the sitemap + self.assertContains(response, 'http://example.com%s' % public.url) + # Private flatpage should not be in the sitemap + self.assertNotContains(response, 'http://example.com%s' % private.url) def test_requestsite_sitemap(self): # Make sure hitting the flatpages sitemap without the sites framework # installed doesn't raise an exception Site._meta.installed = False - response = self.client.get('/flatpages/sitemap.xml') # Retrieve the sitemap. response = self.client.get('/simple/sitemap.xml') # Check for all the important bits: From 3c8bc8c64b2620e44275ff703c47b77cc77a0c59 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 12 Oct 2010 14:09:02 +0000 Subject: [PATCH 326/902] [1.2.X] Modified the test_client tests to use the non-deprecated mail API. Backport of r14187 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14188 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/test_client/views.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index c42a7b7e679e..baa9525b3732 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -1,6 +1,6 @@ from xml.dom.minidom import parseString -from django.core.mail import EmailMessage, SMTPConnection +from django.core import mail from django.template import Context, Template from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound from django.contrib.auth.decorators import login_required, permission_required @@ -38,7 +38,7 @@ def view_with_header(request): response = HttpResponse() response['X-DJANGO-TEST'] = 'Slartibartfast' return response - + def raw_post_view(request): """A view which expects raw XML to be posted and returns content extracted from the XML""" @@ -139,7 +139,7 @@ def login_protected_view_changed_redirect(request): "A simple view that is login protected with a custom redirect field set" t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template') c = Context({'user': request.user}) - + return HttpResponse(t.render(c)) login_protected_view_changed_redirect = login_required(redirect_field_name="redirect_to")(login_protected_view_changed_redirect) @@ -189,7 +189,7 @@ def broken_view(request): raise KeyError("Oops! Looks like you wrote some bad code.") def mail_sending_view(request): - EmailMessage( + mail.EmailMessage( "Test message", "This is a test email", "from@example.com", @@ -197,18 +197,18 @@ def mail_sending_view(request): return HttpResponse("Mail sent") def mass_mail_sending_view(request): - m1 = EmailMessage( + m1 = mail.EmailMessage( 'First Test message', 'This is the first test email', 'from@example.com', ['first@example.com', 'second@example.com']) - m2 = EmailMessage( + m2 = mail.EmailMessage( 'Second Test message', 'This is the second test email', 'from@example.com', ['second@example.com', 'third@example.com']) - c = SMTPConnection() + c = mail.get_connection() c.send_messages([m1,m2]) return HttpResponse("Mail sent") From 4ed10675f953e4349ecd3ad386ef5c8485d0220f Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Tue, 12 Oct 2010 17:27:07 +0000 Subject: [PATCH 327/902] [1.2.X] Enabled area calculations for geography columns. Backport of r14189 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14190 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/db/models/query.py | 5 ++++- django/contrib/gis/tests/geogapp/models.py | 2 +- django/contrib/gis/tests/geogapp/tests.py | 9 +++++++++ docs/ref/contrib/gis/model-api.txt | 1 - 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index 79eed853f9d1..4df1a3ab7852 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -48,7 +48,10 @@ def area(self, tolerance=0.05, **kwargs): s['procedure_args']['tolerance'] = tolerance s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters. elif backend.postgis or backend.spatialite: - if not geo_field.geodetic(connection): + if backend.geography: + # Geography fields support area calculation, returns square meters. + s['select_field'] = AreaField('sq_m') + elif not geo_field.geodetic(connection): # Getting the area units of the geographic field. s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name(connection))) else: diff --git a/django/contrib/gis/tests/geogapp/models.py b/django/contrib/gis/tests/geogapp/models.py index 3198fbd36d71..3696ba2ff421 100644 --- a/django/contrib/gis/tests/geogapp/models.py +++ b/django/contrib/gis/tests/geogapp/models.py @@ -10,7 +10,7 @@ class Zipcode(models.Model): code = models.CharField(max_length=10) poly = models.PolygonField(geography=True) objects = models.GeoManager() - def __unicode__(self): return self.name + def __unicode__(self): return self.code class County(models.Model): name = models.CharField(max_length=25) diff --git a/django/contrib/gis/tests/geogapp/tests.py b/django/contrib/gis/tests/geogapp/tests.py index 3dea930afd01..cb69ce94e1f2 100644 --- a/django/contrib/gis/tests/geogapp/tests.py +++ b/django/contrib/gis/tests/geogapp/tests.py @@ -76,3 +76,12 @@ def test05_geography_layermapping(self): self.assertEqual(num_poly, len(c.mpoly)) self.assertEqual(name, c.name) self.assertEqual(state, c.state) + + def test06_geography_area(self): + "Testing that Area calculations work on geography columns." + from django.contrib.gis.measure import A + # SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002'; + ref_area = 5439084.70637573 + tol = 5 + z = Zipcode.objects.area().get(code='77002') + self.assertAlmostEqual(z.area.sq_m, ref_area, tol) diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index b6d92dd24ce0..6b50cf363e91 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -216,7 +216,6 @@ only the following additional :ref:`spatial lookups ` are available for geography columns: * :lookup:`bboverlaps` -* :lookup:`exact`, and :lookup:`same_as` * :lookup:`coveredby` * :lookup:`covers` * :lookup:`intersects` From 18a605d6ad1041d413f59876bef62b6f9c275ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Kr=C3=A1l?= Date: Wed, 13 Oct 2010 00:33:10 +0000 Subject: [PATCH 328/902] [1.2.X] Changed unique validation in model formsets to ignore None values, not just omit them. Thanks claudep! Backport of r14193 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14194 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/forms/models.py | 7 ++--- tests/modeltests/model_formsets/models.py | 37 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/django/forms/models.py b/django/forms/models.py index 16a4f206b875..d4046a7f7043 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -516,10 +516,9 @@ def validate_unique(self): # it's already invalid if not hasattr(form, "cleaned_data"): continue - # get each of the fields for which we have data on this form - if [f for f in unique_check if f in form.cleaned_data and form.cleaned_data[f] is not None]: - # get the data itself - row_data = tuple([form.cleaned_data[field] for field in unique_check]) + # get data for each field of each of unique_check + row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data]) + if row_data and not None in row_data: # if we've aready seen it then we have a uniqueness failure if row_data in seen_data: # poke error messages into the right places and mark diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index d5825313ec12..1665f62416b0 100644 --- a/tests/modeltests/model_formsets/models.py +++ b/tests/modeltests/model_formsets/models.py @@ -35,6 +35,23 @@ class BookWithCustomPK(models.Model): def __unicode__(self): return u'%s: %s' % (self.my_pk, self.title) +class Editor(models.Model): + name = models.CharField(max_length=100) + +class BookWithOptionalAltEditor(models.Model): + author = models.ForeignKey(Author) + # Optional secondary author + alt_editor = models.ForeignKey(Editor, blank=True, null=True) + title = models.CharField(max_length=100) + + class Meta: + unique_together = ( + ('author', 'title', 'alt_editor'), + ) + + def __unicode__(self): + return self.title + class AlternateBook(Book): notes = models.CharField(max_length=100) @@ -648,6 +665,26 @@ def __unicode__(self): >>> formset.save() [] +Test inline formsets where the inline-edited object has a unique_together constraint with a nullable member + +>>> AuthorBooksFormSet4 = inlineformset_factory(Author, BookWithOptionalAltEditor, can_delete=False, extra=2) + +>>> data = { +... 'bookwithoptionalalteditor_set-TOTAL_FORMS': '2', # the number of forms rendered +... 'bookwithoptionalalteditor_set-INITIAL_FORMS': '0', # the number of forms with initial data +... 'bookwithoptionalalteditor_set-MAX_NUM_FORMS': '', # the max number of forms +... 'bookwithoptionalalteditor_set-0-author': '1', +... 'bookwithoptionalalteditor_set-0-title': 'Les Fleurs du Mal', +... 'bookwithoptionalalteditor_set-1-author': '1', +... 'bookwithoptionalalteditor_set-1-title': 'Les Fleurs du Mal', +... } +>>> formset = AuthorBooksFormSet4(data, instance=author) +>>> formset.is_valid() +True + +>>> formset.save() +[, ] + # ModelForm with a custom save method in an inline formset ################### From a6d7836fd1e5f4e4935b45dcd419dae9aaae51b0 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 13 Oct 2010 01:25:04 +0000 Subject: [PATCH 329/902] [1.2.X] Fixed #13830 -- Updated province name in Indonesian localflavor. Thanks, rodin. Backport of r14195 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14196 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/localflavor/id/id_choices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/localflavor/id/id_choices.py b/django/contrib/localflavor/id/id_choices.py index ed1ea017b90e..5b2a0d7d2668 100644 --- a/django/contrib/localflavor/id/id_choices.py +++ b/django/contrib/localflavor/id/id_choices.py @@ -6,6 +6,7 @@ # I decided to use unambiguous and consistent (some are common) 3-letter codes. PROVINCE_CHOICES = ( + ('ACE', _('Aceh')), ('BLI', _('Bali')), ('BTN', _('Banten')), ('BKL', _('Bengkulu')), @@ -25,7 +26,6 @@ ('LPG', _('Lampung')), ('MLK', _('Maluku')), ('MUT', _('Maluku Utara')), - ('NAD', _('Nanggroe Aceh Darussalam')), ('NTB', _('Nusa Tenggara Barat')), ('NTT', _('Nusa Tenggara Timur')), ('PPA', _('Papua')), From 55b68363f21746e7794412d1a6a4d86e7dce97fe Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Wed, 13 Oct 2010 07:02:43 +0000 Subject: [PATCH 330/902] [1.2.X] Fix a typo in my bio. Backport of [14200] git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14201 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/committers.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index b94f31316720..bc31009afc7e 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -258,7 +258,7 @@ Ramiro Morales `Chris Beaven`_ Chris has been submitting patches and suggesting crazy ideas for Django - since early 2006. An advocate for community involement and a long-term + since early 2006. An advocate for community involvement and a long-term triager, he is still often found answering questions in the #django IRC channel. From 1df25941b388d88e4360d2ebe478b3cd41e3eb28 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 14 Oct 2010 01:11:22 +0000 Subject: [PATCH 331/902] [1.2.X] Fixed #14458 -- converted m2m_regress tests from doctests to unittests. We have always been at war with doctests. Thanks to Gabriel Hurley for the patch. Backport of [14205]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14206 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/m2m_regress/models.py | 67 ------------------ tests/regressiontests/m2m_regress/tests.py | 75 +++++++++++++++++++++ 2 files changed, 75 insertions(+), 67 deletions(-) create mode 100644 tests/regressiontests/m2m_regress/tests.py diff --git a/tests/regressiontests/m2m_regress/models.py b/tests/regressiontests/m2m_regress/models.py index 5453d396ae29..1c2126d76a7c 100644 --- a/tests/regressiontests/m2m_regress/models.py +++ b/tests/regressiontests/m2m_regress/models.py @@ -56,70 +56,3 @@ class Worksheet(models.Model): class User(models.Model): name = models.CharField(max_length=30) friends = models.ManyToManyField(auth.User) - -__test__ = {"regressions": """ -# Multiple m2m references to the same model or a different model must be -# distinguished when accessing the relations through an instance attribute. - ->>> s1 = SelfRefer.objects.create(name='s1') ->>> s2 = SelfRefer.objects.create(name='s2') ->>> s3 = SelfRefer.objects.create(name='s3') ->>> s1.references.add(s2) ->>> s1.related.add(s3) - ->>> e1 = Entry.objects.create(name='e1') ->>> t1 = Tag.objects.create(name='t1') ->>> t2 = Tag.objects.create(name='t2') ->>> e1.topics.add(t1) ->>> e1.related.add(t2) - ->>> s1.references.all() -[] ->>> s1.related.all() -[] - ->>> e1.topics.all() -[] ->>> e1.related.all() -[] - -# The secret internal related names for self-referential many-to-many fields -# shouldn't appear in the list when an error is made. ->>> SelfRefer.objects.filter(porcupine='fred') -Traceback (most recent call last): -... -FieldError: Cannot resolve keyword 'porcupine' into field. Choices are: id, name, references, related, selfreferchild, selfreferchildsibling - -# Test to ensure that the relationship between two inherited models -# with a self-referential m2m field maintains symmetry ->>> sr_child = SelfReferChild(name="Hanna") ->>> sr_child.save() - ->>> sr_sibling = SelfReferChildSibling(name="Beth") ->>> sr_sibling.save() ->>> sr_child.related.add(sr_sibling) ->>> sr_child.related.all() -[] ->>> sr_sibling.related.all() -[] - -# Regression for #11311 - The primary key for models in a m2m relation -# doesn't have to be an AutoField ->>> w = Worksheet(id='abc') ->>> w.save() ->>> w.delete() - -# Regression for #11956 -- You can add an object to a m2m with the -# base class without causing integrity errors ->>> c1 = TagCollection.objects.create(name='c1') ->>> c1.tags = [t1,t2] - ->>> c1 = TagCollection.objects.get(name='c1') ->>> c1.tags.all() -[, ] - ->>> t1.tag_collections.all() -[] - -""" -} diff --git a/tests/regressiontests/m2m_regress/tests.py b/tests/regressiontests/m2m_regress/tests.py new file mode 100644 index 000000000000..7bf2381a91a7 --- /dev/null +++ b/tests/regressiontests/m2m_regress/tests.py @@ -0,0 +1,75 @@ +from django.core.exceptions import FieldError +from django.test import TestCase + +from models import (SelfRefer, Tag, TagCollection, Entry, SelfReferChild, + SelfReferChildSibling, Worksheet) + + +class M2MRegressionTests(TestCase): + def test_multiple_m2m(self): + # Multiple m2m references to model must be distinguished when + # accessing the relations through an instance attribute. + + s1 = SelfRefer.objects.create(name='s1') + s2 = SelfRefer.objects.create(name='s2') + s3 = SelfRefer.objects.create(name='s3') + s1.references.add(s2) + s1.related.add(s3) + + e1 = Entry.objects.create(name='e1') + t1 = Tag.objects.create(name='t1') + t2 = Tag.objects.create(name='t2') + + e1.topics.add(t1) + e1.related.add(t2) + + self.assertQuerysetEqual(s1.references.all(), [""]) + self.assertQuerysetEqual(s1.related.all(), [""]) + + self.assertQuerysetEqual(e1.topics.all(), [""]) + self.assertQuerysetEqual(e1.related.all(), [""]) + + def test_internal_related_name_not_in_error_msg(self): + # The secret internal related names for self-referential many-to-many + # fields shouldn't appear in the list when an error is made. + + self.assertRaisesRegexp(FieldError, + "Choices are: id, name, references, related, selfreferchild, selfreferchildsibling$", + lambda: SelfRefer.objects.filter(porcupine='fred') + ) + + def test_m2m_inheritance_symmetry(self): + # Test to ensure that the relationship between two inherited models + # with a self-referential m2m field maintains symmetry + + sr_child = SelfReferChild(name="Hanna") + sr_child.save() + + sr_sibling = SelfReferChildSibling(name="Beth") + sr_sibling.save() + sr_child.related.add(sr_sibling) + + self.assertQuerysetEqual(sr_child.related.all(), [""]) + self.assertQuerysetEqual(sr_sibling.related.all(), [""]) + + def test_m2m_pk_field_type(self): + # Regression for #11311 - The primary key for models in a m2m relation + # doesn't have to be an AutoField + + w = Worksheet(id='abc') + w.save() + w.delete() + + def test_add_m2m_with_base_class(self): + # Regression for #11956 -- You can add an object to a m2m with the + # base class without causing integrity errors + + t1 = Tag.objects.create(name='t1') + t2 = Tag.objects.create(name='t2') + + c1 = TagCollection.objects.create(name='c1') + c1.tags = [t1,t2] + c1 = TagCollection.objects.get(name='c1') + + self.assertQuerysetEqual(c1.tags.all(), ["", ""]) + self.assertQuerysetEqual(t1.tag_collections.all(), [""]) From 25db006b7b22f19a0fa2ce2846cced2a845a1ac5 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 14 Oct 2010 01:17:32 +0000 Subject: [PATCH 332/902] [1.2.X] Fixed #14460 -- converted managers_regress tests from doctests to unittests. We have always been at war with doctests. Patch from Gabriel Hurley. Backport of [14207]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14208 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../managers_regress/models.py | 53 ------------------ .../regressiontests/managers_regress/tests.py | 54 +++++++++++++++++++ 2 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 tests/regressiontests/managers_regress/tests.py diff --git a/tests/regressiontests/managers_regress/models.py b/tests/regressiontests/managers_regress/models.py index 41f6ec39a127..1e1b1c99818e 100644 --- a/tests/regressiontests/managers_regress/models.py +++ b/tests/regressiontests/managers_regress/models.py @@ -98,56 +98,3 @@ class Child6(Child4): # Will not inherit default manager from parent. class Child7(Parent): pass - -__test__ = {"API_TESTS": """ ->>> a1 = Child1.objects.create(name='fred', data='a1') ->>> a2 = Child1.objects.create(name='barney', data='a2') ->>> b1 = Child2.objects.create(name='fred', data='b1', value=1) ->>> b2 = Child2.objects.create(name='barney', data='b2', value=42) ->>> c1 = Child3.objects.create(name='fred', data='c1', comment='yes') ->>> c2 = Child3.objects.create(name='barney', data='c2', comment='no') ->>> d1 = Child4.objects.create(name='fred', data='d1') ->>> d2 = Child4.objects.create(name='barney', data='d2') ->>> e1 = Child5.objects.create(name='fred', comment='yes') ->>> e2 = Child5.objects.create(name='barney', comment='no') ->>> f1 = Child6.objects.create(name='fred', data='f1', value=42) ->>> f2 = Child6.objects.create(name='barney', data='f2', value=42) ->>> g1 = Child7.objects.create(name='fred') ->>> g2 = Child7.objects.create(name='barney') - ->>> Child1.manager1.all() -[] ->>> Child1.manager2.all() -[] ->>> Child1._default_manager.all() -[] - ->>> Child2._default_manager.all() -[] ->>> Child2.restricted.all() -[] - ->>> Child3._default_manager.all() -[] ->>> Child3.manager1.all() -[] ->>> Child3.manager2.all() -[] - -# Since Child6 inherits from Child4, the corresponding rows from f1 and f2 also -# appear here. This is the expected result. ->>> Child4._default_manager.order_by('data') -[, , , ] ->>> Child4.manager1.all() -[, ] - ->>> Child5._default_manager.all() -[] - ->>> Child6._default_manager.all() -[] - ->>> Child7._default_manager.order_by('name') -[, ] - -"""} diff --git a/tests/regressiontests/managers_regress/tests.py b/tests/regressiontests/managers_regress/tests.py new file mode 100644 index 000000000000..9a6db61418de --- /dev/null +++ b/tests/regressiontests/managers_regress/tests.py @@ -0,0 +1,54 @@ +from django.test import TestCase + +from models import Child1, Child2, Child3, Child4, Child5, Child6, Child7 + + +class ManagersRegressionTests(TestCase): + def test_managers(self): + a1 = Child1.objects.create(name='fred', data='a1') + a2 = Child1.objects.create(name='barney', data='a2') + b1 = Child2.objects.create(name='fred', data='b1', value=1) + b2 = Child2.objects.create(name='barney', data='b2', value=42) + c1 = Child3.objects.create(name='fred', data='c1', comment='yes') + c2 = Child3.objects.create(name='barney', data='c2', comment='no') + d1 = Child4.objects.create(name='fred', data='d1') + d2 = Child4.objects.create(name='barney', data='d2') + e1 = Child5.objects.create(name='fred', comment='yes') + e2 = Child5.objects.create(name='barney', comment='no') + f1 = Child6.objects.create(name='fred', data='f1', value=42) + f2 = Child6.objects.create(name='barney', data='f2', value=42) + g1 = Child7.objects.create(name='fred') + g2 = Child7.objects.create(name='barney') + + self.assertQuerysetEqual(Child1.manager1.all(), [""]) + self.assertQuerysetEqual(Child1.manager2.all(), [""]) + self.assertQuerysetEqual(Child1._default_manager.all(), [""]) + + self.assertQuerysetEqual(Child2._default_manager.all(), [""]) + self.assertQuerysetEqual(Child2.restricted.all(), [""]) + + self.assertQuerysetEqual(Child3._default_manager.all(), [""]) + self.assertQuerysetEqual(Child3.manager1.all(), [""]) + self.assertQuerysetEqual(Child3.manager2.all(), [""]) + + # Since Child6 inherits from Child4, the corresponding rows from f1 and + # f2 also appear here. This is the expected result. + self.assertQuerysetEqual(Child4._default_manager.order_by('data'), [ + "", + "", + "", + "" + ] + ) + self.assertQuerysetEqual(Child4.manager1.all(), [ + "", + "" + ] + ) + self.assertQuerysetEqual(Child5._default_manager.all(), [""]) + self.assertQuerysetEqual(Child6._default_manager.all(), [""]) + self.assertQuerysetEqual(Child7._default_manager.order_by('name'), [ + "", + "" + ] + ) From 14419f916bbbe8017976b19a3bcf7d45e0f30f64 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 14 Oct 2010 01:24:55 +0000 Subject: [PATCH 333/902] [1.2.X] Fixed #14459 -- converted many_to_one_regress tests from doctests to unittests. We have always been at war with doctests. Patch from Gabriel Hurley. Backport of [14210]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14211 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../many_to_one_regress/models.py | 130 ------------------ .../many_to_one_regress/tests.py | 105 ++++++++++++++ 2 files changed, 105 insertions(+), 130 deletions(-) create mode 100644 tests/regressiontests/many_to_one_regress/tests.py diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py index d491f002c29e..53348a753d57 100644 --- a/tests/regressiontests/many_to_one_regress/models.py +++ b/tests/regressiontests/many_to_one_regress/models.py @@ -44,133 +44,3 @@ class Relation(models.Model): def __unicode__(self): return u"%s - %s" % (self.left.category.name, self.right.category.name) - - -__test__ = {'API_TESTS':""" ->>> Third.objects.create(id='3', name='An example') - ->>> parent = Parent(name = 'fred') ->>> parent.save() ->>> Child.objects.create(name='bam-bam', parent=parent) - - -# -# Tests of ForeignKey assignment and the related-object cache (see #6886). -# ->>> p = Parent.objects.create(name="Parent") ->>> c = Child.objects.create(name="Child", parent=p) - -# Look up the object again so that we get a "fresh" object. ->>> c = Child.objects.get(name="Child") ->>> p = c.parent - -# Accessing the related object again returns the exactly same object. ->>> c.parent is p -True - -# But if we kill the cache, we get a new object. ->>> del c._parent_cache ->>> c.parent is p -False - -# Assigning a new object results in that object getting cached immediately. ->>> p2 = Parent.objects.create(name="Parent 2") ->>> c.parent = p2 ->>> c.parent is p2 -True - -# Assigning None succeeds if field is null=True. ->>> p.bestchild = None ->>> p.bestchild is None -True - -# bestchild should still be None after saving. ->>> p.save() ->>> p.bestchild is None -True - -# bestchild should still be None after fetching the object again. ->>> p = Parent.objects.get(name="Parent") ->>> p.bestchild is None -True - -# Assigning None fails: Child.parent is null=False. ->>> c.parent = None -Traceback (most recent call last): - ... -ValueError: Cannot assign None: "Child.parent" does not allow null values. - -# You also can't assign an object of the wrong type here ->>> c.parent = First(id=1, second=1) -Traceback (most recent call last): - ... -ValueError: Cannot assign "": "Child.parent" must be a "Parent" instance. - -# Nor can you explicitly assign None to Child.parent during object creation -# (regression for #9649). ->>> Child(name='xyzzy', parent=None) -Traceback (most recent call last): - ... -ValueError: Cannot assign None: "Child.parent" does not allow null values. ->>> Child.objects.create(name='xyzzy', parent=None) -Traceback (most recent call last): - ... -ValueError: Cannot assign None: "Child.parent" does not allow null values. - -# Creation using keyword argument should cache the related object. ->>> p = Parent.objects.get(name="Parent") ->>> c = Child(parent=p) ->>> c.parent is p -True - -# Creation using keyword argument and unsaved related instance (#8070). ->>> p = Parent() ->>> c = Child(parent=p) ->>> c.parent is p -True - -# Creation using attname keyword argument and an id will cause the related -# object to be fetched. ->>> p = Parent.objects.get(name="Parent") ->>> c = Child(parent_id=p.id) ->>> c.parent is p -False ->>> c.parent == p -True - - -# -# Test of multiple ForeignKeys to the same model (bug #7125). -# ->>> c1 = Category.objects.create(name='First') ->>> c2 = Category.objects.create(name='Second') ->>> c3 = Category.objects.create(name='Third') ->>> r1 = Record.objects.create(category=c1) ->>> r2 = Record.objects.create(category=c1) ->>> r3 = Record.objects.create(category=c2) ->>> r4 = Record.objects.create(category=c2) ->>> r5 = Record.objects.create(category=c3) ->>> r = Relation.objects.create(left=r1, right=r2) ->>> r = Relation.objects.create(left=r3, right=r4) ->>> r = Relation.objects.create(left=r1, right=r3) ->>> r = Relation.objects.create(left=r5, right=r2) ->>> r = Relation.objects.create(left=r3, right=r2) - ->>> Relation.objects.filter(left__category__name__in=['First'], right__category__name__in=['Second']) -[] - ->>> Category.objects.filter(record__left_set__right__category__name='Second').order_by('name') -[, ] - ->>> c2 = Child.objects.create(name="Grandchild", parent=c) -Traceback (most recent call last): - ... -ValueError: Cannot assign "": "Child.parent" must be a "Parent" instance. - -# Regression for #12190 -- Should be able to instantiate a FK -# outside of a model, and interrogate its related field. ->>> cat = models.ForeignKey(Category) ->>> cat.rel.get_related_field().name -'id' - -"""} diff --git a/tests/regressiontests/many_to_one_regress/tests.py b/tests/regressiontests/many_to_one_regress/tests.py new file mode 100644 index 000000000000..7d2a49cea522 --- /dev/null +++ b/tests/regressiontests/many_to_one_regress/tests.py @@ -0,0 +1,105 @@ +from django.db import models +from django.test import TestCase + +from models import First, Second, Third, Parent, Child, Category, Record, Relation + +class ManyToOneRegressionTests(TestCase): + def test_object_creation(self): + Third.objects.create(id='3', name='An example') + parent = Parent(name='fred') + parent.save() + Child.objects.create(name='bam-bam', parent=parent) + + def test_fk_assignment_and_related_object_cache(self): + # Tests of ForeignKey assignment and the related-object cache (see #6886). + + p = Parent.objects.create(name="Parent") + c = Child.objects.create(name="Child", parent=p) + + # Look up the object again so that we get a "fresh" object. + c = Child.objects.get(name="Child") + p = c.parent + + # Accessing the related object again returns the exactly same object. + self.assertTrue(c.parent is p) + + # But if we kill the cache, we get a new object. + del c._parent_cache + self.assertFalse(c.parent is p) + + # Assigning a new object results in that object getting cached immediately. + p2 = Parent.objects.create(name="Parent 2") + c.parent = p2 + self.assertTrue(c.parent is p2) + + # Assigning None succeeds if field is null=True. + p.bestchild = None + self.assertTrue(p.bestchild is None) + + # bestchild should still be None after saving. + p.save() + self.assertTrue(p.bestchild is None) + + # bestchild should still be None after fetching the object again. + p = Parent.objects.get(name="Parent") + self.assertTrue(p.bestchild is None) + + # Assigning None fails: Child.parent is null=False. + self.assertRaises(ValueError, setattr, c, "parent", None) + + # You also can't assign an object of the wrong type here + self.assertRaises(ValueError, setattr, c, "parent", First(id=1, second=1)) + + # Nor can you explicitly assign None to Child.parent during object + # creation (regression for #9649). + self.assertRaises(ValueError, Child, name='xyzzy', parent=None) + self.assertRaises(ValueError, Child.objects.create, name='xyzzy', parent=None) + + # Creation using keyword argument should cache the related object. + p = Parent.objects.get(name="Parent") + c = Child(parent=p) + self.assertTrue(c.parent is p) + + # Creation using keyword argument and unsaved related instance (#8070). + p = Parent() + c = Child(parent=p) + self.assertTrue(c.parent is p) + + # Creation using attname keyword argument and an id will cause the + # related object to be fetched. + p = Parent.objects.get(name="Parent") + c = Child(parent_id=p.id) + self.assertFalse(c.parent is p) + self.assertEqual(c.parent, p) + + def test_multiple_foreignkeys(self): + # Test of multiple ForeignKeys to the same model (bug #7125). + c1 = Category.objects.create(name='First') + c2 = Category.objects.create(name='Second') + c3 = Category.objects.create(name='Third') + r1 = Record.objects.create(category=c1) + r2 = Record.objects.create(category=c1) + r3 = Record.objects.create(category=c2) + r4 = Record.objects.create(category=c2) + r5 = Record.objects.create(category=c3) + r = Relation.objects.create(left=r1, right=r2) + r = Relation.objects.create(left=r3, right=r4) + r = Relation.objects.create(left=r1, right=r3) + r = Relation.objects.create(left=r5, right=r2) + r = Relation.objects.create(left=r3, right=r2) + + q1 = Relation.objects.filter(left__category__name__in=['First'], right__category__name__in=['Second']) + self.assertQuerysetEqual(q1, [""]) + + q2 = Category.objects.filter(record__left_set__right__category__name='Second').order_by('name') + self.assertQuerysetEqual(q2, ["", ""]) + + p = Parent.objects.create(name="Parent") + c = Child.objects.create(name="Child", parent=p) + self.assertRaises(ValueError, Child.objects.create, name="Grandchild", parent=c) + + def test_fk_instantiation_outside_model(self): + # Regression for #12190 -- Should be able to instantiate a FK outside + # of a model, and interrogate its related field. + cat = models.ForeignKey(Category) + self.assertEqual('id', cat.rel.get_related_field().name) From e50066d0b59fd106272357f5747d6f4f33b47751 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 14 Oct 2010 01:40:39 +0000 Subject: [PATCH 334/902] [1.2.X] Fixed #14456 -- converted inline_formsets tests from doctests to unittests. We have always been at war with doctests. Thanks to prestontimmons for the patch. Backport of [14212]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14213 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../regressiontests/inline_formsets/models.py | 44 +--------------- .../regressiontests/inline_formsets/tests.py | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 43 deletions(-) diff --git a/tests/regressiontests/inline_formsets/models.py b/tests/regressiontests/inline_formsets/models.py index 9b1f8b4932f3..d76eea758b6a 100644 --- a/tests/regressiontests/inline_formsets/models.py +++ b/tests/regressiontests/inline_formsets/models.py @@ -1,6 +1,7 @@ # coding: utf-8 from django.db import models + class School(models.Model): name = models.CharField(max_length=100) @@ -25,46 +26,3 @@ class Poem(models.Model): def __unicode__(self): return self.name - -__test__ = {'API_TESTS': """ - ->>> from django.forms.models import inlineformset_factory - - -Child has two ForeignKeys to Parent, so if we don't specify which one to use -for the inline formset, we should get an exception. - ->>> ifs = inlineformset_factory(Parent, Child) -Traceback (most recent call last): - ... -Exception: has more than 1 ForeignKey to - - -These two should both work without a problem. - ->>> ifs = inlineformset_factory(Parent, Child, fk_name='mother') ->>> ifs = inlineformset_factory(Parent, Child, fk_name='father') - - -If we specify fk_name, but it isn't a ForeignKey from the child model to the -parent model, we should get an exception. - ->>> ifs = inlineformset_factory(Parent, Child, fk_name='school') -Traceback (most recent call last): - ... -Exception: fk_name 'school' is not a ForeignKey to - - -If the field specified in fk_name is not a ForeignKey, we should get an -exception. - ->>> ifs = inlineformset_factory(Parent, Child, fk_name='test') -Traceback (most recent call last): - ... -Exception: has no field named 'test' - - -# Regression test for #9171. ->>> ifs = inlineformset_factory(Parent, Child, exclude=('school',), fk_name='mother') -""" -} diff --git a/tests/regressiontests/inline_formsets/tests.py b/tests/regressiontests/inline_formsets/tests.py index 83d2fba1933a..24488661c73a 100644 --- a/tests/regressiontests/inline_formsets/tests.py +++ b/tests/regressiontests/inline_formsets/tests.py @@ -2,7 +2,9 @@ from django.forms.models import inlineformset_factory from regressiontests.inline_formsets.models import Poet, Poem, School, Parent, Child + class DeletionTests(TestCase): + def test_deletion(self): PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True) poet = Poet.objects.create(name='test') @@ -103,3 +105,51 @@ def test_save_new(self): obj.save() self.assertEqual(school.child_set.count(), 1) + +class InlineFormsetFactoryTest(TestCase): + def test_inline_formset_factory(self): + """ + These should both work without a problem. + """ + inlineformset_factory(Parent, Child, fk_name='mother') + inlineformset_factory(Parent, Child, fk_name='father') + + def test_exception_on_unspecified_foreign_key(self): + """ + Child has two ForeignKeys to Parent, so if we don't specify which one + to use for the inline formset, we should get an exception. + """ + self.assertRaisesRegexp(Exception, + " has more than 1 ForeignKey to ", + inlineformset_factory, Parent, Child + ) + + def test_fk_name_not_foreign_key_field_from_child(self): + """ + If we specify fk_name, but it isn't a ForeignKey from the child model + to the parent model, we should get an exception. + """ + self.assertRaises(Exception, + "fk_name 'school' is not a ForeignKey to ", + inlineformset_factory, Parent, Child, fk_name='school' + ) + + def test_non_foreign_key_field(self): + """ + If the field specified in fk_name is not a ForeignKey, we should get an + exception. + """ + self.assertRaisesRegexp(Exception, + " has no field named 'test'", + inlineformset_factory, Parent, Child, fk_name='test' + ) + + def test_any_iterable_allowed_as_argument_to_exclude(self): + # Regression test for #9171. + inlineformset_factory( + Parent, Child, exclude=['school'], fk_name='mother' + ) + + inlineformset_factory( + Parent, Child, exclude=('school',), fk_name='mother' + ) From ece06a80069199acab4569e8a84666114d955535 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Thu, 14 Oct 2010 10:04:03 +0000 Subject: [PATCH 335/902] [1.2.X] Fixed #5327 -- Added standardized field information to ModelChoiceField and ModelMultipleChoiceField documentation. Thanks to danielrubio for the report and PhiR for the text. Backport of [14214] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14215 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/forms/fields.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 5047d4f5edd1..c005436a25fd 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -856,6 +856,12 @@ objects (in the case of ``ModelMultipleChoiceField``) into the .. class:: ModelChoiceField(**kwargs) + * Default widget: ``Select`` + * Empty value: ``None`` + * Normalizes to: A model instance. + * Validates that the given id exists in the queryset. + * Error message keys: ``required``, ``invalid_choice`` + Allows the selection of a single model object, suitable for representing a foreign key. A single argument is required: @@ -901,6 +907,14 @@ example:: .. class:: ModelMultipleChoiceField(**kwargs) + * Default widget: ``SelectMultiple`` + * Empty value: ``[]`` (an empty list) + * Normalizes to: A list of model instances. + * Validates that every id in the given list of values exists in the + queryset. + * Error message keys: ``required``, ``list``, ``invalid_choice``, + ``invalid_pk_value`` + Allows the selection of one or more model objects, suitable for representing a many-to-many relation. As with :class:`ModelChoiceField`, you can use ``label_from_instance`` to customize the object From 7be27dc0836a092f03d7a6f1fc7834ef4f585f18 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 14 Oct 2010 18:38:43 +0000 Subject: [PATCH 336/902] [1.2.X] Fixed #14301 -- Handle email validation gracefully with email addresses containing non-ASCII characters. Thanks, Andi Albrecht. Backport from trunk (r14216). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14217 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/mail/message.py | 6 +++++- tests/regressiontests/mail/tests.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 91a10a0a5cd3..ad0fbc8ef7c4 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -67,7 +67,11 @@ def forbid_multi_line_headers(name, val, encoding): result = [] for nm, addr in getaddresses((val,)): nm = str(Header(nm.encode(encoding), encoding)) - result.append(formataddr((nm, str(addr)))) + try: + addr = addr.encode('ascii') + except UnicodeEncodeError: # IDN + addr = str(Header(addr.encode(encoding), encoding)) + result.append(formataddr((nm, addr))) val = ', '.join(result) else: val = Header(val.encode(encoding), encoding) diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py index f13d280bd685..47b62eabccc9 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/regressiontests/mail/tests.py @@ -336,3 +336,18 @@ def test_mail_prefix(self): settings.ADMINS = old_admins settings.MANAGERS = old_managers + + def test_idn_validation(self): + """Test internationalized email adresses""" + # Regression for #14301. + mail.outbox = [] + from_email = u'fröm@öäü.com' + to_email = u'tö@öäü.com' + connection = mail.get_connection('django.core.mail.backends.locmem.EmailBackend') + send_mail('Subject', 'Content', from_email, [to_email], connection=connection) + self.assertEqual(len(mail.outbox), 1) + message = mail.outbox[0] + self.assertEqual(message.subject, 'Subject') + self.assertEqual(message.from_email, from_email) + self.assertEqual(message.to, [to_email]) + self.assertTrue(message.message().as_string().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: =?utf-8?b?ZnLDtm1Aw7bDpMO8LmNvbQ==?=\nTo: =?utf-8?b?dMO2QMO2w6TDvC5jb20=?=')) From f18e27145c58fa4dcd4055c42ebfb87d0d3b64ff Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 15 Oct 2010 02:41:21 +0000 Subject: [PATCH 337/902] [1.2.x] Fixed #14454 -- converted admin_widgets tests from doctests to unittests. We have always been at war with doctests. Thanks to prestontimmons for the patch. Backport of [14221]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14222 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/admin_widgets/models.py | 105 +----------- tests/regressiontests/admin_widgets/tests.py | 155 +++++++++++++++++- 2 files changed, 155 insertions(+), 105 deletions(-) diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py index c3c824e4a802..450c3e6f9e0a 100644 --- a/tests/regressiontests/admin_widgets/models.py +++ b/tests/regressiontests/admin_widgets/models.py @@ -1,11 +1,8 @@ - -from django.conf import settings from django.db import models -from django.core.files.storage import default_storage from django.contrib.auth.models import User -class MyFileField(models.FileField): - pass +class MyFileField(models.FileField): + pass class Member(models.Model): name = models.CharField(max_length=100) @@ -69,101 +66,3 @@ class CarTire(models.Model): A single car tire. This to test that a user can only select their own cars. """ car = models.ForeignKey(Car) - -__test__ = {'WIDGETS_TESTS': """ ->>> from datetime import datetime ->>> from django.utils.html import escape, conditional_escape ->>> from django.core.files.uploadedfile import SimpleUploadedFile ->>> from django.contrib.admin.widgets import FilteredSelectMultiple, AdminSplitDateTime ->>> from django.contrib.admin.widgets import AdminFileWidget, ForeignKeyRawIdWidget, ManyToManyRawIdWidget ->>> from django.contrib.admin.widgets import RelatedFieldWidgetWrapper ->>> from django.utils.translation import activate, deactivate ->>> from django.conf import settings - - -Calling conditional_escape on the output of widget.render will simulate what -happens in the template. This is easier than setting up a template and context -for each test. - -Make sure that the Admin widgets render properly, that is, without their extra -HTML escaped. - ->>> w = FilteredSelectMultiple('test', False) ->>> print conditional_escape(w.render('test', 'test')) - - - ->>> w = FilteredSelectMultiple('test', True) ->>> print conditional_escape(w.render('test', 'test')) - - - ->>> w = AdminSplitDateTime() ->>> print conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))) -

        Date:
        Time:

        ->>> activate('de-at') ->>> settings.USE_L10N = True ->>> w.is_localized = True ->>> print conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))) -

        Datum:
        Zeit:

        ->>> deactivate() ->>> settings.USE_L10N = False - ->>> band = Band.objects.create(pk=1, name='Linkin Park') ->>> album = band.album_set.create(name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg') - ->>> w = AdminFileWidget() ->>> print conditional_escape(w.render('test', album.cover_art)) -Currently: albums\hybrid_theory.jpg
        Change: ->>> print conditional_escape(w.render('test', SimpleUploadedFile('test', 'content'))) - - ->>> rel = Album._meta.get_field('band').rel ->>> w = ForeignKeyRawIdWidget(rel) ->>> print conditional_escape(w.render('test', band.pk, attrs={})) - Lookup Linkin Park - ->>> m1 = Member.objects.create(pk=1, name='Chester') ->>> m2 = Member.objects.create(pk=2, name='Mike') ->>> band.members.add(m1, m2) - ->>> rel = Band._meta.get_field('members').rel ->>> w = ManyToManyRawIdWidget(rel) ->>> print conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})) - Lookup ->>> print conditional_escape(w.render('test', [m1.pk])) - Lookup ->>> w._has_changed(None, None) -False ->>> w._has_changed([], None) -False ->>> w._has_changed(None, [u'1']) -True ->>> w._has_changed([1, 2], [u'1', u'2']) -False ->>> w._has_changed([1, 2], [u'1']) -True ->>> w._has_changed([1, 2], [u'1', u'3']) -True - -# Check that ForeignKeyRawIdWidget works with fields which aren't related to -# the model's primary key. ->>> apple = Inventory.objects.create(barcode=86, name='Apple') ->>> pear = Inventory.objects.create(barcode=22, name='Pear') ->>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple) ->>> rel = Inventory._meta.get_field('parent').rel ->>> w = ForeignKeyRawIdWidget(rel) ->>> print w.render('test', core.parent_id, attrs={}) - Lookup Apple - -# see #9258 ->>> hidden = Inventory.objects.create(barcode=93, name='Hidden', hidden=True) ->>> child_of_hidden = Inventory.objects.create(barcode=94, name='Child of hidden', parent=hidden) ->>> print w.render('test', child_of_hidden.parent_id, attrs={}) - Lookup Hidden -""" % { - 'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX, - 'STORAGE_URL': default_storage.url(''), -}} diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py index c4456443351e..73eab8cac608 100644 --- a/tests/regressiontests/admin_widgets/tests.py +++ b/tests/regressiontests/admin_widgets/tests.py @@ -1,13 +1,25 @@ # encoding: utf-8 +from datetime import datetime +from unittest import TestCase + from django import forms +from django.conf import settings from django.contrib import admin from django.contrib.admin import widgets -from unittest import TestCase -from django.test import TestCase as DjangoTestCase +from django.contrib.admin.widgets import FilteredSelectMultiple, AdminSplitDateTime +from django.contrib.admin.widgets import (AdminFileWidget, ForeignKeyRawIdWidget, + ManyToManyRawIdWidget) +from django.core.files.storage import default_storage +from django.core.files.uploadedfile import SimpleUploadedFile from django.db.models import DateField +from django.test import TestCase as DjangoTestCase +from django.utils.html import conditional_escape +from django.utils.translation import activate, deactivate + import models + class AdminFormfieldForDBFieldTests(TestCase): """ Tests for correct behavior of ModelAdmin.formfield_for_dbfield @@ -104,6 +116,7 @@ def testChoicesWithRadioFields(self): def testInheritance(self): self.assertFormfield(models.Album, 'backside_art', widgets.AdminFileWidget) + class AdminFormfieldForDBFieldWithRequestTests(DjangoTestCase): fixtures = ["admin-widgets-users.xml"] @@ -116,6 +129,7 @@ def testFilterChoicesByRequestUser(self): self.assert_("BMW M3" not in response.content) self.assert_("Volkswagon Passat" in response.content) + class AdminForeignKeyWidgetChangeList(DjangoTestCase): fixtures = ["admin-widgets-users.xml"] admin_root = '/widget_admin' @@ -130,6 +144,7 @@ def test_changelist_foreignkey(self): response = self.client.get('%s/admin_widgets/car/' % self.admin_root) self.failUnless('%s/auth/user/add/' % self.admin_root in response.content) + class AdminForeignKeyRawIdWidget(DjangoTestCase): fixtures = ["admin-widgets-users.xml"] admin_root = '/widget_admin' @@ -163,3 +178,139 @@ def test_invalid_target_id(self): self.assertContains(response, 'Select a valid choice. That choice is not one of the available choices.') + + +class FilteredSelectMultipleWidgetTest(TestCase): + def test_render(self): + w = FilteredSelectMultiple('test', False) + self.assertEqual( + conditional_escape(w.render('test', 'test')), + '\n' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX} + ) + + def test_stacked_render(self): + w = FilteredSelectMultiple('test', True) + self.assertEqual( + conditional_escape(w.render('test', 'test')), + '\n' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX} + ) + + +class AdminSplitDateTimeWidgetTest(TestCase): + def test_render(self): + w = AdminSplitDateTime() + self.assertEqual( + conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))), + '

        Date:
        Time:

        ', + ) + + def test_localization(self): + w = AdminSplitDateTime() + + activate('de-at') + old_USE_L10N = settings.USE_L10N + settings.USE_L10N = True + w.is_localized = True + self.assertEqual( + conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))), + '

        Datum:
        Zeit:

        ', + ) + deactivate() + settings.USE_L10N = old_USE_L10N + + +class AdminFileWidgetTest(DjangoTestCase): + def test_render(self): + band = models.Band.objects.create(name='Linkin Park') + album = band.album_set.create( + name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg' + ) + + w = AdminFileWidget() + self.assertEqual( + conditional_escape(w.render('test', album.cover_art)), + 'Currently: albums\hybrid_theory.jpg
        Change: ' % {'STORAGE_URL': default_storage.url('')}, + ) + + self.assertEqual( + conditional_escape(w.render('test', SimpleUploadedFile('test', 'content'))), + '', + ) + + +class ForeignKeyRawIdWidgetTest(DjangoTestCase): + def test_render(self): + band = models.Band.objects.create(name='Linkin Park') + band.album_set.create( + name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg' + ) + rel = models.Album._meta.get_field('band').rel + + w = ForeignKeyRawIdWidget(rel) + self.assertEqual( + conditional_escape(w.render('test', band.pk, attrs={})), + ' Lookup Linkin Park' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX, "bandpk": band.pk}, + ) + + def test_relations_to_non_primary_key(self): + # Check that ForeignKeyRawIdWidget works with fields which aren't + # related to the model's primary key. + apple = models.Inventory.objects.create(barcode=86, name='Apple') + models.Inventory.objects.create(barcode=22, name='Pear') + core = models.Inventory.objects.create( + barcode=87, name='Core', parent=apple + ) + rel = models.Inventory._meta.get_field('parent').rel + w = ForeignKeyRawIdWidget(rel) + self.assertEqual( + w.render('test', core.parent_id, attrs={}), + ' Lookup Apple' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX}, + ) + + + def test_proper_manager_for_label_lookup(self): + # see #9258 + rel = models.Inventory._meta.get_field('parent').rel + w = ForeignKeyRawIdWidget(rel) + + hidden = models.Inventory.objects.create( + barcode=93, name='Hidden', hidden=True + ) + child_of_hidden = models.Inventory.objects.create( + barcode=94, name='Child of hidden', parent=hidden + ) + self.assertEqual( + w.render('test', child_of_hidden.parent_id, attrs={}), + ' Lookup Hidden' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX}, + ) + + +class ManyToManyRawIdWidgetTest(DjangoTestCase): + def test_render(self): + band = models.Band.objects.create(name='Linkin Park') + band.album_set.create( + name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg' + ) + + m1 = models.Member.objects.create(name='Chester') + m2 = models.Member.objects.create(name='Mike') + band.members.add(m1, m2) + rel = models.Band._meta.get_field('members').rel + + w = ManyToManyRawIdWidget(rel) + self.assertEqual( + conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})), + ' Lookup' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX, "m1pk": m1.pk, "m2pk": m2.pk}, + ) + + self.assertEqual( + conditional_escape(w.render('test', [m1.pk])), + ' Lookup' % {"ADMIN_MEDIA_PREFIX": settings.ADMIN_MEDIA_PREFIX, "m1pk": m1.pk}, + ) + + self.assertEqual(w._has_changed(None, None), False) + self.assertEqual(w._has_changed([], None), False) + self.assertEqual(w._has_changed(None, [u'1']), True) + self.assertEqual(w._has_changed([1, 2], [u'1', u'2']), False) + self.assertEqual(w._has_changed([1, 2], [u'1']), True) + self.assertEqual(w._has_changed([1, 2], [u'1', u'3']), True) From 3c469e95fbd92986563cbc5508bfce0bcd868d47 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 15 Oct 2010 06:52:53 +0000 Subject: [PATCH 338/902] [1.2.X] Fixed #14472 -- converted generic_relations tests from doctests to unitests. We have always been at war with doctests. Thanks to Gabriel Hurley for the patch. Backport of [14225]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14226 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/generic_relations/models.py | 172 +------------- tests/modeltests/generic_relations/tests.py | 223 +++++++++++++++++++ 2 files changed, 226 insertions(+), 169 deletions(-) create mode 100644 tests/modeltests/generic_relations/tests.py diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py index 1bc6577bf762..6161f9ebeaf5 100644 --- a/tests/modeltests/generic_relations/models.py +++ b/tests/modeltests/generic_relations/models.py @@ -9,9 +9,10 @@ from complete). """ -from django.db import models -from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic +from django.contrib.contenttypes.models import ContentType +from django.db import models + class TaggedItem(models.Model): """A tag on an item.""" @@ -77,170 +78,3 @@ class Mineral(models.Model): def __unicode__(self): return self.name - -__test__ = {'API_TESTS':""" -# Create the world in 7 lines of code... ->>> lion = Animal(common_name="Lion", latin_name="Panthera leo") ->>> platypus = Animal(common_name="Platypus", latin_name="Ornithorhynchus anatinus") ->>> eggplant = Vegetable(name="Eggplant", is_yucky=True) ->>> bacon = Vegetable(name="Bacon", is_yucky=False) ->>> quartz = Mineral(name="Quartz", hardness=7) ->>> for o in (platypus, lion, eggplant, bacon, quartz): -... o.save() - -# Objects with declared GenericRelations can be tagged directly -- the API -# mimics the many-to-many API. ->>> bacon.tags.create(tag="fatty") - ->>> bacon.tags.create(tag="salty") - ->>> lion.tags.create(tag="yellow") - ->>> lion.tags.create(tag="hairy") - ->>> platypus.tags.create(tag="fatty") - - ->>> lion.tags.all() -[, ] ->>> bacon.tags.all() -[, ] - -# You can easily access the content object like a foreign key. ->>> t = TaggedItem.objects.get(tag="salty") ->>> t.content_object - - -# Recall that the Mineral class doesn't have an explicit GenericRelation -# defined. That's OK, because you can create TaggedItems explicitly. ->>> tag1 = TaggedItem(content_object=quartz, tag="shiny") ->>> tag2 = TaggedItem(content_object=quartz, tag="clearish") ->>> tag1.save() ->>> tag2.save() - -# However, excluding GenericRelations means your lookups have to be a bit more -# explicit. ->>> from django.contrib.contenttypes.models import ContentType ->>> ctype = ContentType.objects.get_for_model(quartz) ->>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id) -[, ] - -# You can set a generic foreign key in the way you'd expect. ->>> tag1.content_object = platypus ->>> tag1.save() ->>> platypus.tags.all() -[, ] ->>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id) -[] - -# Queries across generic relations respect the content types. Even though there are two TaggedItems with a tag of "fatty", this query only pulls out the one with the content type related to Animals. ->>> Animal.objects.order_by('common_name') -[, ] ->>> Animal.objects.filter(tags__tag='fatty') -[] ->>> Animal.objects.exclude(tags__tag='fatty') -[] - -# If you delete an object with an explicit Generic relation, the related -# objects are deleted when the source object is deleted. -# Original list of tags: ->>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] -[(u'clearish', , 1), (u'fatty', , 2), (u'fatty', , 1), (u'hairy', , 2), (u'salty', , 2), (u'shiny', , 1), (u'yellow', , 2)] - ->>> lion.delete() ->>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] -[(u'clearish', , 1), (u'fatty', , 2), (u'fatty', , 1), (u'salty', , 2), (u'shiny', , 1)] - -# If Generic Relation is not explicitly defined, any related objects -# remain after deletion of the source object. ->>> quartz.delete() ->>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] -[(u'clearish', , 1), (u'fatty', , 2), (u'fatty', , 1), (u'salty', , 2), (u'shiny', , 1)] - -# If you delete a tag, the objects using the tag are unaffected -# (other than losing a tag) ->>> tag = TaggedItem.objects.get(id=1) ->>> tag.delete() ->>> bacon.tags.all() -[] ->>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()] -[(u'clearish', , 1), (u'fatty', , 1), (u'salty', , 2), (u'shiny', , 1)] - ->>> TaggedItem.objects.filter(tag='fatty').delete() - ->>> ctype = ContentType.objects.get_for_model(lion) ->>> Animal.objects.filter(tags__content_type=ctype) -[] - -# Simple tests for multiple GenericForeignKeys -# only uses one model, since the above tests should be sufficient. ->>> tiger, cheetah, bear = Animal(common_name="tiger"), Animal(common_name="cheetah"), Animal(common_name="bear") ->>> for o in [tiger, cheetah, bear]: o.save() - -# Create directly ->>> Comparison(first_obj=cheetah, other_obj=tiger, comparative="faster").save() ->>> Comparison(first_obj=tiger, other_obj=cheetah, comparative="cooler").save() - -# Create using GenericRelation ->>> tiger.comparisons.create(other_obj=bear, comparative="cooler") - ->>> tiger.comparisons.create(other_obj=cheetah, comparative="stronger") - - ->>> cheetah.comparisons.all() -[] - -# Filtering works ->>> tiger.comparisons.filter(comparative="cooler") -[, ] - -# Filtering and deleting works ->>> subjective = ["cooler"] ->>> tiger.comparisons.filter(comparative__in=subjective).delete() ->>> Comparison.objects.all() -[, ] - -# If we delete cheetah, Comparisons with cheetah as 'first_obj' will be deleted -# since Animal has an explicit GenericRelation to Comparison through first_obj. -# Comparisons with cheetah as 'other_obj' will not be deleted. ->>> cheetah.delete() ->>> Comparison.objects.all() -[] - -# GenericForeignKey should work with subclasses (see #8309) ->>> quartz = Mineral.objects.create(name="Quartz", hardness=7) ->>> valuedtag = ValuableTaggedItem(content_object=quartz, tag="shiny", value=10) ->>> valuedtag.save() ->>> valuedtag.content_object - - -# GenericInlineFormSet tests ################################################## - ->>> from django.contrib.contenttypes.generic import generic_inlineformset_factory - ->>> GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1) ->>> formset = GenericFormSet() ->>> for form in formset.forms: -... print form.as_p() -

        -

        ->>> formset = GenericFormSet(instance=Animal()) ->>> for form in formset.forms: -... print form.as_p() -

        -

        - ->>> formset = GenericFormSet(instance=platypus) ->>> for form in formset.forms: -... print form.as_p() -

        -

        -

        -

        - ->>> formset = GenericFormSet(instance=lion, prefix='x') ->>> for form in formset.forms: -... print form.as_p() -

        -

        -"""} diff --git a/tests/modeltests/generic_relations/tests.py b/tests/modeltests/generic_relations/tests.py new file mode 100644 index 000000000000..e26db55ade5e --- /dev/null +++ b/tests/modeltests/generic_relations/tests.py @@ -0,0 +1,223 @@ +from django.contrib.contenttypes.generic import generic_inlineformset_factory +from django.contrib.contenttypes.models import ContentType +from django.test import TestCase + +from models import (TaggedItem, ValuableTaggedItem, Comparison, Animal, + Vegetable, Mineral) + + +class GenericRelationsTests(TestCase): + def test_generic_relations(self): + # Create the world in 7 lines of code... + lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo") + platypus = Animal.objects.create( + common_name="Platypus", latin_name="Ornithorhynchus anatinus" + ) + eggplant = Vegetable.objects.create(name="Eggplant", is_yucky=True) + bacon = Vegetable.objects.create(name="Bacon", is_yucky=False) + quartz = Mineral.objects.create(name="Quartz", hardness=7) + + # Objects with declared GenericRelations can be tagged directly -- the + # API mimics the many-to-many API. + bacon.tags.create(tag="fatty") + bacon.tags.create(tag="salty") + lion.tags.create(tag="yellow") + lion.tags.create(tag="hairy") + platypus.tags.create(tag="fatty") + self.assertQuerysetEqual(lion.tags.all(), [ + "", + "" + ]) + self.assertQuerysetEqual(bacon.tags.all(), [ + "", + "" + ]) + + # You can easily access the content object like a foreign key. + t = TaggedItem.objects.get(tag="salty") + self.assertEqual(t.content_object, bacon) + + # Recall that the Mineral class doesn't have an explicit GenericRelation + # defined. That's OK, because you can create TaggedItems explicitly. + tag1 = TaggedItem.objects.create(content_object=quartz, tag="shiny") + tag2 = TaggedItem.objects.create(content_object=quartz, tag="clearish") + + # However, excluding GenericRelations means your lookups have to be a + # bit more explicit. + ctype = ContentType.objects.get_for_model(quartz) + q = TaggedItem.objects.filter( + content_type__pk=ctype.id, object_id=quartz.id + ) + self.assertQuerysetEqual(q, [ + "", + "" + ]) + + # You can set a generic foreign key in the way you'd expect. + tag1.content_object = platypus + tag1.save() + self.assertQuerysetEqual(platypus.tags.all(), [ + "", + "" + ]) + q = TaggedItem.objects.filter( + content_type__pk=ctype.id, object_id=quartz.id + ) + self.assertQuerysetEqual(q, [""]) + + # Queries across generic relations respect the content types. Even + # though there are two TaggedItems with a tag of "fatty", this query + # only pulls out the one with the content type related to Animals. + self.assertQuerysetEqual(Animal.objects.order_by('common_name'), [ + "", + "" + ]) + self.assertQuerysetEqual(Animal.objects.filter(tags__tag='fatty'), [ + "" + ]) + self.assertQuerysetEqual(Animal.objects.exclude(tags__tag='fatty'), [ + "" + ]) + + # If you delete an object with an explicit Generic relation, the related + # objects are deleted when the source object is deleted. + # Original list of tags: + comp_func = lambda obj: ( + obj.tag, obj.content_type.model_class(), obj.object_id + ) + + self.assertQuerysetEqual(TaggedItem.objects.all(), [ + (u'clearish', Mineral, quartz.pk), + (u'fatty', Vegetable, bacon.pk), + (u'fatty', Animal, platypus.pk), + (u'hairy', Animal, lion.pk), + (u'salty', Vegetable, bacon.pk), + (u'shiny', Animal, platypus.pk), + (u'yellow', Animal, lion.pk) + ], + comp_func + ) + lion.delete() + self.assertQuerysetEqual(TaggedItem.objects.all(), [ + (u'clearish', Mineral, quartz.pk), + (u'fatty', Vegetable, bacon.pk), + (u'fatty', Animal, platypus.pk), + (u'salty', Vegetable, bacon.pk), + (u'shiny', Animal, platypus.pk) + ], + comp_func + ) + + # If Generic Relation is not explicitly defined, any related objects + # remain after deletion of the source object. + quartz_pk = quartz.pk + quartz.delete() + self.assertQuerysetEqual(TaggedItem.objects.all(), [ + (u'clearish', Mineral, quartz_pk), + (u'fatty', Vegetable, bacon.pk), + (u'fatty', Animal, platypus.pk), + (u'salty', Vegetable, bacon.pk), + (u'shiny', Animal, platypus.pk) + ], + comp_func + ) + # If you delete a tag, the objects using the tag are unaffected + # (other than losing a tag) + tag = TaggedItem.objects.get(id=1) + tag.delete() + self.assertQuerysetEqual(bacon.tags.all(), [""]) + self.assertQuerysetEqual(TaggedItem.objects.all(), [ + (u'clearish', Mineral, quartz_pk), + (u'fatty', Animal, platypus.pk), + (u'salty', Vegetable, bacon.pk), + (u'shiny', Animal, platypus.pk) + ], + comp_func + ) + TaggedItem.objects.filter(tag='fatty').delete() + ctype = ContentType.objects.get_for_model(lion) + self.assertQuerysetEqual(Animal.objects.filter(tags__content_type=ctype), [ + "" + ]) + + + def test_multiple_gfk(self): + # Simple tests for multiple GenericForeignKeys + # only uses one model, since the above tests should be sufficient. + tiger = Animal.objects.create(common_name="tiger") + cheetah = Animal.objects.create(common_name="cheetah") + bear = Animal.objects.create(common_name="bear") + + # Create directly + Comparison.objects.create( + first_obj=cheetah, other_obj=tiger, comparative="faster" + ) + Comparison.objects.create( + first_obj=tiger, other_obj=cheetah, comparative="cooler" + ) + + # Create using GenericRelation + tiger.comparisons.create(other_obj=bear, comparative="cooler") + tiger.comparisons.create(other_obj=cheetah, comparative="stronger") + self.assertQuerysetEqual(cheetah.comparisons.all(), [ + "" + ]) + + # Filtering works + self.assertQuerysetEqual(tiger.comparisons.filter(comparative="cooler"), [ + "", + "" + ]) + + # Filtering and deleting works + subjective = ["cooler"] + tiger.comparisons.filter(comparative__in=subjective).delete() + self.assertQuerysetEqual(Comparison.objects.all(), [ + "", + "" + ]) + + # If we delete cheetah, Comparisons with cheetah as 'first_obj' will be + # deleted since Animal has an explicit GenericRelation to Comparison + # through first_obj. Comparisons with cheetah as 'other_obj' will not + # be deleted. + cheetah.delete() + self.assertQuerysetEqual(Comparison.objects.all(), [ + "" + ]) + + def test_gfk_subclasses(self): + # GenericForeignKey should work with subclasses (see #8309) + quartz = Mineral.objects.create(name="Quartz", hardness=7) + valuedtag = ValuableTaggedItem.objects.create( + content_object=quartz, tag="shiny", value=10 + ) + self.assertEqual(valuedtag.content_object, quartz) + + def test_generic_inline_formsets(self): + GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1) + formset = GenericFormSet() + self.assertEqual(u''.join(form.as_p() for form in formset.forms), u"""

        +

        """) + + formset = GenericFormSet(instance=Animal()) + self.assertEqual(u''.join(form.as_p() for form in formset.forms), u"""

        +

        """) + + platypus = Animal.objects.create( + common_name="Platypus", latin_name="Ornithorhynchus anatinus" + ) + platypus.tags.create(tag="shiny") + GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1) + formset = GenericFormSet(instance=platypus) + tagged_item_id = TaggedItem.objects.get( + tag='shiny', object_id=platypus.id + ).id + self.assertEqual(u''.join(form.as_p() for form in formset.forms), u"""

        +

        +

        """ % tagged_item_id) + + lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo") + formset = GenericFormSet(instance=lion, prefix='x') + self.assertEqual(u''.join(form.as_p() for form in formset.forms), u"""

        +

        """) From 7ddeb86cb6a872059311269baf9da412be753e84 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 15 Oct 2010 07:34:41 +0000 Subject: [PATCH 339/902] [1.2.X] Fixed #14473 -- converted the model_package tests from doctests to unitests. We have always been at war with doctests. Thanks to Gabriel Hurley for the patch. Backport of [14227]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14228 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/model_package/tests.py | 135 +++++++++++------------- 1 file changed, 63 insertions(+), 72 deletions(-) diff --git a/tests/modeltests/model_package/tests.py b/tests/modeltests/model_package/tests.py index 99970faba15c..e63e2e63eae9 100644 --- a/tests/modeltests/model_package/tests.py +++ b/tests/modeltests/model_package/tests.py @@ -1,81 +1,72 @@ +from django.contrib.sites.models import Site from django.db import models +from django.test import TestCase + +from models.publication import Publication +from models.article import Article + class Advertisment(models.Model): customer = models.CharField(max_length=100) - publications = models.ManyToManyField("model_package.Publication", null=True, blank=True) + publications = models.ManyToManyField( + "model_package.Publication", null=True, blank=True + ) class Meta: app_label = 'model_package' -__test__ = {'API_TESTS': """ ->>> from models.publication import Publication ->>> from models.article import Article - ->>> p = Publication(title="FooBar") ->>> p.save() ->>> p - - ->>> from django.contrib.sites.models import Site ->>> current_site = Site.objects.get_current() ->>> current_site - - -# Regression for #12168: models split into subpackages still get M2M tables - ->>> a = Article(headline="a foo headline") ->>> a.save() ->>> a.publications.add(p) ->>> a.sites.add(current_site) - ->>> a = Article.objects.get(id=1) ->>> a - ->>> a.id -1 ->>> a.sites.count() -1 - -# Regression for #12245 - Models can exist in the test package, too - ->>> ad = Advertisment(customer="Lawrence Journal-World") ->>> ad.save() ->>> ad.publications.add(p) - ->>> ad = Advertisment.objects.get(id=1) ->>> ad - - ->>> ad.publications.count() -1 - -# Regression for #12386 - field names on the autogenerated intermediate class -# that are specified as dotted strings don't retain any path component for the -# field or column name - ->>> Article.publications.through._meta.fields[1].name -'article' - ->>> Article.publications.through._meta.fields[1].get_attname_column() -('article_id', 'article_id') - ->>> Article.publications.through._meta.fields[2].name -'publication' - ->>> Article.publications.through._meta.fields[2].get_attname_column() -('publication_id', 'publication_id') - -# The oracle backend truncates the name to 'model_package_article_publ233f'. ->>> Article._meta.get_field('publications').m2m_db_table() \\ -... in ('model_package_article_publications', 'model_package_article_publ233f') -True - ->>> Article._meta.get_field('publications').m2m_column_name() -'article_id' - ->>> Article._meta.get_field('publications').m2m_reverse_name() -'publication_id' - -"""} - +class ModelPackageTests(TestCase): + def test_model_packages(self): + p = Publication.objects.create(title="FooBar") + + current_site = Site.objects.get_current() + self.assertEqual(current_site.domain, "example.com") + + # Regression for #12168: models split into subpackages still get M2M + # tables + a = Article.objects.create(headline="a foo headline") + a.publications.add(p) + a.sites.add(current_site) + + a = Article.objects.get(id=a.pk) + self.assertEqual(a.id, a.pk) + self.assertEqual(a.sites.count(), 1) + + # Regression for #12245 - Models can exist in the test package, too + ad = Advertisment.objects.create(customer="Lawrence Journal-World") + ad.publications.add(p) + + ad = Advertisment.objects.get(id=ad.pk) + self.assertEqual(ad.publications.count(), 1) + + # Regression for #12386 - field names on the autogenerated intermediate + # class that are specified as dotted strings don't retain any path + # component for the field or column name + self.assertEqual( + Article.publications.through._meta.fields[1].name, 'article' + ) + self.assertEqual( + Article.publications.through._meta.fields[1].get_attname_column(), + ('article_id', 'article_id') + ) + self.assertEqual( + Article.publications.through._meta.fields[2].name, 'publication' + ) + self.assertEqual( + Article.publications.through._meta.fields[2].get_attname_column(), + ('publication_id', 'publication_id') + ) + + # The oracle backend truncates the name to 'model_package_article_publ233f'. + self.assertTrue( + Article._meta.get_field('publications').m2m_db_table() in ('model_package_article_publications', 'model_package_article_publ233f') + ) + + self.assertEqual( + Article._meta.get_field('publications').m2m_column_name(), 'article_id' + ) + self.assertEqual( + Article._meta.get_field('publications').m2m_reverse_name(), + 'publication_id' + ) From d0a056bcff57c7c870d66ca7e827d54fece2899b Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Fri, 15 Oct 2010 09:31:59 +0000 Subject: [PATCH 340/902] [1.2.X] Fixed #14376 -- added docs for previously undocumented Brazilian localflavor fields. Thanks to henriquebastos for the report and patch. Backport of [14229] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14230 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/localflavor.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index 48cfa6340a9a..2eb731de3f3a 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -200,6 +200,23 @@ Brazil (``br``) A ``Select`` widget that uses a list of Brazilian states/territories as its choices. +.. class:: br.forms.BRCPFField + + A form field that validates input as `Brazilian CPF`_. + + Input can either be of the format XXX.XXX.XXX-VD or be a group of 11 digits. + +.. _Brazilian CPF: http://en.wikipedia.org/wiki/Cadastro_de_Pessoas_F%C3%ADsicas + +.. class:: br.forms.BRCNPJField + + A form field that validates input as `Brazilian CNPJ`_. + + Input can either be of the format XX.XXX.XXX/XXXX-XX or be a group of 14 + digits. + +.. _Brazilian CNPJ: http://en.wikipedia.org/wiki/National_identification_number#Brazil + Canada (``ca``) =============== From 560f58aba3be6a23698008600a87dc0bcd747fd7 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Fri, 15 Oct 2010 10:22:50 +0000 Subject: [PATCH 341/902] [1.2.X] Fixed #14307 -- Linked ChoiceField.choices docs to Field.choices docs to explain the formatting options available. Thanks to adamv for the report and Russ for the suggested fix. Backport of [14231] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14232 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/forms/fields.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index c005436a25fd..4171e60da62c 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -352,7 +352,11 @@ Takes one extra required argument: .. attribute:: ChoiceField.choices An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this - field. + field. This argument accepts the same formats as the ``choices`` argument + to a model field. See the `model field reference documentation on choices`_ + for more details. + + .. _model field reference documentation on choices: ../models/fields#choices ``TypedChoiceField`` ~~~~~~~~~~~~~~~~~~~~ From ac16c59d3509fe65574a0e10cef2b9051f5e6d57 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Fri, 15 Oct 2010 20:14:22 +0000 Subject: [PATCH 342/902] [1.2.X] Fixed #14307 -- Added a new crossref target to model field reference docs and fixed broken relative link in form field reference docs. Thanks to adamv for the report. Backport of [14234] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14235 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/forms/fields.txt | 6 ++---- docs/ref/models/fields.txt | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 4171e60da62c..cd32e10eed8f 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -353,10 +353,8 @@ Takes one extra required argument: An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field. This argument accepts the same formats as the ``choices`` argument - to a model field. See the `model field reference documentation on choices`_ - for more details. - - .. _model field reference documentation on choices: ../models/fields#choices + to a model field. See the :ref:`model field reference documentation on + choices ` for more details. ``TypedChoiceField`` ~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index e107f41454ed..02ed60833ff4 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -69,6 +69,8 @@ purely database-related, whereas :attr:`~Field.blank` is validation-related. If a field has ``blank=True``, validation on Django's admin site will allow entry of an empty value. If a field has ``blank=False``, the field will be required. +.. _field-choices: + ``choices`` ----------- From 386b6811050009337bf0eb11512c7bbf339805ff Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 16 Oct 2010 17:00:35 +0000 Subject: [PATCH 343/902] [1.2.X] Converted or_lookups tests from doctests to unittests. We have always been at war with doctests. Thanks to Paul Tax for the patch. Backport of [14236]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14237 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/or_lookups/models.py | 109 ------------ tests/modeltests/or_lookups/tests.py | 232 ++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 109 deletions(-) create mode 100644 tests/modeltests/or_lookups/tests.py diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py index 1179c6d2dc57..7f14ba50eb6e 100644 --- a/tests/modeltests/or_lookups/models.py +++ b/tests/modeltests/or_lookups/models.py @@ -20,112 +20,3 @@ class Meta: def __unicode__(self): return self.headline - -__test__ = {'API_TESTS':""" ->>> from datetime import datetime ->>> from django.db.models import Q - ->>> a1 = Article(headline='Hello', pub_date=datetime(2005, 11, 27)) ->>> a1.save() - ->>> a2 = Article(headline='Goodbye', pub_date=datetime(2005, 11, 28)) ->>> a2.save() - ->>> a3 = Article(headline='Hello and goodbye', pub_date=datetime(2005, 11, 29)) ->>> a3.save() - ->>> Article.objects.filter(headline__startswith='Hello') | Article.objects.filter(headline__startswith='Goodbye') -[, , ] - ->>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__startswith='Goodbye')) -[, , ] - ->>> Article.objects.filter(Q(headline__startswith='Hello') & Q(headline__startswith='Goodbye')) -[] - -# You can shorten this syntax with code like the following, -# which is especially useful if building the query in stages: ->>> articles = Article.objects.all() ->>> articles.filter(headline__startswith='Hello') & articles.filter(headline__startswith='Goodbye') -[] - ->>> articles.filter(headline__startswith='Hello') & articles.filter(headline__contains='bye') -[] - ->>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello') -[] - ->>> Article.objects.filter(headline__contains='Hello') | Article.objects.filter(headline__contains='bye') -[, , ] - ->>> Article.objects.filter(headline__iexact='Hello') | Article.objects.filter(headline__contains='ood') -[, , ] - ->>> Article.objects.filter(Q(pk=1) | Q(pk=2)) -[, ] - ->>> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3)) -[, , ] - -# You could also use "in" to accomplish the same as above. ->>> Article.objects.filter(pk__in=[1,2,3]) -[, , ] ->>> Article.objects.filter(pk__in=(1,2,3)) -[, , ] - ->>> Article.objects.filter(pk__in=[1,2,3,4]) -[, , ] - -# Passing "in" an empty list returns no results ... ->>> Article.objects.filter(pk__in=[]) -[] - -# ... but can return results if we OR it with another query. ->>> Article.objects.filter(Q(pk__in=[]) | Q(headline__icontains='goodbye')) -[, ] - -# Q arg objects are ANDed ->>> Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')) -[] - -# Q arg AND order is irrelevant ->>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello') -[] - -# Q objects can be negated ->>> Article.objects.filter(Q(pk=1) | ~Q(pk=2)) -[, ] ->>> Article.objects.filter(~Q(pk=1) & ~Q(pk=2)) -[] - -# This allows for more complex queries than filter() and exclude() alone would -# allow ->>> Article.objects.filter(Q(pk=1) & (~Q(pk=2) | Q(pk=3))) -[] - -# Try some arg queries with operations other than filter. ->>> Article.objects.get(Q(headline__startswith='Hello'), Q(headline__contains='bye')) - - ->>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__contains='bye')).count() -3 - ->>> dicts = list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values()) ->>> [sorted(d.items()) for d in dicts] -[[('headline', u'Hello and goodbye'), ('id', 3), ('pub_date', datetime.datetime(2005, 11, 29, 0, 0))]] - ->>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2]) -{1: } - -# Demonstrating exclude with a Q object ->>> Article.objects.exclude(Q(headline__startswith='Hello')) -[] - -# The 'complex_filter' method supports framework features such as -# 'limit_choices_to' which normally take a single dictionary of lookup arguments -# but need to support arbitrary queries via Q objects too. ->>> Article.objects.complex_filter({'pk': 1}) -[] ->>> Article.objects.complex_filter(Q(pk=1) | Q(pk=2)) -[, ] -"""} diff --git a/tests/modeltests/or_lookups/tests.py b/tests/modeltests/or_lookups/tests.py new file mode 100644 index 000000000000..ad218cd0b218 --- /dev/null +++ b/tests/modeltests/or_lookups/tests.py @@ -0,0 +1,232 @@ +from datetime import datetime +from operator import attrgetter + +from django.db.models import Q +from django.test import TestCase + +from models import Article + + +class OrLookupsTests(TestCase): + + def setUp(self): + self.a1 = Article.objects.create( + headline='Hello', pub_date=datetime(2005, 11, 27) + ).pk + self.a2 = Article.objects.create( + headline='Goodbye', pub_date=datetime(2005, 11, 28) + ).pk + self.a3 = Article.objects.create( + headline='Hello and goodbye', pub_date=datetime(2005, 11, 29) + ).pk + + def test_filter_or(self): + self.assertQuerysetEqual( + Article.objects.filter(headline__startswith='Hello') | Article.objects.filter(headline__startswith='Goodbye'), [ + 'Hello', + 'Goodbye', + 'Hello and goodbye' + ], + attrgetter("headline") + ) + + self.assertQuerysetEqual( + Article.objects.filter(headline__contains='Hello') | Article.objects.filter(headline__contains='bye'), [ + 'Hello', + 'Goodbye', + 'Hello and goodbye' + ], + attrgetter("headline") + ) + + self.assertQuerysetEqual( + Article.objects.filter(headline__iexact='Hello') | Article.objects.filter(headline__contains='ood'), [ + 'Hello', + 'Goodbye', + 'Hello and goodbye' + ], + attrgetter("headline") + ) + + self.assertQuerysetEqual( + Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__startswith='Goodbye')), [ + 'Hello', + 'Goodbye', + 'Hello and goodbye' + ], + attrgetter("headline") + ) + + + def test_stages(self): + # You can shorten this syntax with code like the following, which is + # especially useful if building the query in stages: + articles = Article.objects.all() + self.assertQuerysetEqual( + articles.filter(headline__startswith='Hello') & articles.filter(headline__startswith='Goodbye'), + [] + ) + self.assertQuerysetEqual( + articles.filter(headline__startswith='Hello') & articles.filter(headline__contains='bye'), [ + 'Hello and goodbye' + ], + attrgetter("headline") + ) + + def test_pk_q(self): + self.assertQuerysetEqual( + Article.objects.filter(Q(pk=self.a1) | Q(pk=self.a2)), [ + 'Hello', + 'Goodbye' + ], + attrgetter("headline") + ) + + self.assertQuerysetEqual( + Article.objects.filter(Q(pk=self.a1) | Q(pk=self.a2) | Q(pk=self.a3)), [ + 'Hello', + 'Goodbye', + 'Hello and goodbye' + ], + attrgetter("headline"), + ) + + def test_pk_in(self): + self.assertQuerysetEqual( + Article.objects.filter(pk__in=[self.a1, self.a2, self.a3]), [ + 'Hello', + 'Goodbye', + 'Hello and goodbye' + ], + attrgetter("headline"), + ) + + self.assertQuerysetEqual( + Article.objects.filter(pk__in=(self.a1, self.a2, self.a3)), [ + 'Hello', + 'Goodbye', + 'Hello and goodbye' + ], + attrgetter("headline"), + ) + + self.assertQuerysetEqual( + Article.objects.filter(pk__in=[self.a1, self.a2, self.a3, 40000]), [ + 'Hello', + 'Goodbye', + 'Hello and goodbye' + ], + attrgetter("headline"), + ) + + def test_q_negated(self): + # Q objects can be negated + self.assertQuerysetEqual( + Article.objects.filter(Q(pk=self.a1) | ~Q(pk=self.a2)), [ + 'Hello', + 'Hello and goodbye' + ], + attrgetter("headline") + ) + + self.assertQuerysetEqual( + Article.objects.filter(~Q(pk=self.a1) & ~Q(pk=self.a2)), [ + 'Hello and goodbye' + ], + attrgetter("headline"), + ) + # This allows for more complex queries than filter() and exclude() + # alone would allow + self.assertQuerysetEqual( + Article.objects.filter(Q(pk=self.a1) & (~Q(pk=self.a2) | Q(pk=self.a3))), [ + 'Hello' + ], + attrgetter("headline"), + ) + + def test_complex_filter(self): + # The 'complex_filter' method supports framework features such as + # 'limit_choices_to' which normally take a single dictionary of lookup + # arguments but need to support arbitrary queries via Q objects too. + self.assertQuerysetEqual( + Article.objects.complex_filter({'pk': self.a1}), [ + 'Hello' + ], + attrgetter("headline"), + ) + + self.assertQuerysetEqual( + Article.objects.complex_filter(Q(pk=self.a1) | Q(pk=self.a2)), [ + 'Hello', + 'Goodbye' + ], + attrgetter("headline"), + ) + + def test_empty_in(self): + # Passing "in" an empty list returns no results ... + self.assertQuerysetEqual( + Article.objects.filter(pk__in=[]), + [] + ) + # ... but can return results if we OR it with another query. + self.assertQuerysetEqual( + Article.objects.filter(Q(pk__in=[]) | Q(headline__icontains='goodbye')), [ + 'Goodbye', + 'Hello and goodbye' + ], + attrgetter("headline"), + ) + + def test_q_and(self): + # Q arg objects are ANDed + self.assertQuerysetEqual( + Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')), [ + 'Hello and goodbye' + ], + attrgetter("headline") + ) + # Q arg AND order is irrelevant + self.assertQuerysetEqual( + Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello'), [ + 'Hello and goodbye' + ], + attrgetter("headline"), + ) + + self.assertQuerysetEqual( + Article.objects.filter(Q(headline__startswith='Hello') & Q(headline__startswith='Goodbye')), + [] + ) + + def test_q_exclude(self): + self.assertQuerysetEqual( + Article.objects.exclude(Q(headline__startswith='Hello')), [ + 'Goodbye' + ], + attrgetter("headline") + ) + + def test_other_arg_queries(self): + # Try some arg queries with operations other than filter. + self.assertEqual( + Article.objects.get(Q(headline__startswith='Hello'), Q(headline__contains='bye')).headline, + 'Hello and goodbye' + ) + + self.assertEqual( + Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__contains='bye')).count(), + 3 + ) + + self.assertQuerysetEqual( + Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values(), [ + {"headline": "Hello and goodbye", "id": self.a3, "pub_date": datetime(2005, 11, 29)}, + ], + lambda o: o, + ) + + self.assertEqual( + Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([self.a1, self.a2]), + {self.a1: Article.objects.get(pk=self.a1)} + ) From 11713a8771221767f31718ef6406d276b435b9b0 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sat, 16 Oct 2010 20:51:06 +0000 Subject: [PATCH 344/902] [1.2.X] Fixed #14126 -- Fixed an issue with changes to the blocktrans tag introduced in r13973 related to multiple plural forms. Thanks, mark0978, svetlyak40wt and Ramiro. Backport from trunk (r14239). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14240 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/templatetags/i18n.py | 3 +-- tests/regressiontests/templates/tests.py | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index c4dd2e7835b3..c0e360b7518c 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -78,8 +78,7 @@ def render(self, context): context[self.countervar] = count plural, plural_vars = self.render_token_list(self.plural) result = translation.ungettext(singular, plural, count) - if count != 1: - vars = plural_vars + vars.extend(plural_vars) else: result = translation.ugettext(singular) # Escape all isolated '%' before substituting in the context. diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 9e2d175677d0..cea42224dd92 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -1119,6 +1119,9 @@ def get_template_tests(self): # translation of plural form with extra field in singular form (#13568) 'i18n26': ('{% load i18n %}{% blocktrans with myextra_field as extra_field count number as counter %}singular {{ extra_field }}{% plural %}plural{% endblocktrans %}', {'number': 1, 'myextra_field': 'test'}, "singular test"), + # translation of singular form in russian (#14126) + 'i18n27': ('{% load i18n %}{% blocktrans count number as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %}', {'number': 1, 'LANGUAGE_CODE': 'ru'}, u'1 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442'), + ### HANDLING OF TEMPLATE_STRING_IF_INVALID ################################### 'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')), From 2a33115040557ae2cc348317ca799e293648fff7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 17 Oct 2010 01:50:58 +0000 Subject: [PATCH 345/902] Corrected a suite of test failures when running under postgres. Backport of [14241]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14242 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/generic_relations/models.py | 2 +- tests/modeltests/generic_relations/tests.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py index 6161f9ebeaf5..18b77a355e35 100644 --- a/tests/modeltests/generic_relations/models.py +++ b/tests/modeltests/generic_relations/models.py @@ -23,7 +23,7 @@ class TaggedItem(models.Model): content_object = generic.GenericForeignKey() class Meta: - ordering = ["tag", "-object_id"] + ordering = ["tag", "content_type__name"] def __unicode__(self): return self.tag diff --git a/tests/modeltests/generic_relations/tests.py b/tests/modeltests/generic_relations/tests.py index e26db55ade5e..3d253010ffb2 100644 --- a/tests/modeltests/generic_relations/tests.py +++ b/tests/modeltests/generic_relations/tests.py @@ -88,8 +88,8 @@ def test_generic_relations(self): self.assertQuerysetEqual(TaggedItem.objects.all(), [ (u'clearish', Mineral, quartz.pk), - (u'fatty', Vegetable, bacon.pk), (u'fatty', Animal, platypus.pk), + (u'fatty', Vegetable, bacon.pk), (u'hairy', Animal, lion.pk), (u'salty', Vegetable, bacon.pk), (u'shiny', Animal, platypus.pk), @@ -100,8 +100,8 @@ def test_generic_relations(self): lion.delete() self.assertQuerysetEqual(TaggedItem.objects.all(), [ (u'clearish', Mineral, quartz.pk), - (u'fatty', Vegetable, bacon.pk), (u'fatty', Animal, platypus.pk), + (u'fatty', Vegetable, bacon.pk), (u'salty', Vegetable, bacon.pk), (u'shiny', Animal, platypus.pk) ], @@ -114,8 +114,8 @@ def test_generic_relations(self): quartz.delete() self.assertQuerysetEqual(TaggedItem.objects.all(), [ (u'clearish', Mineral, quartz_pk), - (u'fatty', Vegetable, bacon.pk), (u'fatty', Animal, platypus.pk), + (u'fatty', Vegetable, bacon.pk), (u'salty', Vegetable, bacon.pk), (u'shiny', Animal, platypus.pk) ], @@ -123,7 +123,7 @@ def test_generic_relations(self): ) # If you delete a tag, the objects using the tag are unaffected # (other than losing a tag) - tag = TaggedItem.objects.get(id=1) + tag = TaggedItem.objects.order_by("id")[0] tag.delete() self.assertQuerysetEqual(bacon.tags.all(), [""]) self.assertQuerysetEqual(TaggedItem.objects.all(), [ From 0e4737ec240f144dc072d17e99b12143d872e577 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 17 Oct 2010 15:40:41 +0000 Subject: [PATCH 346/902] [1.2.X] Fixed #13963 -- Use the correct verbose name of a reverse relation field in the admin. Thanks, sfllaw and d0ugal. Backport from trunk (r14244). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14245 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/util.py | 7 ++++++- tests/regressiontests/admin_util/models.py | 15 +++++++++++++-- tests/regressiontests/admin_util/tests.py | 19 ++++++++++++++++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py index 776a6f0f42c8..792d0b689960 100644 --- a/django/contrib/admin/util.py +++ b/django/contrib/admin/util.py @@ -1,5 +1,6 @@ from django.core.exceptions import ObjectDoesNotExist from django.db import models +from django.db.models.related import RelatedObject from django.forms.forms import pretty_name from django.utils import formats from django.utils.html import escape @@ -279,7 +280,11 @@ def lookup_field(name, obj, model_admin=None): def label_for_field(name, model, model_admin=None, return_attr=False): attr = None try: - label = model._meta.get_field_by_name(name)[0].verbose_name + field = model._meta.get_field_by_name(name)[0] + if isinstance(field, RelatedObject): + label = field.opts.verbose_name + else: + label = field.verbose_name except models.FieldDoesNotExist: if name == "__unicode__": label = force_unicode(model._meta.verbose_name) diff --git a/tests/regressiontests/admin_util/models.py b/tests/regressiontests/admin_util/models.py index 493e1271adee..3191a55a2b14 100644 --- a/tests/regressiontests/admin_util/models.py +++ b/tests/regressiontests/admin_util/models.py @@ -1,7 +1,5 @@ from django.db import models - - class Article(models.Model): """ A simple Article model for testing @@ -20,3 +18,16 @@ def test_from_model_with_override(self): class Count(models.Model): num = models.PositiveSmallIntegerField() + +class Event(models.Model): + date = models.DateTimeField(auto_now_add=True) + +class Location(models.Model): + event = models.OneToOneField(Event, verbose_name='awesome event') + +class Guest(models.Model): + event = models.OneToOneField(Event) + name = models.CharField(max_length=255) + + class Meta: + verbose_name = "awesome guest" diff --git a/tests/regressiontests/admin_util/tests.py b/tests/regressiontests/admin_util/tests.py index 5ea0ac585ef6..7476d10f28ba 100644 --- a/tests/regressiontests/admin_util/tests.py +++ b/tests/regressiontests/admin_util/tests.py @@ -12,7 +12,7 @@ from django.contrib.sites.models import Site from django.contrib.admin.util import NestedObjects -from models import Article, Count +from models import Article, Count, Event, Location class NestedObjectsTests(TestCase): @@ -220,3 +220,20 @@ def test_from_model(self, obj): ), ("not Really the Model", MockModelAdmin.test_from_model) ) + + def test_related_name(self): + """ + Regression test for #13963 + """ + self.assertEquals( + label_for_field('location', Event, return_attr=True), + ('location', None), + ) + self.assertEquals( + label_for_field('event', Location, return_attr=True), + ('awesome event', None), + ) + self.assertEquals( + label_for_field('guest', Event, return_attr=True), + ('awesome guest', None), + ) From 630b1fc09b6a1e521b64bc7f8792fd0807d22c07 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 17 Oct 2010 15:41:00 +0000 Subject: [PATCH 347/902] [1.2.X] Removed stray code introduced in r14126 that broke the test suite. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14246 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/makemessages/tests.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/regressiontests/makemessages/tests.py b/tests/regressiontests/makemessages/tests.py index c3be2178e4bb..5798e671b035 100644 --- a/tests/regressiontests/makemessages/tests.py +++ b/tests/regressiontests/makemessages/tests.py @@ -38,6 +38,3 @@ def find_command(cmd, path=None, pathext=None): if xversion >= (0, 15): from extraction import * del p - -if find_command('msgfmt'): - from compilation import * From 55d8c47d29ce24428fbfd425a5ae6030bbeaf09f Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Sun, 17 Oct 2010 16:09:07 +0000 Subject: [PATCH 348/902] [1.2.X] Fixed a few other backporting-related bugs introduced in r14213. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14247 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/inline_formsets/tests.py | 13 ++++++++++--- tests/regressiontests/m2m_regress/tests.py | 11 +++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/regressiontests/inline_formsets/tests.py b/tests/regressiontests/inline_formsets/tests.py index 24488661c73a..73bbeebbf7bd 100644 --- a/tests/regressiontests/inline_formsets/tests.py +++ b/tests/regressiontests/inline_formsets/tests.py @@ -107,6 +107,13 @@ def test_save_new(self): class InlineFormsetFactoryTest(TestCase): + def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs): + self.assertRaises(error, callable, *args, **kwargs) + try: + callable(*args, **kwargs) + except error, e: + self.assertEqual(message, str(e)) + def test_inline_formset_factory(self): """ These should both work without a problem. @@ -119,7 +126,7 @@ def test_exception_on_unspecified_foreign_key(self): Child has two ForeignKeys to Parent, so if we don't specify which one to use for the inline formset, we should get an exception. """ - self.assertRaisesRegexp(Exception, + self.assertRaisesErrorWithMessage(Exception, " has more than 1 ForeignKey to ", inlineformset_factory, Parent, Child ) @@ -129,7 +136,7 @@ def test_fk_name_not_foreign_key_field_from_child(self): If we specify fk_name, but it isn't a ForeignKey from the child model to the parent model, we should get an exception. """ - self.assertRaises(Exception, + self.assertRaisesErrorWithMessage(Exception, "fk_name 'school' is not a ForeignKey to ", inlineformset_factory, Parent, Child, fk_name='school' ) @@ -139,7 +146,7 @@ def test_non_foreign_key_field(self): If the field specified in fk_name is not a ForeignKey, we should get an exception. """ - self.assertRaisesRegexp(Exception, + self.assertRaisesErrorWithMessage(Exception, " has no field named 'test'", inlineformset_factory, Parent, Child, fk_name='test' ) diff --git a/tests/regressiontests/m2m_regress/tests.py b/tests/regressiontests/m2m_regress/tests.py index 7bf2381a91a7..7e5e5c3270c4 100644 --- a/tests/regressiontests/m2m_regress/tests.py +++ b/tests/regressiontests/m2m_regress/tests.py @@ -6,6 +6,13 @@ class M2MRegressionTests(TestCase): + def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs): + self.assertRaises(error, callable, *args, **kwargs) + try: + callable(*args, **kwargs) + except error, e: + self.assertEqual(message, str(e)) + def test_multiple_m2m(self): # Multiple m2m references to model must be distinguished when # accessing the relations through an instance attribute. @@ -33,8 +40,8 @@ def test_internal_related_name_not_in_error_msg(self): # The secret internal related names for self-referential many-to-many # fields shouldn't appear in the list when an error is made. - self.assertRaisesRegexp(FieldError, - "Choices are: id, name, references, related, selfreferchild, selfreferchildsibling$", + self.assertRaisesErrorWithMessage(FieldError, + "Cannot resolve keyword 'porcupine' into field. Choices are: id, name, references, related, selfreferchild, selfreferchildsibling", lambda: SelfRefer.objects.filter(porcupine='fred') ) From efcb7776a0209447cc8ac3eb12760c197eeed7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Kr=C3=A1l?= Date: Mon, 18 Oct 2010 05:02:28 +0000 Subject: [PATCH 349/902] [1.2.X] Fixed #13790 -- auto detection of m2m fields to Site. Thanks, gabrielhurley! Backport from trunk (r14251). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14252 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/sites/managers.py | 37 +++++++++++++++---- .../sites_framework/__init__.py | 0 .../regressiontests/sites_framework/models.py | 36 ++++++++++++++++++ .../regressiontests/sites_framework/tests.py | 34 +++++++++++++++++ 4 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 tests/regressiontests/sites_framework/__init__.py create mode 100644 tests/regressiontests/sites_framework/models.py create mode 100644 tests/regressiontests/sites_framework/tests.py diff --git a/django/contrib/sites/managers.py b/django/contrib/sites/managers.py index 59215c44f92f..3df485a04008 100644 --- a/django/contrib/sites/managers.py +++ b/django/contrib/sites/managers.py @@ -4,17 +4,38 @@ class CurrentSiteManager(models.Manager): "Use this to limit objects to those associated with the current site." - def __init__(self, field_name='site'): + def __init__(self, field_name=None): super(CurrentSiteManager, self).__init__() self.__field_name = field_name self.__is_validated = False - + + def _validate_field_name(self): + field_names = self.model._meta.get_all_field_names() + + # If a custom name is provided, make sure the field exists on the model + if self.__field_name is not None and self.__field_name not in field_names: + raise ValueError("%s couldn't find a field named %s in %s." % \ + (self.__class__.__name__, self.__field_name, self.model._meta.object_name)) + + # Otherwise, see if there is a field called either 'site' or 'sites' + else: + for potential_name in ['site', 'sites']: + if potential_name in field_names: + self.__field_name = potential_name + self.__is_validated = True + break + + # Now do a type check on the field (FK or M2M only) + try: + field = self.model._meta.get_field(self.__field_name) + if not isinstance(field, (models.ForeignKey, models.ManyToManyField)): + raise TypeError("%s must be a ForeignKey or ManyToManyField." %self.__field_name) + except FieldDoesNotExist: + raise ValueError("%s couldn't find a field named %s in %s." % \ + (self.__class__.__name__, self.__field_name, self.model._meta.object_name)) + self.__is_validated = True + def get_query_set(self): if not self.__is_validated: - try: - self.model._meta.get_field(self.__field_name) - except FieldDoesNotExist: - raise ValueError("%s couldn't find a field named %s in %s." % \ - (self.__class__.__name__, self.__field_name, self.model._meta.object_name)) - self.__is_validated = True + self._validate_field_name() return super(CurrentSiteManager, self).get_query_set().filter(**{self.__field_name + '__id__exact': settings.SITE_ID}) diff --git a/tests/regressiontests/sites_framework/__init__.py b/tests/regressiontests/sites_framework/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/regressiontests/sites_framework/models.py b/tests/regressiontests/sites_framework/models.py new file mode 100644 index 000000000000..9ecc3e666007 --- /dev/null +++ b/tests/regressiontests/sites_framework/models.py @@ -0,0 +1,36 @@ +from django.contrib.sites.managers import CurrentSiteManager +from django.contrib.sites.models import Site +from django.db import models + +class AbstractArticle(models.Model): + title = models.CharField(max_length=50) + + objects = models.Manager() + on_site = CurrentSiteManager() + + class Meta: + abstract = True + + def __unicode__(self): + return self.title + +class SyndicatedArticle(AbstractArticle): + sites = models.ManyToManyField(Site) + +class ExclusiveArticle(AbstractArticle): + site = models.ForeignKey(Site) + +class CustomArticle(AbstractArticle): + places_this_article_should_appear = models.ForeignKey(Site) + + objects = models.Manager() + on_site = CurrentSiteManager("places_this_article_should_appear") + +class InvalidArticle(AbstractArticle): + site = models.ForeignKey(Site) + + objects = models.Manager() + on_site = CurrentSiteManager("places_this_article_should_appear") + +class ConfusedArticle(AbstractArticle): + site = models.IntegerField() diff --git a/tests/regressiontests/sites_framework/tests.py b/tests/regressiontests/sites_framework/tests.py new file mode 100644 index 000000000000..b737727a564f --- /dev/null +++ b/tests/regressiontests/sites_framework/tests.py @@ -0,0 +1,34 @@ +from django.conf import settings +from django.contrib.sites.models import Site +from django.test import TestCase + +from models import SyndicatedArticle, ExclusiveArticle, CustomArticle, InvalidArticle, ConfusedArticle + +class SitesFrameworkTestCase(TestCase): + def setUp(self): + Site.objects.get_or_create(id=settings.SITE_ID, domain="example.com", name="example.com") + Site.objects.create(id=settings.SITE_ID+1, domain="example2.com", name="example2.com") + + def test_site_fk(self): + article = ExclusiveArticle.objects.create(title="Breaking News!", site_id=settings.SITE_ID) + self.assertEqual(ExclusiveArticle.on_site.all().get(), article) + + def test_sites_m2m(self): + article = SyndicatedArticle.objects.create(title="Fresh News!") + article.sites.add(Site.objects.get(id=settings.SITE_ID)) + article.sites.add(Site.objects.get(id=settings.SITE_ID+1)) + article2 = SyndicatedArticle.objects.create(title="More News!") + article2.sites.add(Site.objects.get(id=settings.SITE_ID+1)) + self.assertEqual(SyndicatedArticle.on_site.all().get(), article) + + def test_custom_named_field(self): + article = CustomArticle.objects.create(title="Tantalizing News!", places_this_article_should_appear_id=settings.SITE_ID) + self.assertEqual(CustomArticle.on_site.all().get(), article) + + def test_invalid_name(self): + article = InvalidArticle.objects.create(title="Bad News!", site_id=settings.SITE_ID) + self.assertRaises(ValueError, InvalidArticle.on_site.all) + + def test_invalid_field_type(self): + article = ConfusedArticle.objects.create(title="More Bad News!", site=settings.SITE_ID) + self.assertRaises(TypeError, ConfusedArticle.on_site.all) From 9dfdcf86befecad3b638df03273719c5ada90a45 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Tue, 19 Oct 2010 00:11:51 +0000 Subject: [PATCH 350/902] [1.2.X] Fixed #14426 -- Removed "mysite" import statements from examples that might teach people "bad habits" in regards to creating reusable apps. Thanks to idahogray for assisting with the patch (and sorry for forgetting the attribution in the patch on trunk). Backport of [14270] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14271 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/overview.txt | 14 ++++++++------ docs/ref/contrib/formtools/form-wizard.txt | 2 +- docs/ref/contrib/sitemaps.txt | 4 ++-- docs/topics/db/models.txt | 2 +- docs/topics/db/queries.txt | 6 +++--- docs/topics/generic-views.txt | 14 +++++++------- docs/topics/http/urls.txt | 10 +++++----- 7 files changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 0c47e59e14d4..34572a6c806e 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -21,7 +21,8 @@ code. The :doc:`data-model syntax ` offers many rich ways of representing your models -- so far, it's been solving two years' worth of -database-schema problems. Here's a quick example:: +database-schema problems. Here's a quick example, which might be saved in +the file ``mysite/news/models.py``:: class Reporter(models.Model): full_name = models.CharField(max_length=70) @@ -57,7 +58,8 @@ Enjoy the free API With that, you've got a free, and rich, :doc:`Python API ` to access your data. The API is created on the fly, no code generation necessary:: - >>> from mysite.models import Reporter, Article + # Import the models we created from our "news" app + >>> from news.models import Reporter, Article # No reporters are in the system yet. >>> Reporter.objects.all() @@ -177,9 +179,9 @@ example above:: from django.conf.urls.defaults import * urlpatterns = patterns('', - (r'^articles/(\d{4})/$', 'mysite.views.year_archive'), - (r'^articles/(\d{4})/(\d{2})/$', 'mysite.views.month_archive'), - (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'mysite.views.article_detail'), + (r'^articles/(\d{4})/$', 'news.views.year_archive'), + (r'^articles/(\d{4})/(\d{2})/$', 'news.views.month_archive'), + (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'news.views.article_detail'), ) The code above maps URLs, as simple regular expressions, to the location of @@ -195,7 +197,7 @@ is a simple Python function. Each view gets passed a request object -- which contains request metadata -- and the values captured in the regex. For example, if a user requested the URL "/articles/2005/05/39323/", Django -would call the function ``mysite.views.article_detail(request, +would call the function ``news.views.article_detail(request, '2005', '05', '39323')``. Write your views diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt index ab7b4829c9fe..390d575cd3cc 100644 --- a/docs/ref/contrib/formtools/form-wizard.txt +++ b/docs/ref/contrib/formtools/form-wizard.txt @@ -193,7 +193,7 @@ wizard takes a list of your :class:`~django.forms.Form` objects as arguments when you instantiate the Wizard:: from django.conf.urls.defaults import * - from mysite.testapp.forms import ContactForm1, ContactForm2, ContactWizard + from testapp.forms import ContactForm1, ContactForm2, ContactWizard urlpatterns = patterns('', (r'^contact/$', ContactWizard([ContactForm1, ContactForm2])), diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index e8bfcbc60bb3..db80b0c9084c 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -95,7 +95,7 @@ sitemap to include all the links to your individual blog entries. Here's how your sitemap class might look:: from django.contrib.sitemaps import Sitemap - from mysite.blog.models import Entry + from blog.models import Entry class BlogSitemap(Sitemap): changefreq = "never" @@ -242,7 +242,7 @@ Here's an example of a :doc:`URLconf ` using both:: from django.conf.urls.defaults import * from django.contrib.sitemaps import FlatPageSitemap, GenericSitemap - from mysite.blog.models import Entry + from blog.models import Entry info_dict = { 'queryset': Entry.objects.all(), diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 0ff34ea0e12c..22870535533c 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -570,7 +570,7 @@ It's perfectly OK to relate a model to one from another app. To do this, import the related model at the top of the model that holds your model. Then, just refer to the other model class wherever needed. For example:: - from mysite.geography.models import ZipCode + from geography.models import ZipCode class Restaurant(models.Model): # ... diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index e8966807b127..0ee417d86c35 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -60,7 +60,7 @@ funky model importing.) Assuming models live in a file ``mysite/blog/models.py``, here's an example:: - >>> from mysite.blog.models import Blog + >>> from blog.models import Blog >>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') >>> b.save() @@ -98,7 +98,7 @@ Updating a ``ForeignKey`` field works exactly the same way as saving a normal field; simply assign an object of the right type to the field in question. This example updates the ``blog`` attribute of an ``Entry`` instance ``entry``:: - >>> from mysite.blog.models import Entry + >>> from blog.models import Entry >>> entry = Entry.objects.get(pk=1) >>> cheese_blog = Blog.objects.get(name="Cheddar Talk") >>> entry.blog = cheese_blog @@ -108,7 +108,7 @@ Updating a ``ManyToManyField`` works a little differently; use the ``add()`` method on the field to add a record to the relation. This example adds the ``Author`` instance ``joe`` to the ``entry`` object:: - >>> from mysite.blog.models import Author + >>> from blog.models import Author >>> joe = Author.objects.create(name="Joe") >>> entry.authors.add(joe) diff --git a/docs/topics/generic-views.txt b/docs/topics/generic-views.txt index f90745d45138..41e32c87aaba 100644 --- a/docs/topics/generic-views.txt +++ b/docs/topics/generic-views.txt @@ -72,7 +72,7 @@ the URLconf to point to a view function: from django.conf.urls.defaults import * from django.views.generic.simple import direct_to_template - **from mysite.books.views import about_pages** + **from books.views import about_pages** urlpatterns = patterns('', ('^about/$', direct_to_template, { @@ -152,7 +152,7 @@ To build a list page of all publishers, we'd use a URLconf along these lines:: from django.conf.urls.defaults import * from django.views.generic import list_detail - from mysite.books.models import Publisher + from books.models import Publisher publisher_info = { "queryset" : Publisher.objects.all(), @@ -251,7 +251,7 @@ detail view, we'd use an info dict like this: .. parsed-literal:: - from mysite.books.models import Publisher, **Book** + from books.models import Publisher, **Book** publisher_info = { "queryset" : Publisher.objects.all(), @@ -376,7 +376,7 @@ of code by hand. As usual, we'll start by writing a URLconf: .. parsed-literal:: - from mysite.books.views import books_by_publisher + from books.views import books_by_publisher urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info), @@ -387,7 +387,7 @@ Next, we'll write the ``books_by_publisher`` view itself:: from django.http import Http404 from django.views.generic import list_detail - from mysite.books.models import Book, Publisher + from books.models import Book, Publisher def books_by_publisher(request, name): @@ -447,7 +447,7 @@ custom view: .. parsed-literal:: - from mysite.books.views import author_detail + from books.views import author_detail urlpatterns = patterns('', #... @@ -457,7 +457,7 @@ custom view: Then we'd write our wrapper function:: import datetime - from mysite.books.models import Author + from books.models import Author from django.views.generic import list_detail from django.shortcuts import get_object_or_404 diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 1a152ade5d76..d176bbafaed9 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -338,12 +338,12 @@ Here's the example URLconf from the :doc:`Django overview `:: from django.conf.urls.defaults import * urlpatterns = patterns('', - (r'^articles/(\d{4})/$', 'mysite.news.views.year_archive'), - (r'^articles/(\d{4})/(\d{2})/$', 'mysite.news.views.month_archive'), - (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'mysite.news.views.article_detail'), + (r'^articles/(\d{4})/$', 'news.views.year_archive'), + (r'^articles/(\d{4})/(\d{2})/$', 'news.views.month_archive'), + (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'news.views.article_detail'), ) -In this example, each view has a common prefix -- ``'mysite.news.views'``. +In this example, each view has a common prefix -- ``'news.views'``. Instead of typing that out for each entry in ``urlpatterns``, you can use the first argument to the ``patterns()`` function to specify a prefix to apply to each view function. @@ -352,7 +352,7 @@ With this in mind, the above example can be written more concisely as:: from django.conf.urls.defaults import * - urlpatterns = patterns('mysite.news.views', + urlpatterns = patterns('news.views', (r'^articles/(\d{4})/$', 'year_archive'), (r'^articles/(\d{4})/(\d{2})/$', 'month_archive'), (r'^articles/(\d{4})/(\d{2})/(\d+)/$', 'article_detail'), From 4ec58f702b3f6268d011e1942266af9d7ba664fa Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Tue, 19 Oct 2010 00:28:00 +0000 Subject: [PATCH 351/902] [1.2.X] Fixed #14464 -- Strengthened the admonition regarding documentation versions at the end of the install docs. Thanks to PaulM for the report. Backport of [14274] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14275 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/install.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/intro/install.txt b/docs/intro/install.txt index 95728c75fc3f..327686fd2115 100644 --- a/docs/intro/install.txt +++ b/docs/intro/install.txt @@ -67,7 +67,8 @@ You've got three easy options to install Django: `. This is best for users who want the latest-and-greatest features and aren't afraid of running brand-new code. -.. warning:: +.. admonition:: Always refer to the documentation that corresponds to the + version of Django you're using! If you do either of the first two steps, keep an eye out for parts of the documentation marked **new in development version**. That phrase flags From da17c2b84fda02aa1f1615f04e6d681cea9adcb4 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Tue, 19 Oct 2010 00:58:34 +0000 Subject: [PATCH 352/902] [1.2.X] Fixed #7616 -- Added advice on unix socket permissions and umasks to fastcgi deployment documentation. Thanks to Malcolm Tredinnick for the report and advice, and PaulM and cramm for reviewing the patch. Backport of [14276] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14277 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/deployment/fastcgi.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/howto/deployment/fastcgi.txt b/docs/howto/deployment/fastcgi.txt index a445a2d1a7c7..3bf231fb1999 100644 --- a/docs/howto/deployment/fastcgi.txt +++ b/docs/howto/deployment/fastcgi.txt @@ -111,6 +111,14 @@ Running a threaded server on a TCP port:: Running a preforked server on a Unix domain socket:: ./manage.py runfcgi method=prefork socket=/home/user/mysite.sock pidfile=django.pid + +.. admonition:: Socket security + + Django's default umask requires that the webserver and the Django fastcgi + process be run with the same group **and** user. For increased security, + you can run them under the same group but as different users. If you do + this, you will need to set the umask to 0002 using the ``umask`` argument + to ``runfcgi``. Run without daemonizing (backgrounding) the process (good for debugging):: From 9bcdb620cbd7e2cda4b09b194b9ec7b3497c134e Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Tue, 19 Oct 2010 04:17:29 +0000 Subject: [PATCH 353/902] [1.2.X] Converted queries tests from doctests to unittests. Thanks Russell and Alex for reviews and suggestions. Backport of [14279] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14280 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/queries/models.py | 1093 +---------------- tests/regressiontests/queries/tests.py | 1468 ++++++++++++++++++++++- 2 files changed, 1468 insertions(+), 1093 deletions(-) diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index f22cfb3af345..5247ef90cef5 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -2,15 +2,9 @@ Various complex queries that have been problematic in the past. """ -import datetime -import pickle -import sys import threading -from django.conf import settings -from django.db import models, DEFAULT_DB_ALIAS -from django.db.models import Count -from django.db.models.query import Q, ITER_CHUNK_SIZE, EmptyQuerySet +from django.db import models class DumbCategory(models.Model): pass @@ -277,1085 +271,6 @@ class Meta: def __unicode__(self): return self.name - -__test__ = {'API_TESTS':""" ->>> # Regression for #13156 -- exists() queries have minimal SQL ->>> from django.db import connection ->>> settings.DEBUG = True ->>> Tag.objects.exists() -False ->>> # Ok - so the exist query worked - but did it include too many columns? ->>> "id" not in connection.queries[-1]['sql'] and "name" not in connection.queries[-1]['sql'] -True ->>> settings.DEBUG = False - ->>> generic = NamedCategory.objects.create(name="Generic") ->>> t1 = Tag.objects.create(name='t1', category=generic) ->>> t2 = Tag.objects.create(name='t2', parent=t1, category=generic) ->>> t3 = Tag.objects.create(name='t3', parent=t1) ->>> t4 = Tag.objects.create(name='t4', parent=t3) ->>> t5 = Tag.objects.create(name='t5', parent=t3) - ->>> n1 = Note.objects.create(note='n1', misc='foo', id=1) ->>> n2 = Note.objects.create(note='n2', misc='bar', id=2) ->>> n3 = Note.objects.create(note='n3', misc='foo', id=3) - ->>> ann1 = Annotation.objects.create(name='a1', tag=t1) ->>> ann1.notes.add(n1) ->>> ann2 = Annotation.objects.create(name='a2', tag=t4) ->>> ann2.notes.add(n2, n3) - -Create these out of order so that sorting by 'id' will be different to sorting -by 'info'. Helps detect some problems later. ->>> e2 = ExtraInfo.objects.create(info='e2', note=n2) ->>> e1 = ExtraInfo.objects.create(info='e1', note=n1) - ->>> a1 = Author.objects.create(name='a1', num=1001, extra=e1) ->>> a2 = Author.objects.create(name='a2', num=2002, extra=e1) ->>> a3 = Author.objects.create(name='a3', num=3003, extra=e2) ->>> a4 = Author.objects.create(name='a4', num=4004, extra=e2) - ->>> time1 = datetime.datetime(2007, 12, 19, 22, 25, 0) ->>> time2 = datetime.datetime(2007, 12, 19, 21, 0, 0) ->>> time3 = datetime.datetime(2007, 12, 20, 22, 25, 0) ->>> time4 = datetime.datetime(2007, 12, 20, 21, 0, 0) ->>> i1 = Item.objects.create(name='one', created=time1, modified=time1, creator=a1, note=n3) ->>> i1.tags = [t1, t2] ->>> i2 = Item.objects.create(name='two', created=time2, creator=a2, note=n2) ->>> i2.tags = [t1, t3] ->>> i3 = Item.objects.create(name='three', created=time3, creator=a2, note=n3) ->>> i4 = Item.objects.create(name='four', created=time4, creator=a4, note=n3) ->>> i4.tags = [t4] - ->>> r1 = Report.objects.create(name='r1', creator=a1) ->>> r2 = Report.objects.create(name='r2', creator=a3) ->>> r3 = Report.objects.create(name='r3') - -Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering -will be rank3, rank2, rank1. ->>> rank1 = Ranking.objects.create(rank=2, author=a2) ->>> rank2 = Ranking.objects.create(rank=1, author=a3) ->>> rank3 = Ranking.objects.create(rank=3, author=a1) - ->>> c1 = Cover.objects.create(title="first", item=i4) ->>> c2 = Cover.objects.create(title="second", item=i2) - ->>> num1 = Number.objects.create(num=4) ->>> num2 = Number.objects.create(num=8) ->>> num3 = Number.objects.create(num=12) - -Bug #1050 ->>> Item.objects.filter(tags__isnull=True) -[] ->>> Item.objects.filter(tags__id__isnull=True) -[] - -Bug #1801 ->>> Author.objects.filter(item=i2) -[] ->>> Author.objects.filter(item=i3) -[] ->>> Author.objects.filter(item=i2) & Author.objects.filter(item=i3) -[] - -Bug #2306 -Checking that no join types are "left outer" joins. ->>> query = Item.objects.filter(tags=t2).query ->>> query.LOUTER not in [x[2] for x in query.alias_map.values()] -True - ->>> Item.objects.filter(Q(tags=t1)).order_by('name') -[, ] ->>> Item.objects.filter(Q(tags=t1)).filter(Q(tags=t2)) -[] ->>> Item.objects.filter(Q(tags=t1)).filter(Q(creator__name='fred')|Q(tags=t2)) -[] - -Each filter call is processed "at once" against a single table, so this is -different from the previous example as it tries to find tags that are two -things at once (rather than two tags). ->>> Item.objects.filter(Q(tags=t1) & Q(tags=t2)) -[] ->>> Item.objects.filter(Q(tags=t1), Q(creator__name='fred')|Q(tags=t2)) -[] - ->>> qs = Author.objects.filter(ranking__rank=2, ranking__id=rank1.id) ->>> list(qs) -[] ->>> qs.query.count_active_tables() -2 ->>> qs = Author.objects.filter(ranking__rank=2).filter(ranking__id=rank1.id) ->>> qs.query.count_active_tables() -3 - -Bug #4464 ->>> Item.objects.filter(tags=t1).filter(tags=t2) -[] ->>> Item.objects.filter(tags__in=[t1, t2]).distinct().order_by('name') -[, ] ->>> Item.objects.filter(tags__in=[t1, t2]).filter(tags=t3) -[] - -Make sure .distinct() works with slicing (this was broken in Oracle). ->>> Item.objects.filter(tags__in=[t1, t2]).order_by('name')[:3] -[, , ] ->>> Item.objects.filter(tags__in=[t1, t2]).distinct().order_by('name')[:3] -[, ] - -Bug #2080, #3592 ->>> Author.objects.filter(item__name='one') | Author.objects.filter(name='a3') -[, ] ->>> Author.objects.filter(Q(item__name='one') | Q(name='a3')) -[, ] ->>> Author.objects.filter(Q(name='a3') | Q(item__name='one')) -[, ] ->>> Author.objects.filter(Q(item__name='three') | Q(report__name='r3')) -[] - -Bug #4289 -A slight variation on the above theme: restricting the choices by the lookup -constraints. ->>> Number.objects.filter(num__lt=4) -[] ->>> Number.objects.filter(num__gt=8, num__lt=12) -[] ->>> Number.objects.filter(num__gt=8, num__lt=13) -[] ->>> Number.objects.filter(Q(num__lt=4) | Q(num__gt=8, num__lt=12)) -[] ->>> Number.objects.filter(Q(num__gt=8, num__lt=12) | Q(num__lt=4)) -[] ->>> Number.objects.filter(Q(num__gt=8) & Q(num__lt=12) | Q(num__lt=4)) -[] ->>> Number.objects.filter(Q(num__gt=7) & Q(num__lt=12) | Q(num__lt=4)) -[] - -Bug #12239 -Float was being rounded to integer on gte queries on integer field. Tests -show that gt, lt, gte, and lte work as desired. Note that the fix changes -get_prep_lookup for gte and lt queries only. ->>> Number.objects.filter(num__gt=11.9) -[] ->>> Number.objects.filter(num__gt=12) -[] ->>> Number.objects.filter(num__gt=12.0) -[] ->>> Number.objects.filter(num__gt=12.1) -[] ->>> Number.objects.filter(num__lt=12) -[, ] ->>> Number.objects.filter(num__lt=12.0) -[, ] ->>> Number.objects.filter(num__lt=12.1) -[, , ] ->>> Number.objects.filter(num__gte=11.9) -[] ->>> Number.objects.filter(num__gte=12) -[] ->>> Number.objects.filter(num__gte=12.0) -[] ->>> Number.objects.filter(num__gte=12.1) -[] ->>> Number.objects.filter(num__gte=12.9) -[] ->>> Number.objects.filter(num__lte=11.9) -[, ] ->>> Number.objects.filter(num__lte=12) -[, , ] ->>> Number.objects.filter(num__lte=12.0) -[, , ] ->>> Number.objects.filter(num__lte=12.1) -[, , ] ->>> Number.objects.filter(num__lte=12.9) -[, , ] - -Bug #7872 -Another variation on the disjunctive filtering theme. - -# For the purposes of this regression test, it's important that there is no -# Join object releated to the LeafA we create. ->>> LeafA.objects.create(data='first') - ->>> LeafA.objects.filter(Q(data='first')|Q(join__b__data='second')) -[] - -Bug #6074 -Merging two empty result sets shouldn't leave a queryset with no constraints -(which would match everything). ->>> Author.objects.filter(Q(id__in=[])) -[] ->>> Author.objects.filter(Q(id__in=[])|Q(id__in=[])) -[] - -Bug #1878, #2939 ->>> Item.objects.values('creator').distinct().count() -3 - -# Create something with a duplicate 'name' so that we can test multi-column -# cases (which require some tricky SQL transformations under the covers). ->>> xx = Item(name='four', created=time1, creator=a2, note=n1) ->>> xx.save() ->>> Item.objects.exclude(name='two').values('creator', 'name').distinct().count() -4 ->>> Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name', 'foo').distinct().count() -4 ->>> Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name').distinct().count() -4 ->>> xx.delete() - -Bug #7323 ->>> Item.objects.values('creator', 'name').count() -4 - -Bug #2253 ->>> q1 = Item.objects.order_by('name') ->>> q2 = Item.objects.filter(id=i1.id) ->>> q1 -[, , , ] ->>> q2 -[] ->>> (q1 | q2).order_by('name') -[, , , ] ->>> (q1 & q2).order_by('name') -[] - -# FIXME: This is difficult to fix and very much an edge case, so punt for now. -# # This is related to the order_by() tests, below, but the old bug exhibited -# # itself here (q2 was pulling too many tables into the combined query with the -# # new ordering, but only because we have evaluated q2 already). -# >>> len((q1 & q2).order_by('name').query.tables) -# 1 - ->>> q1 = Item.objects.filter(tags=t1) ->>> q2 = Item.objects.filter(note=n3, tags=t2) ->>> q3 = Item.objects.filter(creator=a4) ->>> ((q1 & q2) | q3).order_by('name') -[, ] - -Bugs #4088, #4306 ->>> Report.objects.filter(creator=1001) -[] ->>> Report.objects.filter(creator__num=1001) -[] ->>> Report.objects.filter(creator__id=1001) -[] ->>> Report.objects.filter(creator__id=a1.id) -[] ->>> Report.objects.filter(creator__name='a1') -[] - -Bug #4510 ->>> Author.objects.filter(report__name='r1') -[] - -Bug #7378 ->>> a1.report_set.all() -[] - -Bug #5324, #6704 ->>> Item.objects.filter(tags__name='t4') -[] ->>> Item.objects.exclude(tags__name='t4').order_by('name').distinct() -[, , ] ->>> Item.objects.exclude(tags__name='t4').order_by('name').distinct().reverse() -[, , ] ->>> Author.objects.exclude(item__name='one').distinct().order_by('name') -[, , ] - - -# Excluding across a m2m relation when there is more than one related object -# associated was problematic. ->>> Item.objects.exclude(tags__name='t1').order_by('name') -[, ] ->>> Item.objects.exclude(tags__name='t1').exclude(tags__name='t4') -[] - -# Excluding from a relation that cannot be NULL should not use outer joins. ->>> query = Item.objects.exclude(creator__in=[a1, a2]).query ->>> query.LOUTER not in [x[2] for x in query.alias_map.values()] -True - -Similarly, when one of the joins cannot possibly, ever, involve NULL values (Author -> ExtraInfo, in the following), it should never be promoted to a left outer join. So the following query should only involve one "left outer" join (Author -> Item is 0-to-many). ->>> qs = Author.objects.filter(id=a1.id).filter(Q(extra__note=n1)|Q(item__note=n3)) ->>> len([x[2] for x in qs.query.alias_map.values() if x[2] == query.LOUTER and qs.query.alias_refcount[x[1]]]) -1 - -The previous changes shouldn't affect nullable foreign key joins. ->>> Tag.objects.filter(parent__isnull=True).order_by('name') -[] ->>> Tag.objects.exclude(parent__isnull=True).order_by('name') -[, , , ] ->>> Tag.objects.exclude(Q(parent__name='t1') | Q(parent__isnull=True)).order_by('name') -[, ] ->>> Tag.objects.exclude(Q(parent__isnull=True) | Q(parent__name='t1')).order_by('name') -[, ] ->>> Tag.objects.exclude(Q(parent__parent__isnull=True)).order_by('name') -[, ] ->>> Tag.objects.filter(~Q(parent__parent__isnull=True)).order_by('name') -[, ] - -Bug #2091 ->>> t = Tag.objects.get(name='t4') ->>> Item.objects.filter(tags__in=[t]) -[] - -Combining querysets built on different models should behave in a well-defined -fashion. We raise an error. ->>> Author.objects.all() & Tag.objects.all() -Traceback (most recent call last): -... -AssertionError: Cannot combine queries on two different base models. ->>> Author.objects.all() | Tag.objects.all() -Traceback (most recent call last): -... -AssertionError: Cannot combine queries on two different base models. - -Bug #3141 ->>> Author.objects.extra(select={'foo': '1'}).count() -4 ->>> Author.objects.extra(select={'foo': '%s'}, select_params=(1,)).count() -4 - -Bug #2400 ->>> Author.objects.filter(item__isnull=True) -[] ->>> Tag.objects.filter(item__isnull=True) -[] - -Bug #2496 ->>> Item.objects.extra(tables=['queries_author']).select_related().order_by('name')[:1] -[] - -Bug #2076 -# Ordering on related tables should be possible, even if the table is not -# otherwise involved. ->>> Item.objects.order_by('note__note', 'name') -[, , , ] - -# Ordering on a related field should use the remote model's default ordering as -# a final step. ->>> Author.objects.order_by('extra', '-name') -[, , , ] - -# Using remote model default ordering can span multiple models (in this case, -# Cover is ordered by Item's default, which uses Note's default). ->>> Cover.objects.all() -[, ] - -# If the remote model does not have a default ordering, we order by its 'id' -# field. ->>> Item.objects.order_by('creator', 'name') -[, , , ] - -# Cross model ordering is possible in Meta, too. ->>> Ranking.objects.all() -[, , ] ->>> Ranking.objects.all().order_by('rank') -[, , ] - -# Ordering by a many-valued attribute (e.g. a many-to-many or reverse -# ForeignKey) is legal, but the results might not make sense. That isn't -# Django's problem. Garbage in, garbage out. ->>> Item.objects.filter(tags__isnull=False).order_by('tags', 'id') -[, , , , ] - -# If we replace the default ordering, Django adjusts the required tables -# automatically. Item normally requires a join with Note to do the default -# ordering, but that isn't needed here. ->>> qs = Item.objects.order_by('name') ->>> list(qs) -[, , , ] ->>> len(qs.query.tables) -1 - -# Ordering of extra() pieces is possible, too and you can mix extra fields and -# model fields in the ordering. ->>> Ranking.objects.extra(tables=['django_site'], order_by=['-django_site.id', 'rank']) -[, , ] - ->>> qs = Ranking.objects.extra(select={'good': 'case when rank > 2 then 1 else 0 end'}) ->>> [o.good for o in qs.extra(order_by=('-good',))] == [True, False, False] -True ->>> qs.extra(order_by=('-good', 'id')) -[, , ] - -# Despite having some extra aliases in the query, we can still omit them in a -# values() query. ->>> dicts = qs.values('id', 'rank').order_by('id') ->>> [sorted(d.items()) for d in dicts] -[[('id', 1), ('rank', 2)], [('id', 2), ('rank', 1)], [('id', 3), ('rank', 3)]] - -Bug #7256 -# An empty values() call includes all aliases, including those from an extra() ->>> dicts = qs.values().order_by('id') ->>> [sorted(d.items()) for d in dicts] -[[('author_id', 2), ('good', 0), ('id', 1), ('rank', 2)], [('author_id', 3), ('good', 0), ('id', 2), ('rank', 1)], [('author_id', 1), ('good', 1), ('id', 3), ('rank', 3)]] - -Bugs #2874, #3002 ->>> qs = Item.objects.select_related().order_by('note__note', 'name') ->>> list(qs) -[, , , ] - -# This is also a good select_related() test because there are multiple Note -# entries in the SQL. The two Note items should be different. ->>> qs[0].note, qs[0].creator.extra.note -(, ) - -Bug #3037 ->>> Item.objects.filter(Q(creator__name='a3', name='two')|Q(creator__name='a4', name='four')) -[] - -Bug #5321, #7070 - -Ordering columns must be included in the output columns. Note that this means -results that might otherwise be distinct are not (if there are multiple values -in the ordering cols), as in this example. This isn't a bug; it's a warning to -be careful with the selection of ordering columns. - ->>> Note.objects.values('misc').distinct().order_by('note', '-misc') -[{'misc': u'foo'}, {'misc': u'bar'}, {'misc': u'foo'}] - -Bug #4358 -If you don't pass any fields to values(), relation fields are returned as -"foo_id" keys, not "foo". For consistency, you should be able to pass "foo_id" -in the fields list and have it work, too. We actually allow both "foo" and -"foo_id". - -# The *_id version is returned by default. ->>> 'note_id' in ExtraInfo.objects.values()[0] -True - -# You can also pass it in explicitly. ->>> ExtraInfo.objects.values('note_id') -[{'note_id': 1}, {'note_id': 2}] - -# ...or use the field name. ->>> ExtraInfo.objects.values('note') -[{'note': 1}, {'note': 2}] - -Bug #5261 ->>> Note.objects.exclude(Q()) -[, , ] - -Bug #3045, #3288 -Once upon a time, select_related() with circular relations would loop -infinitely if you forgot to specify "depth". Now we set an arbitrary default -upper bound. ->>> X.objects.all() -[] ->>> X.objects.select_related() -[] - -Bug #3739 -The all() method on querysets returns a copy of the queryset. ->>> q1 = Item.objects.order_by('name') ->>> id(q1) == id(q1.all()) -False - -Bug #2902 -Parameters can be given to extra_select, *if* you use a SortedDict. - -(First we need to know which order the keys fall in "naturally" on your system, -so we can put things in the wrong way around from normal. A normal dict would -thus fail.) ->>> from django.utils.datastructures import SortedDict ->>> s = [('a', '%s'), ('b', '%s')] ->>> params = ['one', 'two'] ->>> if {'a': 1, 'b': 2}.keys() == ['a', 'b']: -... s.reverse() -... params.reverse() - -# This slightly odd comparison works around the fact that PostgreSQL will -# return 'one' and 'two' as strings, not Unicode objects. It's a side-effect of -# using constants here and not a real concern. ->>> d = Item.objects.extra(select=SortedDict(s), select_params=params).values('a', 'b')[0] ->>> d == {'a': u'one', 'b': u'two'} -True - -# Order by the number of tags attached to an item. ->>> l = Item.objects.extra(select={'count': 'select count(*) from queries_item_tags where queries_item_tags.item_id = queries_item.id'}).order_by('-count') ->>> [o.count for o in l] -[2, 2, 1, 0] - -Bug #6154 -Multiple filter statements are joined using "AND" all the time. - ->>> Author.objects.filter(id=a1.id).filter(Q(extra__note=n1)|Q(item__note=n3)) -[] ->>> Author.objects.filter(Q(extra__note=n1)|Q(item__note=n3)).filter(id=a1.id) -[] - -Bug #6981 ->>> Tag.objects.select_related('parent').order_by('name') -[, , , , ] - -Bug #9926 ->>> Tag.objects.select_related("parent", "category").order_by('name') -[, , , , ] ->>> Tag.objects.select_related('parent', "parent__category").order_by('name') -[, , , , ] - -Bug #6180, #6203 -- dates with limits and/or counts ->>> Item.objects.count() -4 ->>> Item.objects.dates('created', 'month').count() -1 ->>> Item.objects.dates('created', 'day').count() -2 ->>> len(Item.objects.dates('created', 'day')) -2 ->>> Item.objects.dates('created', 'day')[0] -datetime.datetime(2007, 12, 19, 0, 0) - -Bug #7087/#12242 -- dates with extra select columns ->>> Item.objects.dates('created', 'day').extra(select={'a': 1}) -[datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)] - ->>> Item.objects.extra(select={'a': 1}).dates('created', 'day') -[datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)] - ->>> name="one" ->>> Item.objects.dates('created', 'day').extra(where=['name=%s'], params=[name]) -[datetime.datetime(2007, 12, 19, 0, 0)] - ->>> Item.objects.extra(where=['name=%s'], params=[name]).dates('created', 'day') -[datetime.datetime(2007, 12, 19, 0, 0)] - -Bug #7155 -- nullable dates ->>> Item.objects.dates('modified', 'day') -[datetime.datetime(2007, 12, 19, 0, 0)] - -Test that parallel iterators work. - ->>> qs = Tag.objects.all() ->>> i1, i2 = iter(qs), iter(qs) ->>> i1.next(), i1.next() -(, ) ->>> i2.next(), i2.next(), i2.next() -(, , ) ->>> i1.next() - - ->>> qs = X.objects.all() ->>> bool(qs) -False ->>> bool(qs) -False - -We can do slicing beyond what is currently in the result cache, too. - -## FIXME!! This next test causes really weird PostgreSQL behaviour, but it's -## only apparent much later when the full test suite runs. I don't understand -## what's going on here yet. -## -## # We need to mess with the implementation internals a bit here to decrease the -## # cache fill size so that we don't read all the results at once. -## >>> from django.db.models import query -## >>> query.ITER_CHUNK_SIZE = 2 -## >>> qs = Tag.objects.all() -## -## # Fill the cache with the first chunk. -## >>> bool(qs) -## True -## >>> len(qs._result_cache) -## 2 -## -## # Query beyond the end of the cache and check that it is filled out as required. -## >>> qs[4] -## -## >>> len(qs._result_cache) -## 5 -## -## # But querying beyond the end of the result set will fail. -## >>> qs[100] -## Traceback (most recent call last): -## ... -## IndexError: ... - -Bug #7045 -- extra tables used to crash SQL construction on the second use. ->>> qs = Ranking.objects.extra(tables=['django_site']) ->>> s = qs.query.get_compiler(qs.db).as_sql() ->>> s = qs.query.get_compiler(qs.db).as_sql() # test passes if this doesn't raise an exception. - -Bug #7098 -- Make sure semi-deprecated ordering by related models syntax still -works. ->>> Item.objects.values('note__note').order_by('queries_note.note', 'id') -[{'note__note': u'n2'}, {'note__note': u'n3'}, {'note__note': u'n3'}, {'note__note': u'n3'}] - -Bug #7096 -- Make sure exclude() with multiple conditions continues to work. ->>> Tag.objects.filter(parent=t1, name='t3').order_by('name') -[] ->>> Tag.objects.exclude(parent=t1, name='t3').order_by('name') -[, , , ] ->>> Item.objects.exclude(tags__name='t1', name='one').order_by('name').distinct() -[, , ] ->>> Item.objects.filter(name__in=['three', 'four']).exclude(tags__name='t1').order_by('name') -[, ] - -More twisted cases, involving nested negations. ->>> Item.objects.exclude(~Q(tags__name='t1', name='one')) -[] ->>> Item.objects.filter(~Q(tags__name='t1', name='one'), name='two') -[] ->>> Item.objects.exclude(~Q(tags__name='t1', name='one'), name='two') -[, , ] - -Bug #7095 -Updates that are filtered on the model being updated are somewhat tricky -in MySQL. This exercises that case. ->>> mm = ManagedModel.objects.create(data='mm1', tag=t1, public=True) ->>> ManagedModel.objects.update(data='mm') -1 - -A values() or values_list() query across joined models must use outer joins -appropriately. ->>> Report.objects.values_list("creator__extra__info", flat=True).order_by("name") -[u'e1', u'e2', ] - -Similarly for select_related(), joins beyond an initial nullable join must -use outer joins so that all results are included. ->>> Report.objects.select_related("creator", "creator__extra").order_by("name") -[, , ] - -When there are multiple paths to a table from another table, we have to be -careful not to accidentally reuse an inappropriate join when using -select_related(). We used to return the parent's Detail record here by mistake. - ->>> d1 = Detail.objects.create(data="d1") ->>> d2 = Detail.objects.create(data="d2") ->>> m1 = Member.objects.create(name="m1", details=d1) ->>> m2 = Member.objects.create(name="m2", details=d2) ->>> c1 = Child.objects.create(person=m2, parent=m1) ->>> obj = m1.children.select_related("person__details")[0] ->>> obj.person.details.data -u'd2' - -Bug #7076 -- excluding shouldn't eliminate NULL entries. ->>> Item.objects.exclude(modified=time1).order_by('name') -[, , ] ->>> Tag.objects.exclude(parent__name=t1.name) -[, , ] - -Bug #7181 -- ordering by related tables should accomodate nullable fields (this -test is a little tricky, since NULL ordering is database dependent. Instead, we -just count the number of results). ->>> len(Tag.objects.order_by('parent__name')) -5 - -Bug #7107 -- this shouldn't create an infinite loop. ->>> Valid.objects.all() -[] - -Empty querysets can be merged with others. ->>> Note.objects.none() | Note.objects.all() -[, , ] ->>> Note.objects.all() | Note.objects.none() -[, , ] ->>> Note.objects.none() & Note.objects.all() -[] ->>> Note.objects.all() & Note.objects.none() -[] - -Bug #7204, #7506 -- make sure querysets with related fields can be pickled. If -this doesn't crash, it's a Good Thing. ->>> out = pickle.dumps(Item.objects.all()) - -We should also be able to pickle things that use select_related(). The only -tricky thing here is to ensure that we do the related selections properly after -unpickling. ->>> qs = Item.objects.select_related() ->>> query = qs.query.get_compiler(qs.db).as_sql()[0] ->>> query2 = pickle.loads(pickle.dumps(qs.query)) ->>> query2.get_compiler(qs.db).as_sql()[0] == query -True - -Check pickling of deferred-loading querysets ->>> qs = Item.objects.defer('name', 'creator') ->>> q2 = pickle.loads(pickle.dumps(qs)) ->>> list(qs) == list(q2) -True ->>> q3 = pickle.loads(pickle.dumps(qs, pickle.HIGHEST_PROTOCOL)) ->>> list(qs) == list(q3) -True - -Bug #7277 ->>> n1.annotation_set.filter(Q(tag=t5) | Q(tag__children=t5) | Q(tag__children__children=t5)) -[] - -Bug #7371 ->>> Related.objects.order_by('custom') -[] - -Bug #7448, #7707 -- Complex objects should be converted to strings before being -used in lookups. ->>> Item.objects.filter(created__in=[time1, time2]) -[, ] - -Bug #7698, #10202 -- People like to slice with '0' as the high-water mark. ->>> Item.objects.all()[0:0] -[] ->>> Item.objects.all()[0:0][:10] -[] ->>> Item.objects.all()[:0].count() -0 ->>> Item.objects.all()[:0].latest('created') -Traceback (most recent call last): - ... -AssertionError: Cannot change a query once a slice has been taken. - -Bug #7411 - saving to db must work even with partially read result set in -another cursor. - ->>> for num in range(2 * ITER_CHUNK_SIZE + 1): -... _ = Number.objects.create(num=num) - ->>> for i, obj in enumerate(Number.objects.all()): -... obj.save() -... if i > 10: break - -Bug #7759 -- count should work with a partially read result set. ->>> count = Number.objects.count() ->>> qs = Number.objects.all() ->>> for obj in qs: -... qs.count() == count -... break -True - -Bug #7235 -- an EmptyQuerySet should not raise exceptions if it is filtered. ->>> q = EmptyQuerySet() ->>> q.all() -[] ->>> q.filter(x=10) -[] ->>> q.exclude(y=3) -[] ->>> q.complex_filter({'pk': 1}) -[] ->>> q.select_related('spam', 'eggs') -[] ->>> q.annotate(Count('eggs')) -[] ->>> q.order_by('-pub_date', 'headline') -[] ->>> q.distinct() -[] ->>> q.extra(select={'is_recent': "pub_date > '2006-01-01'"}) -[] ->>> q.query.low_mark = 1 ->>> q.extra(select={'is_recent': "pub_date > '2006-01-01'"}) -Traceback (most recent call last): -... -AssertionError: Cannot change a query once a slice has been taken ->>> q.reverse() -[] ->>> q.defer('spam', 'eggs') -[] ->>> q.only('spam', 'eggs') -[] - -Bug #7791 -- there were "issues" when ordering and distinct-ing on fields -related via ForeignKeys. ->>> len(Note.objects.order_by('extrainfo__info').distinct()) -3 - -Bug #7778 - Model subclasses could not be deleted if a nullable foreign key -relates to a model that relates back. - ->>> num_celebs = Celebrity.objects.count() ->>> tvc = TvChef.objects.create(name="Huey") ->>> Celebrity.objects.count() == num_celebs + 1 -True ->>> f1 = Fan.objects.create(fan_of=tvc) ->>> f2 = Fan.objects.create(fan_of=tvc) ->>> tvc.delete() - -# The parent object should have been deleted as well. ->>> Celebrity.objects.count() == num_celebs -True - -Bug #8283 -- Checking that applying filters after a disjunction works correctly. ->>> (ExtraInfo.objects.filter(note=n1)|ExtraInfo.objects.filter(info='e2')).filter(note=n1) -[] ->>> (ExtraInfo.objects.filter(info='e2')|ExtraInfo.objects.filter(note=n1)).filter(note=n1) -[] - -Pickling of DateQuerySets used to fail ->>> qs = Item.objects.dates('created', 'month') ->>> _ = pickle.loads(pickle.dumps(qs)) - -Bug #8683 -- raise proper error when a DateQuerySet gets passed a wrong type of field ->>> Item.objects.dates('name', 'month') -Traceback (most recent call last): -... -AssertionError: 'name' isn't a DateField. - -Bug #8597: regression tests for case-insensitive comparisons ->>> _ = Item.objects.create(name="a_b", created=datetime.datetime.now(), creator=a2, note=n1) ->>> _ = Item.objects.create(name="x%y", created=datetime.datetime.now(), creator=a2, note=n1) ->>> Item.objects.filter(name__iexact="A_b") -[] ->>> Item.objects.filter(name__iexact="x%Y") -[] ->>> Item.objects.filter(name__istartswith="A_b") -[] ->>> Item.objects.filter(name__iendswith="A_b") -[] - -Bug #7302: reserved names are appropriately escaped ->>> _ = ReservedName.objects.create(name='a',order=42) ->>> _ = ReservedName.objects.create(name='b',order=37) ->>> ReservedName.objects.all().order_by('order') -[, ] ->>> ReservedName.objects.extra(select={'stuff':'name'}, order_by=('order','stuff')) -[, ] - -Bug #8439 -- complex combinations of conjunctions, disjunctions and nullable -relations. ->>> Author.objects.filter(Q(item__note__extrainfo=e2)|Q(report=r1, name='xyz')) -[] ->>> Author.objects.filter(Q(report=r1, name='xyz')|Q(item__note__extrainfo=e2)) -[] ->>> Annotation.objects.filter(Q(tag__parent=t1)|Q(notes__note='n1', name='a1')) -[] ->>> xx = ExtraInfo.objects.create(info='xx', note=n3) ->>> Note.objects.filter(Q(extrainfo__author=a1)|Q(extrainfo=xx)) -[, ] ->>> xx.delete() ->>> q = Note.objects.filter(Q(extrainfo__author=a1)|Q(extrainfo=xx)).query ->>> len([x[2] for x in q.alias_map.values() if x[2] == q.LOUTER and q.alias_refcount[x[1]]]) -1 - -Make sure bump_prefix() (an internal Query method) doesn't (re-)break. It's -sufficient that this query runs without error. ->>> qs = Tag.objects.values_list('id', flat=True).order_by('id') ->>> qs.query.bump_prefix() ->>> list(qs) -[1, 2, 3, 4, 5] - -Calling order_by() with no parameters removes any existing ordering on the -model. But it should still be possible to add new ordering after that. ->>> qs = Author.objects.order_by().order_by('name') ->>> 'ORDER BY' in qs.query.get_compiler(qs.db).as_sql()[0] -True - -Incorrect SQL was being generated for certain types of exclude() queries that -crossed multi-valued relations (#8921, #9188 and some pre-emptively discovered -cases). - ->>> PointerA.objects.filter(connection__pointerb__id=1) -[] ->>> PointerA.objects.exclude(connection__pointerb__id=1) -[] - ->>> Tag.objects.exclude(children=None) -[, ] - -# This example is tricky because the parent could be NULL, so only checking -# parents with annotations omits some results (tag t1, in this case). ->>> Tag.objects.exclude(parent__annotation__name="a1") -[, , ] - -# The annotation->tag link is single values and tag->children links is -# multi-valued. So we have to split the exclude filter in the middle and then -# optimise the inner query without losing results. ->>> Annotation.objects.exclude(tag__children__name="t2") -[] - -Nested queries are possible (although should be used with care, since they have -performance problems on backends like MySQL. - ->>> Annotation.objects.filter(notes__in=Note.objects.filter(note="n1")) -[] - -Nested queries should not evaluate the inner query as part of constructing the -SQL (so we should see a nested query here, indicated by two "SELECT" calls). ->>> qs = Annotation.objects.filter(notes__in=Note.objects.filter(note="xyzzy")) ->>> qs.query.get_compiler(qs.db).as_sql()[0].count('SELECT') -2 - -Bug #10181 -- Avoid raising an EmptyResultSet if an inner query is provably -empty (and hence, not executed). ->>> Tag.objects.filter(id__in=Tag.objects.filter(id__in=[])) -[] - -Bug #9997 -- If a ValuesList or Values queryset is passed as an inner query, we -make sure it's only requesting a single value and use that as the thing to -select. ->>> Tag.objects.filter(name__in=Tag.objects.filter(parent=t1).values('name')) -[, ] - -# Multi-valued values() and values_list() querysets should raise errors. ->>> Tag.objects.filter(name__in=Tag.objects.filter(parent=t1).values('name', 'id')) -Traceback (most recent call last): -... -TypeError: Cannot use a multi-field ValuesQuerySet as a filter value. ->>> Tag.objects.filter(name__in=Tag.objects.filter(parent=t1).values_list('name', 'id')) -Traceback (most recent call last): -... -TypeError: Cannot use a multi-field ValuesListQuerySet as a filter value. - -Bug #9985 -- qs.values_list(...).values(...) combinations should work. ->>> Note.objects.values_list("note", flat=True).values("id").order_by("id") -[{'id': 1}, {'id': 2}, {'id': 3}] ->>> Annotation.objects.filter(notes__in=Note.objects.filter(note="n1").values_list('note').values('id')) -[] - -Bug #10028 -- ordering by model related to nullable relations(!) should use -outer joins, so that all results are included. ->>> _ = Plaything.objects.create(name="p1") ->>> Plaything.objects.all() -[] - -Bug #10205 -- When bailing out early because of an empty "__in" filter, we need -to set things up correctly internally so that subqueries can continue properly. ->>> Tag.objects.filter(name__in=()).update(name="foo") -0 - -Bug #10432 (see also the Python 2.4+ tests for this, below). Testing an empty -"__in" filter with a generator as the value. ->>> def f(): -... return iter([]) ->>> n_obj = Note.objects.all()[0] ->>> def g(): -... for i in [n_obj.pk]: -... yield i ->>> Note.objects.filter(pk__in=f()) -[] ->>> list(Note.objects.filter(pk__in=g())) == [n_obj] -True - -Make sure that updates which only filter on sub-tables don't inadvertently -update the wrong records (bug #9848). - -# Make sure that the IDs from different tables don't happen to match. ->>> Ranking.objects.filter(author__name='a1') -[] ->>> Ranking.objects.filter(author__name='a1').update(rank='4') -1 ->>> r = Ranking.objects.filter(author__name='a1')[0] ->>> r.id != r.author.id -True ->>> r.rank -4 ->>> r.rank = 3 ->>> r.save() ->>> Ranking.objects.all() -[, , ] - -# Regression test for #10742: -# Queries used in an __in clause don't execute subqueries - ->>> subq = Author.objects.filter(num__lt=3000) ->>> qs = Author.objects.filter(pk__in=subq) ->>> list(qs) -[, ] - -# The subquery result cache should not be populated ->>> subq._result_cache is None -True - ->>> subq = Author.objects.filter(num__lt=3000) ->>> qs = Author.objects.exclude(pk__in=subq) ->>> list(qs) -[, ] - -# The subquery result cache should not be populated ->>> subq._result_cache is None -True - ->>> subq = Author.objects.filter(num__lt=3000) ->>> list(Author.objects.filter(Q(pk__in=subq) & Q(name='a1'))) -[] - -# The subquery result cache should not be populated ->>> subq._result_cache is None -True - -"""} - -# In Python 2.6 beta releases, exceptions raised in __len__ -# are swallowed (Python issue 1242657), so these cases return an empty list, -# rather than raising an exception. Not a lot we can do about that, -# unfortunately, due to the way Python handles list() calls internally. Thus, -# we skip the tests for Python 2.6. -if sys.version_info < (2, 6): - __test__["API_TESTS"] += """ -# If you're not careful, it's possible to introduce infinite loops via default -# ordering on foreign keys in a cycle. We detect that. ->>> LoopX.objects.all() -Traceback (most recent call last): -... -FieldError: Infinite loop caused by ordering. - ->>> LoopZ.objects.all() -Traceback (most recent call last): -... -FieldError: Infinite loop caused by ordering. - -# Note that this doesn't cause an infinite loop, since the default ordering on -# the Tag model is empty (and thus defaults to using "id" for the related -# field). ->>> len(Tag.objects.order_by('parent')) -5 - -# ... but you can still order in a non-recursive fashion amongst linked fields -# (the previous test failed because the default ordering was recursive). ->>> LoopX.objects.all().order_by('y__x__y__x__id') -[] - -""" - - -# In Oracle, we expect a null CharField to return u'' instead of None. -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == "django.db.backends.oracle": - __test__["API_TESTS"] = __test__["API_TESTS"].replace("", "u''") -else: - __test__["API_TESTS"] = __test__["API_TESTS"].replace("", "None") - - -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == "django.db.backends.mysql": - __test__["API_TESTS"] += """ -When grouping without specifying ordering, we add an explicit "ORDER BY NULL" -portion in MySQL to prevent unnecessary sorting. - ->>> query = Tag.objects.values_list('parent_id', flat=True).order_by().query ->>> query.group_by = ['parent_id'] ->>> sql = query.get_compiler(DEFAULT_DB_ALIAS).as_sql()[0] ->>> fragment = "ORDER BY " ->>> pos = sql.find(fragment) ->>> sql.find(fragment, pos + 1) == -1 -True ->>> sql.find("NULL", pos + len(fragment)) == pos + len(fragment) -True - -""" - -# Generator expressions are only in Python 2.4 and later. -if sys.version_info >= (2, 4): - __test__["API_TESTS"] += """ -Using an empty generator expression as the rvalue for an "__in" lookup is legal -(regression for #10432). ->>> Note.objects.filter(pk__in=(x for x in ())) -[] - -""" - -# Sqlite 3 does not support passing in more than 1000 parameters except by -# changing a parameter at compilation time. -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != "django.db.backends.sqlite3": - __test__["API_TESTS"] += """ -Bug #14244: Test that the "in" lookup works with lists of 1000 items or more. ->>> Number.objects.all().delete() ->>> numbers = range(2500) ->>> for num in numbers: -... _ = Number.objects.create(num=num) ->>> Number.objects.filter(num__in=numbers[:1000]).count() -1000 ->>> Number.objects.filter(num__in=numbers[:1001]).count() -1001 ->>> Number.objects.filter(num__in=numbers[:2000]).count() -2000 ->>> Number.objects.filter(num__in=numbers).count() -2500 - -""" +class Article(models.Model): + name = models.CharField(max_length=20) + created = models.DateTimeField() diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index ab5369246d0c..a5ab88927927 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -1,10 +1,1354 @@ +import datetime +import pickle +import sys import unittest -from django.db import DatabaseError, connections, DEFAULT_DB_ALIAS +from django.conf import settings +from django.core.exceptions import FieldError +from django.db import DatabaseError, connection, connections, DEFAULT_DB_ALIAS from django.db.models import Count +from django.db.models.query import Q, ITER_CHUNK_SIZE, EmptyQuerySet from django.test import TestCase +from django.utils.datastructures import SortedDict + +from models import (Annotation, Article, Author, Celebrity, Child, Cover, Detail, + DumbCategory, ExtraInfo, Fan, Item, LeafA, LoopX, LoopZ, ManagedModel, + Member, NamedCategory, Note, Number, Plaything, PointerA, Ranking, Related, + Report, ReservedName, Tag, TvChef, Valid, X) + + +class BaseQuerysetTest(TestCase): + def assertValueQuerysetEqual(self, qs, values): + return self.assertQuerysetEqual(qs, values, transform=lambda x: x) + + def assertRaisesMessage(self, exc, msg, func, *args, **kwargs): + try: + func(*args, **kwargs) + except Exception, e: + self.assertEqual(msg, str(e)) + self.assertTrue(isinstance(e, exc), "Expected %s, got %s" % (exc, type(e))) + + +class Queries1Tests(BaseQuerysetTest): + def setUp(self): + generic = NamedCategory.objects.create(name="Generic") + self.t1 = Tag.objects.create(name='t1', category=generic) + self.t2 = Tag.objects.create(name='t2', parent=self.t1, category=generic) + self.t3 = Tag.objects.create(name='t3', parent=self.t1) + t4 = Tag.objects.create(name='t4', parent=self.t3) + self.t5 = Tag.objects.create(name='t5', parent=self.t3) + + self.n1 = Note.objects.create(note='n1', misc='foo', id=1) + n2 = Note.objects.create(note='n2', misc='bar', id=2) + self.n3 = Note.objects.create(note='n3', misc='foo', id=3) + + ann1 = Annotation.objects.create(name='a1', tag=self.t1) + ann1.notes.add(self.n1) + ann2 = Annotation.objects.create(name='a2', tag=t4) + ann2.notes.add(n2, self.n3) + + # Create these out of order so that sorting by 'id' will be different to sorting + # by 'info'. Helps detect some problems later. + self.e2 = ExtraInfo.objects.create(info='e2', note=n2) + e1 = ExtraInfo.objects.create(info='e1', note=self.n1) + + self.a1 = Author.objects.create(name='a1', num=1001, extra=e1) + self.a2 = Author.objects.create(name='a2', num=2002, extra=e1) + a3 = Author.objects.create(name='a3', num=3003, extra=self.e2) + self.a4 = Author.objects.create(name='a4', num=4004, extra=self.e2) + + self.time1 = datetime.datetime(2007, 12, 19, 22, 25, 0) + self.time2 = datetime.datetime(2007, 12, 19, 21, 0, 0) + time3 = datetime.datetime(2007, 12, 20, 22, 25, 0) + time4 = datetime.datetime(2007, 12, 20, 21, 0, 0) + self.i1 = Item.objects.create(name='one', created=self.time1, modified=self.time1, creator=self.a1, note=self.n3) + self.i1.tags = [self.t1, self.t2] + self.i2 = Item.objects.create(name='two', created=self.time2, creator=self.a2, note=n2) + self.i2.tags = [self.t1, self.t3] + self.i3 = Item.objects.create(name='three', created=time3, creator=self.a2, note=self.n3) + i4 = Item.objects.create(name='four', created=time4, creator=self.a4, note=self.n3) + i4.tags = [t4] + + self.r1 = Report.objects.create(name='r1', creator=self.a1) + Report.objects.create(name='r2', creator=a3) + Report.objects.create(name='r3') + + # Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering + # will be rank3, rank2, rank1. + self.rank1 = Ranking.objects.create(rank=2, author=self.a2) + + Cover.objects.create(title="first", item=i4) + Cover.objects.create(title="second", item=self.i2) + + def test_ticket1050(self): + self.assertQuerysetEqual( + Item.objects.filter(tags__isnull=True), + [''] + ) + self.assertQuerysetEqual( + Item.objects.filter(tags__id__isnull=True), + [''] + ) + + def test_ticket1801(self): + self.assertQuerysetEqual( + Author.objects.filter(item=self.i2), + [''] + ) + self.assertQuerysetEqual( + Author.objects.filter(item=self.i3), + [''] + ) + self.assertQuerysetEqual( + Author.objects.filter(item=self.i2) & Author.objects.filter(item=self.i3), + [''] + ) + + def test_ticket2306(self): + # Checking that no join types are "left outer" joins. + query = Item.objects.filter(tags=self.t2).query + self.assertTrue(query.LOUTER not in [x[2] for x in query.alias_map.values()]) + + self.assertQuerysetEqual( + Item.objects.filter(Q(tags=self.t1)).order_by('name'), + ['', ''] + ) + self.assertQuerysetEqual( + Item.objects.filter(Q(tags=self.t1)).filter(Q(tags=self.t2)), + [''] + ) + self.assertQuerysetEqual( + Item.objects.filter(Q(tags=self.t1)).filter(Q(creator__name='fred')|Q(tags=self.t2)), + [''] + ) + + # Each filter call is processed "at once" against a single table, so this is + # different from the previous example as it tries to find tags that are two + # things at once (rather than two tags). + self.assertQuerysetEqual( + Item.objects.filter(Q(tags=self.t1) & Q(tags=self.t2)), + [] + ) + self.assertQuerysetEqual( + Item.objects.filter(Q(tags=self.t1), Q(creator__name='fred')|Q(tags=self.t2)), + [] + ) + + qs = Author.objects.filter(ranking__rank=2, ranking__id=self.rank1.id) + self.assertQuerysetEqual(list(qs), ['']) + self.assertEqual(2, qs.query.count_active_tables(), 2) + qs = Author.objects.filter(ranking__rank=2).filter(ranking__id=self.rank1.id) + self.assertEqual(qs.query.count_active_tables(), 3) + + def test_ticket4464(self): + self.assertQuerysetEqual( + Item.objects.filter(tags=self.t1).filter(tags=self.t2), + [''] + ) + self.assertQuerysetEqual( + Item.objects.filter(tags__in=[self.t1, self.t2]).distinct().order_by('name'), + ['', ''] + ) + self.assertQuerysetEqual( + Item.objects.filter(tags__in=[self.t1, self.t2]).filter(tags=self.t3), + [''] + ) + + # Make sure .distinct() works with slicing (this was broken in Oracle). + self.assertQuerysetEqual( + Item.objects.filter(tags__in=[self.t1, self.t2]).order_by('name')[:3], + ['', '', ''] + ) + self.assertQuerysetEqual( + Item.objects.filter(tags__in=[self.t1, self.t2]).distinct().order_by('name')[:3], + ['', ''] + ) + + def test_tickets_2080_3592(self): + self.assertQuerysetEqual( + Author.objects.filter(item__name='one') | Author.objects.filter(name='a3'), + ['', ''] + ) + self.assertQuerysetEqual( + Author.objects.filter(Q(item__name='one') | Q(name='a3')), + ['', ''] + ) + self.assertQuerysetEqual( + Author.objects.filter(Q(name='a3') | Q(item__name='one')), + ['', ''] + ) + self.assertQuerysetEqual( + Author.objects.filter(Q(item__name='three') | Q(report__name='r3')), + [''] + ) + + def test_ticket6074(self): + # Merging two empty result sets shouldn't leave a queryset with no constraints + # (which would match everything). + self.assertQuerysetEqual(Author.objects.filter(Q(id__in=[])), []) + self.assertQuerysetEqual( + Author.objects.filter(Q(id__in=[])|Q(id__in=[])), + [] + ) + + def test_tickets_1878_2939(self): + self.assertEqual(Item.objects.values('creator').distinct().count(), 3) + + # Create something with a duplicate 'name' so that we can test multi-column + # cases (which require some tricky SQL transformations under the covers). + xx = Item(name='four', created=self.time1, creator=self.a2, note=self.n1) + xx.save() + self.assertEqual( + Item.objects.exclude(name='two').values('creator', 'name').distinct().count(), + 4 + ) + self.assertEqual( + Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name', 'foo').distinct().count(), + 4 + ) + self.assertEqual( + Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name').distinct().count(), + 4 + ) + xx.delete() + + def test_ticket7323(self): + self.assertEqual(Item.objects.values('creator', 'name').count(), 4) + + def test_ticket2253(self): + q1 = Item.objects.order_by('name') + q2 = Item.objects.filter(id=self.i1.id) + self.assertQuerysetEqual( + q1, + ['', '', '', ''] + ) + self.assertQuerysetEqual(q2, ['']) + self.assertQuerysetEqual( + (q1 | q2).order_by('name'), + ['', '', '', ''] + ) + self.assertQuerysetEqual((q1 & q2).order_by('name'), ['']) + + # FIXME: This is difficult to fix and very much an edge case, so punt for now. + # This is related to the order_by() tests, below, but the old bug exhibited + # itself here (q2 was pulling too many tables into the combined query with the + # new ordering, but only because we have evaluated q2 already). + # + #self.assertEqual(len((q1 & q2).order_by('name').query.tables), 1) + + q1 = Item.objects.filter(tags=self.t1) + q2 = Item.objects.filter(note=self.n3, tags=self.t2) + q3 = Item.objects.filter(creator=self.a4) + self.assertQuerysetEqual( + ((q1 & q2) | q3).order_by('name'), + ['', ''] + ) + + def test_tickets_4088_4306(self): + self.assertQuerysetEqual( + Report.objects.filter(creator=1001), + [''] + ) + self.assertQuerysetEqual( + Report.objects.filter(creator__num=1001), + [''] + ) + self.assertQuerysetEqual(Report.objects.filter(creator__id=1001), []) + self.assertQuerysetEqual( + Report.objects.filter(creator__id=self.a1.id), + [''] + ) + self.assertQuerysetEqual( + Report.objects.filter(creator__name='a1'), + [''] + ) + + def test_ticket4510(self): + self.assertQuerysetEqual( + Author.objects.filter(report__name='r1'), + [''] + ) + + def test_ticket7378(self): + self.assertQuerysetEqual(self.a1.report_set.all(), ['']) + + def test_tickets_5324_6704(self): + self.assertQuerysetEqual( + Item.objects.filter(tags__name='t4'), + [''] + ) + self.assertQuerysetEqual( + Item.objects.exclude(tags__name='t4').order_by('name').distinct(), + ['', '', ''] + ) + self.assertQuerysetEqual( + Item.objects.exclude(tags__name='t4').order_by('name').distinct().reverse(), + ['', '', ''] + ) + self.assertQuerysetEqual( + Author.objects.exclude(item__name='one').distinct().order_by('name'), + ['', '', ''] + ) + + # Excluding across a m2m relation when there is more than one related + # object associated was problematic. + self.assertQuerysetEqual( + Item.objects.exclude(tags__name='t1').order_by('name'), + ['', ''] + ) + self.assertQuerysetEqual( + Item.objects.exclude(tags__name='t1').exclude(tags__name='t4'), + [''] + ) + + # Excluding from a relation that cannot be NULL should not use outer joins. + query = Item.objects.exclude(creator__in=[self.a1, self.a2]).query + self.assertTrue(query.LOUTER not in [x[2] for x in query.alias_map.values()]) + + # Similarly, when one of the joins cannot possibly, ever, involve NULL + # values (Author -> ExtraInfo, in the following), it should never be + # promoted to a left outer join. So the following query should only + # involve one "left outer" join (Author -> Item is 0-to-many). + qs = Author.objects.filter(id=self.a1.id).filter(Q(extra__note=self.n1)|Q(item__note=self.n3)) + self.assertEqual( + len([x[2] for x in qs.query.alias_map.values() if x[2] == query.LOUTER and qs.query.alias_refcount[x[1]]]), + 1 + ) + + # The previous changes shouldn't affect nullable foreign key joins. + self.assertQuerysetEqual( + Tag.objects.filter(parent__isnull=True).order_by('name'), + [''] + ) + self.assertQuerysetEqual( + Tag.objects.exclude(parent__isnull=True).order_by('name'), + ['', '', '', ''] + ) + self.assertQuerysetEqual( + Tag.objects.exclude(Q(parent__name='t1') | Q(parent__isnull=True)).order_by('name'), + ['', ''] + ) + self.assertQuerysetEqual( + Tag.objects.exclude(Q(parent__isnull=True) | Q(parent__name='t1')).order_by('name'), + ['', ''] + ) + self.assertQuerysetEqual( + Tag.objects.exclude(Q(parent__parent__isnull=True)).order_by('name'), + ['', ''] + ) + self.assertQuerysetEqual( + Tag.objects.filter(~Q(parent__parent__isnull=True)).order_by('name'), + ['', ''] + ) + + def test_ticket2091(self): + t = Tag.objects.get(name='t4') + self.assertQuerysetEqual( + Item.objects.filter(tags__in=[t]), + [''] + ) + + def test_heterogeneous_qs_combination(self): + # Combining querysets built on different models should behave in a well-defined + # fashion. We raise an error. + self.assertRaisesMessage( + AssertionError, + 'Cannot combine queries on two different base models.', + lambda: Author.objects.all() & Tag.objects.all() + ) + self.assertRaisesMessage( + AssertionError, + 'Cannot combine queries on two different base models.', + lambda: Author.objects.all() | Tag.objects.all() + ) + + def test_ticket3141(self): + self.assertEqual(Author.objects.extra(select={'foo': '1'}).count(), 4) + self.assertEqual( + Author.objects.extra(select={'foo': '%s'}, select_params=(1,)).count(), + 4 + ) + + def test_ticket2400(self): + self.assertQuerysetEqual( + Author.objects.filter(item__isnull=True), + [''] + ) + self.assertQuerysetEqual( + Tag.objects.filter(item__isnull=True), + [''] + ) + + def test_ticket2496(self): + self.assertQuerysetEqual( + Item.objects.extra(tables=['queries_author']).select_related().order_by('name')[:1], + [''] + ) + + def test_tickets_2076_7256(self): + # Ordering on related tables should be possible, even if the table is + # not otherwise involved. + self.assertQuerysetEqual( + Item.objects.order_by('note__note', 'name'), + ['', '', '', ''] + ) + + # Ordering on a related field should use the remote model's default + # ordering as a final step. + self.assertQuerysetEqual( + Author.objects.order_by('extra', '-name'), + ['', '', '', ''] + ) + + # Using remote model default ordering can span multiple models (in this + # case, Cover is ordered by Item's default, which uses Note's default). + self.assertQuerysetEqual( + Cover.objects.all(), + ['', ''] + ) + + # If the remote model does not have a default ordering, we order by its 'id' + # field. + self.assertQuerysetEqual( + Item.objects.order_by('creator', 'name'), + ['', '', '', ''] + ) + + # Ordering by a many-valued attribute (e.g. a many-to-many or reverse + # ForeignKey) is legal, but the results might not make sense. That + # isn't Django's problem. Garbage in, garbage out. + self.assertQuerysetEqual( + Item.objects.filter(tags__isnull=False).order_by('tags', 'id'), + ['', '', '', '', ''] + ) + + # If we replace the default ordering, Django adjusts the required + # tables automatically. Item normally requires a join with Note to do + # the default ordering, but that isn't needed here. + qs = Item.objects.order_by('name') + self.assertQuerysetEqual( + qs, + ['', '', '', ''] + ) + self.assertEqual(len(qs.query.tables), 1) + + def test_tickets_2874_3002(self): + qs = Item.objects.select_related().order_by('note__note', 'name') + self.assertQuerysetEqual( + qs, + ['', '', '', ''] + ) + + # This is also a good select_related() test because there are multiple + # Note entries in the SQL. The two Note items should be different. + self.assertTrue(repr(qs[0].note), '') + self.assertEqual(repr(qs[0].creator.extra.note), '') + + def test_ticket3037(self): + self.assertQuerysetEqual( + Item.objects.filter(Q(creator__name='a3', name='two')|Q(creator__name='a4', name='four')), + [''] + ) + + def test_tickets_5321_7070(self): + # Ordering columns must be included in the output columns. Note that + # this means results that might otherwise be distinct are not (if there + # are multiple values in the ordering cols), as in this example. This + # isn't a bug; it's a warning to be careful with the selection of + # ordering columns. + self.assertValueQuerysetEqual( + Note.objects.values('misc').distinct().order_by('note', '-misc'), + [{'misc': u'foo'}, {'misc': u'bar'}, {'misc': u'foo'}] + ) + + def test_ticket4358(self): + # If you don't pass any fields to values(), relation fields are + # returned as "foo_id" keys, not "foo". For consistency, you should be + # able to pass "foo_id" in the fields list and have it work, too. We + # actually allow both "foo" and "foo_id". + + # The *_id version is returned by default. + self.assertTrue('note_id' in ExtraInfo.objects.values()[0]) + + # You can also pass it in explicitly. + self.assertValueQuerysetEqual( + ExtraInfo.objects.values('note_id'), + [{'note_id': 1}, {'note_id': 2}] + ) + + # ...or use the field name. + self.assertValueQuerysetEqual( + ExtraInfo.objects.values('note'), + [{'note': 1}, {'note': 2}] + ) + + def test_ticket2902(self): + # Parameters can be given to extra_select, *if* you use a SortedDict. + + # (First we need to know which order the keys fall in "naturally" on + # your system, so we can put things in the wrong way around from + # normal. A normal dict would thus fail.) + s = [('a', '%s'), ('b', '%s')] + params = ['one', 'two'] + if {'a': 1, 'b': 2}.keys() == ['a', 'b']: + s.reverse() + params.reverse() + + # This slightly odd comparison works around the fact that PostgreSQL will + # return 'one' and 'two' as strings, not Unicode objects. It's a side-effect of + # using constants here and not a real concern. + d = Item.objects.extra(select=SortedDict(s), select_params=params).values('a', 'b')[0] + self.assertEqual(d, {'a': u'one', 'b': u'two'}) + + # Order by the number of tags attached to an item. + l = Item.objects.extra(select={'count': 'select count(*) from queries_item_tags where queries_item_tags.item_id = queries_item.id'}).order_by('-count') + self.assertEqual([o.count for o in l], [2, 2, 1, 0]) + + def test_ticket6154(self): + # Multiple filter statements are joined using "AND" all the time. + + self.assertQuerysetEqual( + Author.objects.filter(id=self.a1.id).filter(Q(extra__note=self.n1)|Q(item__note=self.n3)), + [''] + ) + self.assertQuerysetEqual( + Author.objects.filter(Q(extra__note=self.n1)|Q(item__note=self.n3)).filter(id=self.a1.id), + [''] + ) + + def test_ticket6981(self): + self.assertQuerysetEqual( + Tag.objects.select_related('parent').order_by('name'), + ['', '', '', '', ''] + ) + + def test_ticket9926(self): + self.assertQuerysetEqual( + Tag.objects.select_related("parent", "category").order_by('name'), + ['', '', '', '', ''] + ) + self.assertQuerysetEqual( + Tag.objects.select_related('parent', "parent__category").order_by('name'), + ['', '', '', '', ''] + ) + + def test_tickets_6180_6203(self): + # Dates with limits and/or counts + self.assertEqual(Item.objects.count(), 4) + self.assertEqual(Item.objects.dates('created', 'month').count(), 1) + self.assertEqual(Item.objects.dates('created', 'day').count(), 2) + self.assertEqual(len(Item.objects.dates('created', 'day')), 2) + self.assertEqual(Item.objects.dates('created', 'day')[0], datetime.datetime(2007, 12, 19, 0, 0)) + + def test_tickets_7087_12242(self): + # Dates with extra select columns + self.assertQuerysetEqual( + Item.objects.dates('created', 'day').extra(select={'a': 1}), + ['datetime.datetime(2007, 12, 19, 0, 0)', 'datetime.datetime(2007, 12, 20, 0, 0)'] + ) + self.assertQuerysetEqual( + Item.objects.extra(select={'a': 1}).dates('created', 'day'), + ['datetime.datetime(2007, 12, 19, 0, 0)', 'datetime.datetime(2007, 12, 20, 0, 0)'] + ) + + name="one" + self.assertQuerysetEqual( + Item.objects.dates('created', 'day').extra(where=['name=%s'], params=[name]), + ['datetime.datetime(2007, 12, 19, 0, 0)'] + ) + + self.assertQuerysetEqual( + Item.objects.extra(where=['name=%s'], params=[name]).dates('created', 'day'), + ['datetime.datetime(2007, 12, 19, 0, 0)'] + ) + + def test_ticket7155(self): + # Nullable dates + self.assertQuerysetEqual( + Item.objects.dates('modified', 'day'), + ['datetime.datetime(2007, 12, 19, 0, 0)'] + ) + + def test_ticket7098(self): + # Make sure semi-deprecated ordering by related models syntax still + # works. + self.assertValueQuerysetEqual( + Item.objects.values('note__note').order_by('queries_note.note', 'id'), + [{'note__note': u'n2'}, {'note__note': u'n3'}, {'note__note': u'n3'}, {'note__note': u'n3'}] + ) + + def test_ticket7096(self): + # Make sure exclude() with multiple conditions continues to work. + self.assertQuerysetEqual( + Tag.objects.filter(parent=self.t1, name='t3').order_by('name'), + [''] + ) + self.assertQuerysetEqual( + Tag.objects.exclude(parent=self.t1, name='t3').order_by('name'), + ['', '', '', ''] + ) + self.assertQuerysetEqual( + Item.objects.exclude(tags__name='t1', name='one').order_by('name').distinct(), + ['', '', ''] + ) + self.assertQuerysetEqual( + Item.objects.filter(name__in=['three', 'four']).exclude(tags__name='t1').order_by('name'), + ['', ''] + ) + + # More twisted cases, involving nested negations. + self.assertQuerysetEqual( + Item.objects.exclude(~Q(tags__name='t1', name='one')), + [''] + ) + self.assertQuerysetEqual( + Item.objects.filter(~Q(tags__name='t1', name='one'), name='two'), + [''] + ) + self.assertQuerysetEqual( + Item.objects.exclude(~Q(tags__name='t1', name='one'), name='two'), + ['', '', ''] + ) + + def test_tickets_7204_7506(self): + # Make sure querysets with related fields can be pickled. If this + # doesn't crash, it's a Good Thing. + pickle.dumps(Item.objects.all()) + + def test_ticket7813(self): + # We should also be able to pickle things that use select_related(). + # The only tricky thing here is to ensure that we do the related + # selections properly after unpickling. + qs = Item.objects.select_related() + query = qs.query.get_compiler(qs.db).as_sql()[0] + query2 = pickle.loads(pickle.dumps(qs.query)) + self.assertEqual( + query2.get_compiler(qs.db).as_sql()[0], + query + ) + + def test_deferred_load_qs_pickling(self): + # Check pickling of deferred-loading querysets + qs = Item.objects.defer('name', 'creator') + q2 = pickle.loads(pickle.dumps(qs)) + self.assertEqual(list(qs), list(q2)) + q3 = pickle.loads(pickle.dumps(qs, pickle.HIGHEST_PROTOCOL)) + self.assertEqual(list(qs), list(q3)) + + def test_ticket7277(self): + self.assertQuerysetEqual( + self.n1.annotation_set.filter(Q(tag=self.t5) | Q(tag__children=self.t5) | Q(tag__children__children=self.t5)), + [''] + ) + + def test_tickets_7448_7707(self): + # Complex objects should be converted to strings before being used in + # lookups. + self.assertQuerysetEqual( + Item.objects.filter(created__in=[self.time1, self.time2]), + ['', ''] + ) + + def test_ticket7235(self): + # An EmptyQuerySet should not raise exceptions if it is filtered. + q = EmptyQuerySet() + self.assertQuerysetEqual(q.all(), []) + self.assertQuerysetEqual(q.filter(x=10), []) + self.assertQuerysetEqual(q.exclude(y=3), []) + self.assertQuerysetEqual(q.complex_filter({'pk': 1}), []) + self.assertQuerysetEqual(q.select_related('spam', 'eggs'), []) + self.assertQuerysetEqual(q.annotate(Count('eggs')), []) + self.assertQuerysetEqual(q.order_by('-pub_date', 'headline'), []) + self.assertQuerysetEqual(q.distinct(), []) + self.assertQuerysetEqual( + q.extra(select={'is_recent': "pub_date > '2006-01-01'"}), + [] + ) + q.query.low_mark = 1 + self.assertRaisesMessage( + AssertionError, + 'Cannot change a query once a slice has been taken', + q.extra, select={'is_recent': "pub_date > '2006-01-01'"} + ) + self.assertQuerysetEqual(q.reverse(), []) + self.assertQuerysetEqual(q.defer('spam', 'eggs'), []) + self.assertQuerysetEqual(q.only('spam', 'eggs'), []) + + def test_ticket7791(self): + # There were "issues" when ordering and distinct-ing on fields related + # via ForeignKeys. + self.assertEqual( + len(Note.objects.order_by('extrainfo__info').distinct()), + 3 + ) + + # Pickling of DateQuerySets used to fail + qs = Item.objects.dates('created', 'month') + _ = pickle.loads(pickle.dumps(qs)) + + def test_ticket9997(self): + # If a ValuesList or Values queryset is passed as an inner query, we + # make sure it's only requesting a single value and use that as the + # thing to select. + self.assertQuerysetEqual( + Tag.objects.filter(name__in=Tag.objects.filter(parent=self.t1).values('name')), + ['', ''] + ) + + # Multi-valued values() and values_list() querysets should raise errors. + self.assertRaisesMessage( + TypeError, + 'Cannot use a multi-field ValuesQuerySet as a filter value.', + lambda: Tag.objects.filter(name__in=Tag.objects.filter(parent=self.t1).values('name', 'id')) + ) + self.assertRaisesMessage( + TypeError, + 'Cannot use a multi-field ValuesListQuerySet as a filter value.', + lambda: Tag.objects.filter(name__in=Tag.objects.filter(parent=self.t1).values_list('name', 'id')) + ) + + def test_ticket9985(self): + # qs.values_list(...).values(...) combinations should work. + self.assertValueQuerysetEqual( + Note.objects.values_list("note", flat=True).values("id").order_by("id"), + [{'id': 1}, {'id': 2}, {'id': 3}] + ) + self.assertQuerysetEqual( + Annotation.objects.filter(notes__in=Note.objects.filter(note="n1").values_list('note').values('id')), + [''] + ) + + def test_ticket10205(self): + # When bailing out early because of an empty "__in" filter, we need + # to set things up correctly internally so that subqueries can continue properly. + self.assertEqual(Tag.objects.filter(name__in=()).update(name="foo"), 0) + + def test_ticket10432(self): + # Testing an empty "__in" filter with a generator as the value. + def f(): + return iter([]) + n_obj = Note.objects.all()[0] + def g(): + for i in [n_obj.pk]: + yield i + self.assertQuerysetEqual(Note.objects.filter(pk__in=f()), []) + self.assertEqual(list(Note.objects.filter(pk__in=g())), [n_obj]) + + def test_ticket10742(self): + # Queries used in an __in clause don't execute subqueries + + subq = Author.objects.filter(num__lt=3000) + qs = Author.objects.filter(pk__in=subq) + self.assertQuerysetEqual(qs, ['', '']) + + # The subquery result cache should not be populated + self.assertTrue(subq._result_cache is None) + + subq = Author.objects.filter(num__lt=3000) + qs = Author.objects.exclude(pk__in=subq) + self.assertQuerysetEqual(qs, ['', '']) + + # The subquery result cache should not be populated + self.assertTrue(subq._result_cache is None) + + subq = Author.objects.filter(num__lt=3000) + self.assertQuerysetEqual( + Author.objects.filter(Q(pk__in=subq) & Q(name='a1')), + [''] + ) + + # The subquery result cache should not be populated + self.assertTrue(subq._result_cache is None) + + def test_ticket7076(self): + # Excluding shouldn't eliminate NULL entries. + self.assertQuerysetEqual( + Item.objects.exclude(modified=self.time1).order_by('name'), + ['', '', ''] + ) + self.assertQuerysetEqual( + Tag.objects.exclude(parent__name=self.t1.name), + ['', '', ''] + ) + + def test_ticket7181(self): + # Ordering by related tables should accomodate nullable fields (this + # test is a little tricky, since NULL ordering is database dependent. + # Instead, we just count the number of results). + self.assertEqual(len(Tag.objects.order_by('parent__name')), 5) + + # Empty querysets can be merged with others. + self.assertQuerysetEqual( + Note.objects.none() | Note.objects.all(), + ['', '', ''] + ) + self.assertQuerysetEqual( + Note.objects.all() | Note.objects.none(), + ['', '', ''] + ) + self.assertQuerysetEqual(Note.objects.none() & Note.objects.all(), []) + self.assertQuerysetEqual(Note.objects.all() & Note.objects.none(), []) + + def test_ticket9411(self): + # Make sure bump_prefix() (an internal Query method) doesn't (re-)break. It's + # sufficient that this query runs without error. + qs = Tag.objects.values_list('id', flat=True).order_by('id') + qs.query.bump_prefix() + first = qs[0] + self.assertEqual(list(qs), range(first, first+5)) + + def test_ticket8439(self): + # Complex combinations of conjunctions, disjunctions and nullable + # relations. + self.assertQuerysetEqual( + Author.objects.filter(Q(item__note__extrainfo=self.e2)|Q(report=self.r1, name='xyz')), + [''] + ) + self.assertQuerysetEqual( + Author.objects.filter(Q(report=self.r1, name='xyz')|Q(item__note__extrainfo=self.e2)), + [''] + ) + self.assertQuerysetEqual( + Annotation.objects.filter(Q(tag__parent=self.t1)|Q(notes__note='n1', name='a1')), + [''] + ) + xx = ExtraInfo.objects.create(info='xx', note=self.n3) + self.assertQuerysetEqual( + Note.objects.filter(Q(extrainfo__author=self.a1)|Q(extrainfo=xx)), + ['', ''] + ) + xx.delete() + q = Note.objects.filter(Q(extrainfo__author=self.a1)|Q(extrainfo=xx)).query + self.assertEqual( + len([x[2] for x in q.alias_map.values() if x[2] == q.LOUTER and q.alias_refcount[x[1]]]), + 1 + ) + + +class Queries2Tests(TestCase): + def setUp(self): + Number.objects.create(num=4) + Number.objects.create(num=8) + Number.objects.create(num=12) + + def test_ticket4289(self): + # A slight variation on the restricting the filtering choices by the + # lookup constraints. + self.assertQuerysetEqual(Number.objects.filter(num__lt=4), []) + self.assertQuerysetEqual(Number.objects.filter(num__gt=8, num__lt=12), []) + self.assertQuerysetEqual( + Number.objects.filter(num__gt=8, num__lt=13), + [''] + ) + self.assertQuerysetEqual( + Number.objects.filter(Q(num__lt=4) | Q(num__gt=8, num__lt=12)), + [] + ) + self.assertQuerysetEqual( + Number.objects.filter(Q(num__gt=8, num__lt=12) | Q(num__lt=4)), + [] + ) + self.assertQuerysetEqual( + Number.objects.filter(Q(num__gt=8) & Q(num__lt=12) | Q(num__lt=4)), + [] + ) + self.assertQuerysetEqual( + Number.objects.filter(Q(num__gt=7) & Q(num__lt=12) | Q(num__lt=4)), + [''] + ) + + def test_ticket12239(self): + # Float was being rounded to integer on gte queries on integer field. Tests + # show that gt, lt, gte, and lte work as desired. Note that the fix changes + # get_prep_lookup for gte and lt queries only. + self.assertQuerysetEqual( + Number.objects.filter(num__gt=11.9), + [''] + ) + self.assertQuerysetEqual(Number.objects.filter(num__gt=12), []) + self.assertQuerysetEqual(Number.objects.filter(num__gt=12.0), []) + self.assertQuerysetEqual(Number.objects.filter(num__gt=12.1), []) + self.assertQuerysetEqual( + Number.objects.filter(num__lt=12), + ['', ''] + ) + self.assertQuerysetEqual( + Number.objects.filter(num__lt=12.0), + ['', ''] + ) + self.assertQuerysetEqual( + Number.objects.filter(num__lt=12.1), + ['', '', ''] + ) + self.assertQuerysetEqual( + Number.objects.filter(num__gte=11.9), + [''] + ) + self.assertQuerysetEqual( + Number.objects.filter(num__gte=12), + [''] + ) + self.assertQuerysetEqual( + Number.objects.filter(num__gte=12.0), + [''] + ) + self.assertQuerysetEqual(Number.objects.filter(num__gte=12.1), []) + self.assertQuerysetEqual(Number.objects.filter(num__gte=12.9), []) + self.assertQuerysetEqual( + Number.objects.filter(num__lte=11.9), + ['', ''] + ) + self.assertQuerysetEqual( + Number.objects.filter(num__lte=12), + ['', '', ''] + ) + self.assertQuerysetEqual( + Number.objects.filter(num__lte=12.0), + ['', '', ''] + ) + self.assertQuerysetEqual( + Number.objects.filter(num__lte=12.1), + ['', '', ''] + ) + self.assertQuerysetEqual( + Number.objects.filter(num__lte=12.9), + ['', '', ''] + ) + + def test_ticket7411(self): + # Saving to db must work even with partially read result set in another + # cursor. + for num in range(2 * ITER_CHUNK_SIZE + 1): + _ = Number.objects.create(num=num) + + for i, obj in enumerate(Number.objects.all()): + obj.save() + if i > 10: break + + def test_ticket7759(self): + # Count should work with a partially read result set. + count = Number.objects.count() + qs = Number.objects.all() + def run(): + for obj in qs: + return qs.count() == count + self.assertTrue(run()) + + +class Queries3Tests(BaseQuerysetTest): + def test_ticket7107(self): + # This shouldn't create an infinite loop. + self.assertQuerysetEqual(Valid.objects.all(), []) + + def test_ticket8683(self): + # Raise proper error when a DateQuerySet gets passed a wrong type of + # field + self.assertRaisesMessage( + AssertionError, + "'name' isn't a DateField.", + Item.objects.dates, 'name', 'month' + ) + +class Queries4Tests(BaseQuerysetTest): + def setUp(self): + generic = NamedCategory.objects.create(name="Generic") + self.t1 = Tag.objects.create(name='t1', category=generic) + + n1 = Note.objects.create(note='n1', misc='foo', id=1) + n2 = Note.objects.create(note='n2', misc='bar', id=2) + + e1 = ExtraInfo.objects.create(info='e1', note=n1) + e2 = ExtraInfo.objects.create(info='e2', note=n2) + + a1 = Author.objects.create(name='a1', num=1001, extra=e1) + a3 = Author.objects.create(name='a3', num=3003, extra=e2) + + Report.objects.create(name='r1', creator=a1) + Report.objects.create(name='r2', creator=a3) + Report.objects.create(name='r3') + + def test_ticket7095(self): + # Updates that are filtered on the model being updated are somewhat + # tricky in MySQL. This exercises that case. + ManagedModel.objects.create(data='mm1', tag=self.t1, public=True) + self.assertEqual(ManagedModel.objects.update(data='mm'), 1) + + # A values() or values_list() query across joined models must use outer + # joins appropriately. + # Note: In Oracle, we expect a null CharField to return u'' instead of + # None. + if connection.features.interprets_empty_strings_as_nulls: + expected_null_charfield_repr = u'' + else: + expected_null_charfield_repr = None + self.assertValueQuerysetEqual( + Report.objects.values_list("creator__extra__info", flat=True).order_by("name"), + [u'e1', u'e2', expected_null_charfield_repr], + ) + + # Similarly for select_related(), joins beyond an initial nullable join + # must use outer joins so that all results are included. + self.assertQuerysetEqual( + Report.objects.select_related("creator", "creator__extra").order_by("name"), + ['', '', ''] + ) + + # When there are multiple paths to a table from another table, we have + # to be careful not to accidentally reuse an inappropriate join when + # using select_related(). We used to return the parent's Detail record + # here by mistake. + + d1 = Detail.objects.create(data="d1") + d2 = Detail.objects.create(data="d2") + m1 = Member.objects.create(name="m1", details=d1) + m2 = Member.objects.create(name="m2", details=d2) + Child.objects.create(person=m2, parent=m1) + obj = m1.children.select_related("person__details")[0] + self.assertEqual(obj.person.details.data, u'd2') + + def test_order_by_resetting(self): + # Calling order_by() with no parameters removes any existing ordering on the + # model. But it should still be possible to add new ordering after that. + qs = Author.objects.order_by().order_by('name') + self.assertTrue('ORDER BY' in qs.query.get_compiler(qs.db).as_sql()[0]) + + def test_ticket10181(self): + # Avoid raising an EmptyResultSet if an inner query is probably + # empty (and hence, not executed). + self.assertQuerysetEqual( + Tag.objects.filter(id__in=Tag.objects.filter(id__in=[])), + [] + ) + + +class Queries5Tests(TestCase): + def setUp(self): + # Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering + # will be rank3, rank2, rank1. + n1 = Note.objects.create(note='n1', misc='foo', id=1) + n2 = Note.objects.create(note='n2', misc='bar', id=2) + e1 = ExtraInfo.objects.create(info='e1', note=n1) + e2 = ExtraInfo.objects.create(info='e2', note=n2) + a1 = Author.objects.create(name='a1', num=1001, extra=e1) + a2 = Author.objects.create(name='a2', num=2002, extra=e1) + a3 = Author.objects.create(name='a3', num=3003, extra=e2) + self.rank1 = Ranking.objects.create(rank=2, author=a2) + Ranking.objects.create(rank=1, author=a3) + Ranking.objects.create(rank=3, author=a1) + + def test_ordering(self): + # Cross model ordering is possible in Meta, too. + self.assertQuerysetEqual( + Ranking.objects.all(), + ['', '', ''] + ) + self.assertQuerysetEqual( + Ranking.objects.all().order_by('rank'), + ['', '', ''] + ) + + + # Ordering of extra() pieces is possible, too and you can mix extra + # fields and model fields in the ordering. + self.assertQuerysetEqual( + Ranking.objects.extra(tables=['django_site'], order_by=['-django_site.id', 'rank']), + ['', '', ''] + ) + + qs = Ranking.objects.extra(select={'good': 'case when rank > 2 then 1 else 0 end'}) + self.assertEqual( + [o.good for o in qs.extra(order_by=('-good',))], + [True, False, False] + ) + self.assertQuerysetEqual( + qs.extra(order_by=('-good', 'id')), + ['', '', ''] + ) + + # Despite having some extra aliases in the query, we can still omit + # them in a values() query. + dicts = qs.values('id', 'rank').order_by('id') + self.assertEqual( + [d.items()[1] for d in dicts], + [('rank', 2), ('rank', 1), ('rank', 3)] + ) + + def test_ticket7256(self): + # An empty values() call includes all aliases, including those from an + # extra() + qs = Ranking.objects.extra(select={'good': 'case when rank > 2 then 1 else 0 end'}) + dicts = qs.values().order_by('id') + for d in dicts: del d['id']; del d['author_id'] + self.assertEqual( + [sorted(d.items()) for d in dicts], + [[('good', 0), ('rank', 2)], [('good', 0), ('rank', 1)], [('good', 1), ('rank', 3)]] + ) + + def test_ticket7045(self): + # Extra tables used to crash SQL construction on the second use. + qs = Ranking.objects.extra(tables=['django_site']) + qs.query.get_compiler(qs.db).as_sql() + qs.query.get_compiler(qs.db).as_sql() # test passes if this doesn't raise an exception. + + def test_ticket9848(self): + # Make sure that updates which only filter on sub-tables don't inadvertently + # update the wrong records (bug #9848). + + # Make sure that the IDs from different tables don't happen to match. + self.assertQuerysetEqual( + Ranking.objects.filter(author__name='a1'), + [''] + ) + self.assertEqual( + Ranking.objects.filter(author__name='a1').update(rank='4'), + 1 + ) + r = Ranking.objects.filter(author__name='a1')[0] + self.assertNotEqual(r.id, r.author.id) + self.assertEqual(r.rank, 4) + r.rank = 3 + r.save() + self.assertQuerysetEqual( + Ranking.objects.all(), + ['', '', ''] + ) + + def test_ticket5261(self): + self.assertQuerysetEqual( + Note.objects.exclude(Q()), + ['', ''] + ) + + +class SelectRelatedTests(TestCase): + def test_tickets_3045_3288(self): + # Once upon a time, select_related() with circular relations would loop + # infinitely if you forgot to specify "depth". Now we set an arbitrary + # default upper bound. + self.assertQuerysetEqual(X.objects.all(), []) + self.assertQuerysetEqual(X.objects.select_related(), []) + + +class SubclassFKTests(TestCase): + def test_ticket7778(self): + # Model subclasses could not be deleted if a nullable foreign key + # relates to a model that relates back. + + num_celebs = Celebrity.objects.count() + tvc = TvChef.objects.create(name="Huey") + self.assertEqual(Celebrity.objects.count(), num_celebs + 1) + Fan.objects.create(fan_of=tvc) + Fan.objects.create(fan_of=tvc) + tvc.delete() + + # The parent object should have been deleted as well. + self.assertEqual(Celebrity.objects.count(), num_celebs) + + +class CustomPkTests(TestCase): + def test_ticket7371(self): + self.assertQuerysetEqual(Related.objects.order_by('custom'), []) + + +class NullableRelOrderingTests(TestCase): + def test_ticket10028(self): + # Ordering by model related to nullable relations(!) should use outer + # joins, so that all results are included. + _ = Plaything.objects.create(name="p1") + self.assertQuerysetEqual( + Plaything.objects.all(), + [''] + ) + + +class DisjunctiveFilterTests(TestCase): + def setUp(self): + self.n1 = Note.objects.create(note='n1', misc='foo', id=1) + ExtraInfo.objects.create(info='e1', note=self.n1) + + def test_ticket7872(self): + # Another variation on the disjunctive filtering theme. + + # For the purposes of this regression test, it's important that there is no + # Join object releated to the LeafA we create. + LeafA.objects.create(data='first') + self.assertQuerysetEqual(LeafA.objects.all(), ['']) + self.assertQuerysetEqual( + LeafA.objects.filter(Q(data='first')|Q(join__b__data='second')), + [''] + ) + + def test_ticket8283(self): + # Checking that applying filters after a disjunction works correctly. + self.assertQuerysetEqual( + (ExtraInfo.objects.filter(note=self.n1)|ExtraInfo.objects.filter(info='e2')).filter(note=self.n1), + [''] + ) + self.assertQuerysetEqual( + (ExtraInfo.objects.filter(info='e2')|ExtraInfo.objects.filter(note=self.n1)).filter(note=self.n1), + [''] + ) + + +class Queries6Tests(TestCase): + def setUp(self): + generic = NamedCategory.objects.create(name="Generic") + t1 = Tag.objects.create(name='t1', category=generic) + t2 = Tag.objects.create(name='t2', parent=t1, category=generic) + t3 = Tag.objects.create(name='t3', parent=t1) + t4 = Tag.objects.create(name='t4', parent=t3) + t5 = Tag.objects.create(name='t5', parent=t3) + n1 = Note.objects.create(note='n1', misc='foo', id=1) + ann1 = Annotation.objects.create(name='a1', tag=t1) + ann1.notes.add(n1) + ann2 = Annotation.objects.create(name='a2', tag=t4) + + # FIXME!! This next test causes really weird PostgreSQL behaviour, but it's + # only apparent much later when the full test suite runs. I don't understand + # what's going on here yet. + ##def test_slicing_and_cache_interaction(self): + ## # We can do slicing beyond what is currently in the result cache, + ## # too. + ## + ## # We need to mess with the implementation internals a bit here to decrease the + ## # cache fill size so that we don't read all the results at once. + ## from django.db.models import query + ## query.ITER_CHUNK_SIZE = 2 + ## qs = Tag.objects.all() + ## + ## # Fill the cache with the first chunk. + ## self.assertTrue(bool(qs)) + ## self.assertEqual(len(qs._result_cache), 2) + ## + ## # Query beyond the end of the cache and check that it is filled out as required. + ## self.assertEqual(repr(qs[4]), '') + ## self.assertEqual(len(qs._result_cache), 5) + ## + ## # But querying beyond the end of the result set will fail. + ## self.assertRaises(IndexError, lambda: qs[100]) + + def test_parallel_iterators(self): + # Test that parallel iterators work. + qs = Tag.objects.all() + i1, i2 = iter(qs), iter(qs) + self.assertEqual(repr(i1.next()), '') + self.assertEqual(repr(i1.next()), '') + self.assertEqual(repr(i2.next()), '') + self.assertEqual(repr(i2.next()), '') + self.assertEqual(repr(i2.next()), '') + self.assertEqual(repr(i1.next()), '') + + qs = X.objects.all() + self.assertEqual(bool(qs), False) + self.assertEqual(bool(qs), False) + + def test_nested_queries_sql(self): + # Nested queries should not evaluate the inner query as part of constructing the + # SQL (so we should see a nested query here, indicated by two "SELECT" calls). + qs = Annotation.objects.filter(notes__in=Note.objects.filter(note="xyzzy")) + self.assertEqual( + qs.query.get_compiler(qs.db).as_sql()[0].count('SELECT'), + 2 + ) + + def test_tickets_8921_9188(self): + # Incorrect SQL was being generated for certain types of exclude() + # queries that crossed multi-valued relations (#8921, #9188 and some + # pre-emptively discovered cases). + + self.assertQuerysetEqual( + PointerA.objects.filter(connection__pointerb__id=1), + [] + ) + self.assertQuerysetEqual( + PointerA.objects.exclude(connection__pointerb__id=1), + [] + ) + + self.assertQuerysetEqual( + Tag.objects.exclude(children=None), + ['', ''] + ) + + # This example is tricky because the parent could be NULL, so only checking + # parents with annotations omits some results (tag t1, in this case). + self.assertQuerysetEqual( + Tag.objects.exclude(parent__annotation__name="a1"), + ['', '', ''] + ) + + # The annotation->tag link is single values and tag->children links is + # multi-valued. So we have to split the exclude filter in the middle and then + # optimise the inner query without losing results. + self.assertQuerysetEqual( + Annotation.objects.exclude(tag__children__name="t2"), + [''] + ) + + # Nested queries are possible (although should be used with care, since they have + # performance problems on backends like MySQL. + + self.assertQuerysetEqual( + Annotation.objects.filter(notes__in=Note.objects.filter(note="n1")), + [''] + ) + + def test_ticket3739(self): + # The all() method on querysets returns a copy of the queryset. + q1 = Tag.objects.order_by('name') + self.assertNotEqual(id(q1), id(q1.all())) + + +class GeneratorExpressionTests(TestCase): + def test_ticket10432(self): + # Using an empty generator expression as the rvalue for an "__in" + # lookup is legal. + self.assertQuerysetEqual( + Note.objects.filter(pk__in=(x for x in ())), + [] + ) + + +class ComparisonTests(TestCase): + def setUp(self): + self.n1 = Note.objects.create(note='n1', misc='foo', id=1) + e1 = ExtraInfo.objects.create(info='e1', note=self.n1) + self.a2 = Author.objects.create(name='a2', num=2002, extra=e1) + + def test_ticket8597(self): + # Regression tests for case-insensitive comparisons + _ = Item.objects.create(name="a_b", created=datetime.datetime.now(), creator=self.a2, note=self.n1) + _ = Item.objects.create(name="x%y", created=datetime.datetime.now(), creator=self.a2, note=self.n1) + self.assertQuerysetEqual( + Item.objects.filter(name__iexact="A_b"), + [''] + ) + self.assertQuerysetEqual( + Item.objects.filter(name__iexact="x%Y"), + [''] + ) + self.assertQuerysetEqual( + Item.objects.filter(name__istartswith="A_b"), + [''] + ) + self.assertQuerysetEqual( + Item.objects.filter(name__iendswith="A_b"), + [''] + ) + + +class ExistsSql(TestCase): + def setUp(self): + settings.DEBUG = True + + def test_exists(self): + self.assertFalse(Tag.objects.exists()) + # Ok - so the exist query worked - but did it include too many columns? + self.assertTrue("id" not in connection.queries[-1]['sql'] and "name" not in connection.queries[-1]['sql']) + + def tearDown(self): + settings.DEBUG = False -from models import Tag, Annotation, DumbCategory, Note, ExtraInfo, Number class QuerysetOrderedTests(unittest.TestCase): """ @@ -64,6 +1408,7 @@ def test_sliced_delete(self): # Refs #10099 self.assertFalse(connections[DEFAULT_DB_ALIAS].features.allow_sliced_subqueries) + class CloneTests(TestCase): def test_evaluated_queryset_as_argument(self): "#13227 -- If a queryset is already evaluated, it can still be used as a query arg" @@ -87,8 +1432,123 @@ class EmptyQuerySetTests(TestCase): def test_emptyqueryset_values(self): "#14366 -- calling .values() on an EmptyQuerySet and then cloning that should not cause an error" self.assertEqual(list(Number.objects.none().values('num').order_by('num')), []) - + def test_values_subquery(self): self.assertQuerysetEqual( - Number.objects.filter(pk__in=Number.objects.none().values("pk")), [] + Number.objects.filter(pk__in=Number.objects.none().values("pk")), + [] + ) + + +class WeirdQuerysetSlicingTests(BaseQuerysetTest): + def setUp(self): + Number.objects.create(num=1) + Number.objects.create(num=2) + + Article.objects.create(name='one', created=datetime.datetime.now()) + Article.objects.create(name='two', created=datetime.datetime.now()) + Article.objects.create(name='three', created=datetime.datetime.now()) + Article.objects.create(name='four', created=datetime.datetime.now()) + + def test_tickets_7698_10202(self): + # People like to slice with '0' as the high-water mark. + self.assertQuerysetEqual(Article.objects.all()[0:0], []) + self.assertQuerysetEqual(Article.objects.all()[0:0][:10], []) + self.assertEqual(Article.objects.all()[:0].count(), 0) + self.assertRaisesMessage( + AssertionError, + 'Cannot change a query once a slice has been taken.', + Article.objects.all()[:0].latest, 'created' ) + + +class EscapingTests(TestCase): + def test_ticket_7302(self): + # Reserved names are appropriately escaped + _ = ReservedName.objects.create(name='a',order=42) + ReservedName.objects.create(name='b',order=37) + self.assertQuerysetEqual( + ReservedName.objects.all().order_by('order'), + ['', ''] + ) + self.assertQuerysetEqual( + ReservedName.objects.extra(select={'stuff':'name'}, order_by=('order','stuff')), + ['', ''] + ) + + +# In Python 2.6 beta releases, exceptions raised in __len__ are swallowed +# (Python issue 1242657), so these cases return an empty list, rather than +# raising an exception. Not a lot we can do about that, unfortunately, due to +# the way Python handles list() calls internally. Thus, we skip the tests for +# Python 2.6. +if sys.version_info[:2] != (2, 6): + class OrderingLoopTests(TestCase): + def test_infinite_loop(self): + # If you're not careful, it's possible to introduce infinite loops via + # default ordering on foreign keys in a cycle. We detect that. + self.assertRaises( + FieldError, + 'Infinite loop caused by ordering.', + LoopX.objects.all + ) + self.assertRaisesMessage( + FieldError, + 'Infinite loop caused by ordering.', + LoopZ.objects.all + ) + + # Note that this doesn't cause an infinite loop, since the default + # ordering on the Tag model is empty (and thus defaults to using "id" + # for the related field). + self.assertEqual(len(Tag.objects.order_by('parent')), 5) + + # ... but you can still order in a non-recursive fashion amongst linked + # fields (the previous test failed because the default ordering was + # recursive). + self.assertQuerysetEqual( + LoopX.objects.all().order_by('y__x__y__x__id'), + [] + ) + + +# When grouping without specifying ordering, we add an explicit "ORDER BY NULL" +# portion in MySQL to prevent unnecessary sorting. +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == "django.db.backends.mysql": + class GroupingTests(TestCase): + def test_null_ordering_added(self): + query = Tag.objects.values_list('parent_id', flat=True).order_by().query + query.group_by = ['parent_id'] + sql = query.get_compiler(DEFAULT_DB_ALIAS).as_sql()[0] + fragment = "ORDER BY " + pos = sql.find(fragment) + self.assertEqual(sql.find(fragment, pos + 1), -1) + self.assertEqual(sql.find("NULL", pos + len(fragment)), pos + len(fragment)) + + +# Sqlite 3 does not support passing in more than 1000 parameters except by +# changing a parameter at compilation time. +if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != "django.db.backends.sqlite3": + class InLookupTests(TestCase): + def test_ticket14244(self): + # Test that the "in" lookup works with lists of 1000 items or more. + Number.objects.all().delete() + numbers = range(2500) + for num in numbers: + _ = Number.objects.create(num=num) + self.assertEqual( + Number.objects.filter(num__in=numbers[:1000]).count(), + 1000 + ) + self.assertEqual( + Number.objects.filter(num__in=numbers[:1001]).count(), + 1001 + ) + self.assertEqual( + Number.objects.filter(num__in=numbers[:2000]).count(), + 2000 + ) + self.assertEqual( + Number.objects.filter(num__in=numbers).count(), + 2500 + ) From 0476014a768d450cab1f1415dc3397f06f12d7b0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 19 Oct 2010 05:11:56 +0000 Subject: [PATCH 354/902] [1.2.X] Migrated many-to-one doctests. Thanks to George Sakkis for the patch. Backport of r14281 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14282 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/many_to_one/models.py | 287 ------------------- tests/modeltests/many_to_one/tests.py | 371 +++++++++++++++++++++++++ 2 files changed, 371 insertions(+), 287 deletions(-) create mode 100644 tests/modeltests/many_to_one/tests.py diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index c6be981453c4..b4a0f377ab66 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -24,290 +24,3 @@ def __unicode__(self): class Meta: ordering = ('headline',) - -__test__ = {'API_TESTS':""" -# Create a few Reporters. ->>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com') ->>> r.save() - ->>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com') ->>> r2.save() - -# Create an Article. ->>> from datetime import datetime ->>> a = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r) ->>> a.save() - ->>> a.reporter.id -1 - ->>> a.reporter - - -# Article objects have access to their related Reporter objects. ->>> r = a.reporter - -# These are strings instead of unicode strings because that's what was used in -# the creation of this reporter (and we haven't refreshed the data from the -# database, which always returns unicode strings). ->>> r.first_name, r.last_name -('John', 'Smith') - -# Create an Article via the Reporter object. ->>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29)) ->>> new_article - ->>> new_article.reporter.id -1 - -# Create a new article, and add it to the article set. ->>> new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17)) ->>> r.article_set.add(new_article2) ->>> new_article2.reporter.id -1 ->>> r.article_set.all() -[, , ] - -# Add the same article to a different article set - check that it moves. ->>> r2.article_set.add(new_article2) ->>> new_article2.reporter.id -2 - -# Adding an object of the wrong type raises TypeError ->>> r.article_set.add(r2) -Traceback (most recent call last): -... -TypeError: 'Article' instance expected - ->>> r.article_set.all() -[, ] ->>> r2.article_set.all() -[] - -# Assign the article to the reporter directly using the descriptor ->>> new_article2.reporter = r ->>> new_article2.save() ->>> new_article2.reporter - ->>> new_article2.reporter.id -1 ->>> r.article_set.all() -[, , ] ->>> r2.article_set.all() -[] - -# Set the article back again using set descriptor. ->>> r2.article_set = [new_article, new_article2] ->>> r.article_set.all() -[] ->>> r2.article_set.all() -[, ] - -# Funny case - assignment notation can only go so far; because the -# ForeignKey cannot be null, existing members of the set must remain ->>> r.article_set = [new_article] ->>> r.article_set.all() -[, ] ->>> r2.article_set.all() -[] - -# Reporter cannot be null - there should not be a clear or remove method ->>> hasattr(r2.article_set, 'remove') -False ->>> hasattr(r2.article_set, 'clear') -False - -# Reporter objects have access to their related Article objects. ->>> r.article_set.all() -[, ] - ->>> r.article_set.filter(headline__startswith='This') -[] - ->>> r.article_set.count() -2 - ->>> r2.article_set.count() -1 - -# Get articles by id ->>> Article.objects.filter(id__exact=1) -[] ->>> Article.objects.filter(pk=1) -[] - -# Query on an article property ->>> Article.objects.filter(headline__startswith='This') -[] - -# The API automatically follows relationships as far as you need. -# Use double underscores to separate relationships. -# This works as many levels deep as you want. There's no limit. -# Find all Articles for any Reporter whose first name is "John". ->>> Article.objects.filter(reporter__first_name__exact='John') -[, ] - -# Check that implied __exact also works ->>> Article.objects.filter(reporter__first_name='John') -[, ] - -# Query twice over the related field. ->>> Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') -[, ] - -# The underlying query only makes one join when a related table is referenced twice. ->>> queryset = Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') ->>> sql = queryset.query.get_compiler(queryset.db).as_sql()[0] ->>> sql.count('INNER JOIN') -1 - -# The automatically joined table has a predictable name. ->>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_reporter.last_name='Smith'"]) -[, ] - -# And should work fine with the unicode that comes out of -# forms.Form.cleaned_data ->>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_reporter.last_name='%s'" % u'Smith']) -[, ] - -# Find all Articles for the Reporter whose ID is 1. -# Use direct ID check, pk check, and object comparison ->>> Article.objects.filter(reporter__id__exact=1) -[, ] ->>> Article.objects.filter(reporter__pk=1) -[, ] ->>> Article.objects.filter(reporter=1) -[, ] ->>> Article.objects.filter(reporter=r) -[, ] - ->>> Article.objects.filter(reporter__in=[1,2]).distinct() -[, , ] ->>> Article.objects.filter(reporter__in=[r,r2]).distinct() -[, , ] - -# You can also use a queryset instead of a literal list of instances. -# The queryset must be reduced to a list of values using values(), -# then converted into a query ->>> Article.objects.filter(reporter__in=Reporter.objects.filter(first_name='John').values('pk').query).distinct() -[, ] - -# You need two underscores between "reporter" and "id" -- not one. ->>> Article.objects.filter(reporter_id__exact=1) -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'reporter_id' into field. Choices are: headline, id, pub_date, reporter - -# You need to specify a comparison clause ->>> Article.objects.filter(reporter_id=1) -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'reporter_id' into field. Choices are: headline, id, pub_date, reporter - -# You can also instantiate an Article by passing -# the Reporter's ID instead of a Reporter object. ->>> a3 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id=r.id) ->>> a3.save() ->>> a3.reporter.id -1 ->>> a3.reporter - - -# Similarly, the reporter ID can be a string. ->>> a4 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id="1") ->>> a4.save() ->>> a4.reporter - - -# Reporters can be queried ->>> Reporter.objects.filter(id__exact=1) -[] ->>> Reporter.objects.filter(pk=1) -[] ->>> Reporter.objects.filter(first_name__startswith='John') -[] - -# Reporters can query in opposite direction of ForeignKey definition ->>> Reporter.objects.filter(article__id__exact=1) -[] ->>> Reporter.objects.filter(article__pk=1) -[] ->>> Reporter.objects.filter(article=1) -[] ->>> Reporter.objects.filter(article=a) -[] - ->>> Reporter.objects.filter(article__in=[1,4]).distinct() -[] ->>> Reporter.objects.filter(article__in=[1,a3]).distinct() -[] ->>> Reporter.objects.filter(article__in=[a,a3]).distinct() -[] - ->>> Reporter.objects.filter(article__headline__startswith='This') -[, , ] ->>> Reporter.objects.filter(article__headline__startswith='This').distinct() -[] - -# Counting in the opposite direction works in conjunction with distinct() ->>> Reporter.objects.filter(article__headline__startswith='This').count() -3 ->>> Reporter.objects.filter(article__headline__startswith='This').distinct().count() -1 - -# Queries can go round in circles. ->>> Reporter.objects.filter(article__reporter__first_name__startswith='John') -[, , , ] ->>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct() -[] ->>> Reporter.objects.filter(article__reporter__exact=r).distinct() -[] - -# Regression for #12876 -- Model methods that include queries that -# recursive don't cause recursion depth problems under deepcopy. ->>> r.cached_query = Article.objects.filter(reporter=r) ->>> from copy import deepcopy ->>> deepcopy(r) - - -# Check that implied __exact also works. ->>> Reporter.objects.filter(article__reporter=r).distinct() -[] - -# It's possible to use values() calls across many-to-one relations. (Note, too, that we clear the ordering here so as not to drag the 'headline' field into the columns being used to determine uniqueness.) ->>> d = {'reporter__first_name': u'John', 'reporter__last_name': u'Smith'} ->>> list(Article.objects.filter(reporter=r).distinct().order_by().values('reporter__first_name', 'reporter__last_name')) == [d] -True - -# If you delete a reporter, his articles will be deleted. ->>> Article.objects.all() -[, , , , ] ->>> Reporter.objects.order_by('first_name') -[, ] ->>> r2.delete() ->>> Article.objects.all() -[, , , ] ->>> Reporter.objects.order_by('first_name') -[] - -# You can delete using a JOIN in the query. ->>> Reporter.objects.filter(article__headline__startswith='This').delete() ->>> Reporter.objects.all() -[] ->>> Article.objects.all() -[] - -# Check that Article.objects.select_related().dates() works properly when -# there are multiple Articles with the same date but different foreign-key -# objects (Reporters). ->>> r1 = Reporter.objects.create(first_name='Mike', last_name='Royko', email='royko@suntimes.com') ->>> r2 = Reporter.objects.create(first_name='John', last_name='Kass', email='jkass@tribune.com') ->>> a1 = Article.objects.create(headline='First', pub_date=datetime(1980, 4, 23), reporter=r1) ->>> a2 = Article.objects.create(headline='Second', pub_date=datetime(1980, 4, 23), reporter=r2) ->>> Article.objects.select_related().dates('pub_date', 'day') -[datetime.datetime(1980, 4, 23, 0, 0)] ->>> Article.objects.select_related().dates('pub_date', 'month') -[datetime.datetime(1980, 4, 1, 0, 0)] ->>> Article.objects.select_related().dates('pub_date', 'year') -[datetime.datetime(1980, 1, 1, 0, 0)] -"""} diff --git a/tests/modeltests/many_to_one/tests.py b/tests/modeltests/many_to_one/tests.py new file mode 100644 index 000000000000..53306b70af56 --- /dev/null +++ b/tests/modeltests/many_to_one/tests.py @@ -0,0 +1,371 @@ +from datetime import datetime +from django.test import TestCase +from django.core.exceptions import FieldError +from models import Article, Reporter + +class ManyToOneTests(TestCase): + + def setUp(self): + # Create a few Reporters. + self.r = Reporter(first_name='John', last_name='Smith', email='john@example.com') + self.r.save() + self.r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com') + self.r2.save() + # Create an Article. + self.a = Article(id=None, headline="This is a test", + pub_date=datetime(2005, 7, 27), reporter=self.r) + self.a.save() + + def test_get(self): + # Article objects have access to their related Reporter objects. + r = self.a.reporter + self.assertEqual(r.id, self.r.id) + # These are strings instead of unicode strings because that's what was used in + # the creation of this reporter (and we haven't refreshed the data from the + # database, which always returns unicode strings). + self.assertEqual((r.first_name, self.r.last_name), ('John', 'Smith')) + + def test_create(self): + # You can also instantiate an Article by passing the Reporter's ID + # instead of a Reporter object. + a3 = Article(id=None, headline="Third article", + pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) + a3.save() + self.assertEqual(a3.reporter.id, self.r.id) + + # Similarly, the reporter ID can be a string. + a4 = Article(id=None, headline="Fourth article", + pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) + a4.save() + self.assertEqual(repr(a4.reporter), "") + + def test_add(self): + # Create an Article via the Reporter object. + new_article = self.r.article_set.create(headline="John's second story", + pub_date=datetime(2005, 7, 29)) + self.assertEqual(repr(new_article), "") + self.assertEqual(new_article.reporter.id, self.r.id) + + # Create a new article, and add it to the article set. + new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17)) + self.r.article_set.add(new_article2) + self.assertEqual(new_article2.reporter.id, self.r.id) + self.assertQuerysetEqual(self.r.article_set.all(), + [ + "", + "", + "", + ]) + + # Add the same article to a different article set - check that it moves. + self.r2.article_set.add(new_article2) + self.assertEqual(new_article2.reporter.id, self.r2.id) + self.assertQuerysetEqual(self.r2.article_set.all(), [""]) + + # Adding an object of the wrong type raises TypeError. + self.assertRaises(TypeError, self.r.article_set.add, self.r2) + self.assertQuerysetEqual(self.r.article_set.all(), + [ + "", + "", + ]) + + def test_assign(self): + new_article = self.r.article_set.create(headline="John's second story", + pub_date=datetime(2005, 7, 29)) + new_article2 = self.r2.article_set.create(headline="Paul's story", + pub_date=datetime(2006, 1, 17)) + # Assign the article to the reporter directly using the descriptor. + new_article2.reporter = self.r + new_article2.save() + self.assertEqual(repr(new_article2.reporter), "") + self.assertEqual(new_article2.reporter.id, self.r.id) + self.assertQuerysetEqual(self.r.article_set.all(), [ + "", + "", + "", + ]) + self.assertQuerysetEqual(self.r2.article_set.all(), []) + # Set the article back again using set descriptor. + self.r2.article_set = [new_article, new_article2] + self.assertQuerysetEqual(self.r.article_set.all(), [""]) + self.assertQuerysetEqual(self.r2.article_set.all(), + [ + "", + "", + ]) + + # Funny case - assignment notation can only go so far; because the + # ForeignKey cannot be null, existing members of the set must remain. + self.r.article_set = [new_article] + self.assertQuerysetEqual(self.r.article_set.all(), + [ + "", + "", + ]) + self.assertQuerysetEqual(self.r2.article_set.all(), [""]) + # Reporter cannot be null - there should not be a clear or remove method + self.assertFalse(hasattr(self.r2.article_set, 'remove')) + self.assertFalse(hasattr(self.r2.article_set, 'clear')) + + def test_selects(self): + new_article = self.r.article_set.create(headline="John's second story", + pub_date=datetime(2005, 7, 29)) + new_article2 = self.r2.article_set.create(headline="Paul's story", + pub_date=datetime(2006, 1, 17)) + # Reporter objects have access to their related Article objects. + self.assertQuerysetEqual(self.r.article_set.all(), [ + "", + "", + ]) + self.assertQuerysetEqual(self.r.article_set.filter(headline__startswith='This'), + [""]) + self.assertEqual(self.r.article_set.count(), 2) + self.assertEqual(self.r2.article_set.count(), 1) + # Get articles by id + self.assertQuerysetEqual(Article.objects.filter(id__exact=self.a.id), + [""]) + self.assertQuerysetEqual(Article.objects.filter(pk=self.a.id), + [""]) + # Query on an article property + self.assertQuerysetEqual(Article.objects.filter(headline__startswith='This'), + [""]) + # The API automatically follows relationships as far as you need. + # Use double underscores to separate relationships. + # This works as many levels deep as you want. There's no limit. + # Find all Articles for any Reporter whose first name is "John". + self.assertQuerysetEqual(Article.objects.filter(reporter__first_name__exact='John'), + [ + "", + "", + ]) + # Check that implied __exact also works + self.assertQuerysetEqual(Article.objects.filter(reporter__first_name='John'), + [ + "", + "", + ]) + # Query twice over the related field. + self.assertQuerysetEqual( + Article.objects.filter(reporter__first_name__exact='John', + reporter__last_name__exact='Smith'), + [ + "", + "", + ]) + # The underlying query only makes one join when a related table is referenced twice. + queryset = Article.objects.filter(reporter__first_name__exact='John', + reporter__last_name__exact='Smith') + self.assertEqual(queryset.query.get_compiler(queryset.db).as_sql()[0].count('INNER JOIN'), 1) + + # The automatically joined table has a predictable name. + self.assertQuerysetEqual( + Article.objects.filter(reporter__first_name__exact='John').extra( + where=["many_to_one_reporter.last_name='Smith'"]), + [ + "", + "", + ]) + # ... and should work fine with the unicode that comes out of forms.Form.cleaned_data + self.assertQuerysetEqual( + Article.objects.filter(reporter__first_name__exact='John' + ).extra(where=["many_to_one_reporter.last_name='%s'" % u'Smith']), + [ + "", + "", + ]) + # Find all Articles for a Reporter. + # Use direct ID check, pk check, and object comparison + self.assertQuerysetEqual( + Article.objects.filter(reporter__id__exact=self.r.id), + [ + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter__pk=self.r.id), + [ + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter=self.r.id), + [ + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter=self.r), + [ + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter__in=[self.r.id,self.r2.id]).distinct(), + [ + "", + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter__in=[self.r,self.r2]).distinct(), + [ + "", + "", + "", + ]) + # You can also use a queryset instead of a literal list of instances. + # The queryset must be reduced to a list of values using values(), + # then converted into a query + self.assertQuerysetEqual( + Article.objects.filter( + reporter__in=Reporter.objects.filter(first_name='John').values('pk').query + ).distinct(), + [ + "", + "", + ]) + # You need two underscores between "reporter" and "id" -- not one. + self.assertRaises(FieldError, Article.objects.filter, reporter_id__exact=self.r.id) + # You need to specify a comparison clause + self.assertRaises(FieldError, Article.objects.filter, reporter_id=self.r.id) + + def test_reverse_selects(self): + a3 = Article.objects.create(id=None, headline="Third article", + pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) + a4 = Article.objects.create(id=None, headline="Fourth article", + pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) + # Reporters can be queried + self.assertQuerysetEqual(Reporter.objects.filter(id__exact=self.r.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(pk=self.r.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(first_name__startswith='John'), + [""]) + # Reporters can query in opposite direction of ForeignKey definition + self.assertQuerysetEqual(Reporter.objects.filter(article__id__exact=self.a.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(article__pk=self.a.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(article=self.a.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(article=self.a), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__in=[self.a.id,a3.id]).distinct(), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__in=[self.a.id,a3]).distinct(), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__in=[self.a,a3]).distinct(), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__headline__startswith='T'), + ["", ""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__headline__startswith='T').distinct(), + [""]) + + # Counting in the opposite direction works in conjunction with distinct() + self.assertEqual( + Reporter.objects.filter(article__headline__startswith='T').count(), 2) + self.assertEqual( + Reporter.objects.filter(article__headline__startswith='T').distinct().count(), 1) + + # Queries can go round in circles. + self.assertQuerysetEqual( + Reporter.objects.filter(article__reporter__first_name__startswith='John'), + [ + "", + "", + "", + ]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct(), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__reporter__exact=self.r).distinct(), + [""]) + + # Check that implied __exact also works. + self.assertQuerysetEqual( + Reporter.objects.filter(article__reporter=self.r).distinct(), + [""]) + + # It's possible to use values() calls across many-to-one relations. + # (Note, too, that we clear the ordering here so as not to drag the + # 'headline' field into the columns being used to determine uniqueness) + d = {'reporter__first_name': u'John', 'reporter__last_name': u'Smith'} + self.assertEqual([d], + list(Article.objects.filter(reporter=self.r).distinct().order_by() + .values('reporter__first_name', 'reporter__last_name'))) + + def test_select_related(self): + # Check that Article.objects.select_related().dates() works properly when + # there are multiple Articles with the same date but different foreign-key + # objects (Reporters). + r1 = Reporter.objects.create(first_name='Mike', last_name='Royko', email='royko@suntimes.com') + r2 = Reporter.objects.create(first_name='John', last_name='Kass', email='jkass@tribune.com') + a1 = Article.objects.create(headline='First', pub_date=datetime(1980, 4, 23), reporter=r1) + a2 = Article.objects.create(headline='Second', pub_date=datetime(1980, 4, 23), reporter=r2) + self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'day')), + [ + datetime(1980, 4, 23, 0, 0), + datetime(2005, 7, 27, 0, 0), + ]) + self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'month')), + [ + datetime(1980, 4, 1, 0, 0), + datetime(2005, 7, 1, 0, 0), + ]) + self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'year')), + [ + datetime(1980, 1, 1, 0, 0), + datetime(2005, 1, 1, 0, 0), + ]) + + def test_delete(self): + new_article = self.r.article_set.create(headline="John's second story", + pub_date=datetime(2005, 7, 29)) + new_article2 = self.r2.article_set.create(headline="Paul's story", + pub_date=datetime(2006, 1, 17)) + a3 = Article.objects.create(id=None, headline="Third article", + pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) + a4 = Article.objects.create(id=None, headline="Fourth article", + pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) + # If you delete a reporter, his articles will be deleted. + self.assertQuerysetEqual(Article.objects.all(), + [ + "", + "", + "", + "", + "", + ]) + self.assertQuerysetEqual(Reporter.objects.order_by('first_name'), + [ + "", + "", + ]) + self.r2.delete() + self.assertQuerysetEqual(Article.objects.all(), + [ + "", + "", + "", + "", + ]) + self.assertQuerysetEqual(Reporter.objects.order_by('first_name'), + [""]) + # You can delete using a JOIN in the query. + Reporter.objects.filter(article__headline__startswith='This').delete() + self.assertQuerysetEqual(Reporter.objects.all(), []) + self.assertQuerysetEqual(Article.objects.all(), []) + + def test_regression_12876(self): + # Regression for #12876 -- Model methods that include queries that + # recursive don't cause recursion depth problems under deepcopy. + self.r.cached_query = Article.objects.filter(reporter=self.r) + from copy import deepcopy + self.assertEqual(repr(deepcopy(self.r)), "") From ed195a6fd399d9d236300ecc4858a45bfbaf6659 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 19 Oct 2010 12:28:48 +0000 Subject: [PATCH 355/902] [1.2.X] Migrated many-to-many doctests. Thanks to George Sakkis for the patch. Backport of r14285 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14286 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/many_to_many/models.py | 248 --------------- tests/modeltests/many_to_many/tests.py | 384 ++++++++++++++++++++++++ 2 files changed, 384 insertions(+), 248 deletions(-) create mode 100644 tests/modeltests/many_to_many/tests.py diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py index 21b4e82aa023..96636da2b217 100644 --- a/tests/modeltests/many_to_many/models.py +++ b/tests/modeltests/many_to_many/models.py @@ -27,251 +27,3 @@ def __unicode__(self): class Meta: ordering = ('headline',) - -__test__ = {'API_TESTS':""" -# Create a couple of Publications. ->>> p1 = Publication(id=None, title='The Python Journal') ->>> p1.save() ->>> p2 = Publication(id=None, title='Science News') ->>> p2.save() ->>> p3 = Publication(id=None, title='Science Weekly') ->>> p3.save() - -# Create an Article. ->>> a1 = Article(id=None, headline='Django lets you build Web apps easily') - -# You can't associate it with a Publication until it's been saved. ->>> a1.publications.add(p1) -Traceback (most recent call last): -... -ValueError: 'Article' instance needs to have a primary key value before a many-to-many relationship can be used. - -# Save it! ->>> a1.save() - -# Associate the Article with a Publication. ->>> a1.publications.add(p1) - -# Create another Article, and set it to appear in both Publications. ->>> a2 = Article(id=None, headline='NASA uses Python') ->>> a2.save() ->>> a2.publications.add(p1, p2) ->>> a2.publications.add(p3) - -# Adding a second time is OK ->>> a2.publications.add(p3) - -# Adding an object of the wrong type raises TypeError ->>> a2.publications.add(a1) -Traceback (most recent call last): -... -TypeError: 'Publication' instance expected - -# Add a Publication directly via publications.add by using keyword arguments. ->>> new_publication = a2.publications.create(title='Highlights for Children') - -# Article objects have access to their related Publication objects. ->>> a1.publications.all() -[] ->>> a2.publications.all() -[, , , ] - -# Publication objects have access to their related Article objects. ->>> p2.article_set.all() -[] ->>> p1.article_set.all() -[, ] ->>> Publication.objects.get(id=4).article_set.all() -[] - -# We can perform kwarg queries across m2m relationships ->>> Article.objects.filter(publications__id__exact=1) -[, ] ->>> Article.objects.filter(publications__pk=1) -[, ] ->>> Article.objects.filter(publications=1) -[, ] ->>> Article.objects.filter(publications=p1) -[, ] - ->>> Article.objects.filter(publications__title__startswith="Science") -[, ] - ->>> Article.objects.filter(publications__title__startswith="Science").distinct() -[] - -# The count() function respects distinct() as well. ->>> Article.objects.filter(publications__title__startswith="Science").count() -2 - ->>> Article.objects.filter(publications__title__startswith="Science").distinct().count() -1 - ->>> Article.objects.filter(publications__in=[1,2]).distinct() -[, ] ->>> Article.objects.filter(publications__in=[1,p2]).distinct() -[, ] ->>> Article.objects.filter(publications__in=[p1,p2]).distinct() -[, ] - -# Reverse m2m queries are supported (i.e., starting at the table that doesn't -# have a ManyToManyField). ->>> Publication.objects.filter(id__exact=1) -[] ->>> Publication.objects.filter(pk=1) -[] - ->>> Publication.objects.filter(article__headline__startswith="NASA") -[, , , ] - ->>> Publication.objects.filter(article__id__exact=1) -[] ->>> Publication.objects.filter(article__pk=1) -[] ->>> Publication.objects.filter(article=1) -[] ->>> Publication.objects.filter(article=a1) -[] - ->>> Publication.objects.filter(article__in=[1,2]).distinct() -[, , , ] ->>> Publication.objects.filter(article__in=[1,a2]).distinct() -[, , , ] ->>> Publication.objects.filter(article__in=[a1,a2]).distinct() -[, , , ] - -# Excluding a related item works as you would expect, too (although the SQL -# involved is a little complex). ->>> Article.objects.exclude(publications=p2) -[] - -# If we delete a Publication, its Articles won't be able to access it. ->>> p1.delete() ->>> Publication.objects.all() -[, , ] ->>> a1 = Article.objects.get(pk=1) ->>> a1.publications.all() -[] - -# If we delete an Article, its Publications won't be able to access it. ->>> a2.delete() ->>> Article.objects.all() -[] ->>> p2.article_set.all() -[] - -# Adding via the 'other' end of an m2m ->>> a4 = Article(headline='NASA finds intelligent life on Earth') ->>> a4.save() ->>> p2.article_set.add(a4) ->>> p2.article_set.all() -[] ->>> a4.publications.all() -[] - -# Adding via the other end using keywords ->>> new_article = p2.article_set.create(headline='Oxygen-free diet works wonders') ->>> p2.article_set.all() -[, ] ->>> a5 = p2.article_set.all()[1] ->>> a5.publications.all() -[] - -# Removing publication from an article: ->>> a4.publications.remove(p2) ->>> p2.article_set.all() -[] ->>> a4.publications.all() -[] - -# And from the other end ->>> p2.article_set.remove(a5) ->>> p2.article_set.all() -[] ->>> a5.publications.all() -[] - -# Relation sets can be assigned. Assignment clears any existing set members ->>> p2.article_set = [a4, a5] ->>> p2.article_set.all() -[, ] ->>> a4.publications.all() -[] ->>> a4.publications = [p3] ->>> p2.article_set.all() -[] ->>> a4.publications.all() -[] - -# Relation sets can be cleared: ->>> p2.article_set.clear() ->>> p2.article_set.all() -[] ->>> a4.publications.all() -[] - -# And you can clear from the other end ->>> p2.article_set.add(a4, a5) ->>> p2.article_set.all() -[, ] ->>> a4.publications.all() -[, ] ->>> a4.publications.clear() ->>> a4.publications.all() -[] ->>> p2.article_set.all() -[] - -# Relation sets can also be set using primary key values ->>> p2.article_set = [a4.id, a5.id] ->>> p2.article_set.all() -[, ] ->>> a4.publications.all() -[] ->>> a4.publications = [p3.id] ->>> p2.article_set.all() -[] ->>> a4.publications.all() -[] - -# Recreate the article and Publication we have deleted. ->>> p1 = Publication(id=None, title='The Python Journal') ->>> p1.save() ->>> a2 = Article(id=None, headline='NASA uses Python') ->>> a2.save() ->>> a2.publications.add(p1, p2, p3) - -# Bulk delete some Publications - references to deleted publications should go ->>> Publication.objects.filter(title__startswith='Science').delete() ->>> Publication.objects.all() -[, ] ->>> Article.objects.all() -[, , , ] ->>> a2.publications.all() -[] - -# Bulk delete some articles - references to deleted objects should go ->>> q = Article.objects.filter(headline__startswith='Django') ->>> print q -[] ->>> q.delete() - -# After the delete, the QuerySet cache needs to be cleared, and the referenced objects should be gone ->>> print q -[] ->>> p1.article_set.all() -[] - -# An alternate to calling clear() is to assign the empty set ->>> p1.article_set = [] ->>> p1.article_set.all() -[] - ->>> a2.publications = [p1, new_publication] ->>> a2.publications.all() -[, ] ->>> a2.publications = [] ->>> a2.publications.all() -[] - -"""} diff --git a/tests/modeltests/many_to_many/tests.py b/tests/modeltests/many_to_many/tests.py new file mode 100644 index 000000000000..39fe58125215 --- /dev/null +++ b/tests/modeltests/many_to_many/tests.py @@ -0,0 +1,384 @@ +from django.test import TestCase +from models import Article, Publication + +class ManyToManyTests(TestCase): + + def setUp(self): + # Create a couple of Publications. + self.p1 = Publication.objects.create(id=None, title='The Python Journal') + self.p2 = Publication.objects.create(id=None, title='Science News') + self.p3 = Publication.objects.create(id=None, title='Science Weekly') + self.p4 = Publication.objects.create(title='Highlights for Children') + + self.a1 = Article.objects.create(id=None, headline='Django lets you build Web apps easily') + self.a1.publications.add(self.p1) + + self.a2 = Article.objects.create(id=None, headline='NASA uses Python') + self.a2.publications.add(self.p1, self.p2, self.p3, self.p4) + + self.a3 = Article.objects.create(headline='NASA finds intelligent life on Earth') + self.a3.publications.add(self.p2) + + self.a4 = Article.objects.create(headline='Oxygen-free diet works wonders') + self.a4.publications.add(self.p2) + + def test_add(self): + # Create an Article. + a5 = Article(id=None, headline='Django lets you reate Web apps easily') + # You can't associate it with a Publication until it's been saved. + self.assertRaises(ValueError, getattr, a5, 'publications') + # Save it! + a5.save() + # Associate the Article with a Publication. + a5.publications.add(self.p1) + self.assertQuerysetEqual(a5.publications.all(), + ['']) + # Create another Article, and set it to appear in both Publications. + a6 = Article(id=None, headline='ESA uses Python') + a6.save() + a6.publications.add(self.p1, self.p2) + a6.publications.add(self.p3) + # Adding a second time is OK + a6.publications.add(self.p3) + self.assertQuerysetEqual(a6.publications.all(), + [ + '', + '', + '', + ]) + + # Adding an object of the wrong type raises TypeError + self.assertRaises(TypeError, a6.publications.add, a5) + # Add a Publication directly via publications.add by using keyword arguments. + p4 = a6.publications.create(title='Highlights for Adults') + self.assertQuerysetEqual(a6.publications.all(), + [ + '', + '', + '', + '', + ]) + + def test_reverse_add(self): + # Adding via the 'other' end of an m2m + a5 = Article(headline='NASA finds intelligent life on Mars') + a5.save() + self.p2.article_set.add(a5) + self.assertQuerysetEqual(self.p2.article_set.all(), + [ + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(a5.publications.all(), + ['']) + + # Adding via the other end using keywords + new_article = self.p2.article_set.create(headline='Carbon-free diet works wonders') + self.assertQuerysetEqual( + self.p2.article_set.all(), + [ + '', + '', + '', + '', + '', + ]) + a6 = self.p2.article_set.all()[3] + self.assertQuerysetEqual(a6.publications.all(), + [ + '', + '', + '', + '', + ]) + + def test_related_sets(self): + # Article objects have access to their related Publication objects. + self.assertQuerysetEqual(self.a1.publications.all(), + ['']) + self.assertQuerysetEqual(self.a2.publications.all(), + [ + '', + '', + '', + '', + ]) + # Publication objects have access to their related Article objects. + self.assertQuerysetEqual(self.p2.article_set.all(), + [ + '', + '', + '', + ]) + self.assertQuerysetEqual(self.p1.article_set.all(), + [ + '', + '', + ]) + self.assertQuerysetEqual(Publication.objects.get(id=self.p4.id).article_set.all(), + ['']) + + def test_selects(self): + # We can perform kwarg queries across m2m relationships + self.assertQuerysetEqual( + Article.objects.filter(publications__id__exact=self.p1.id), + [ + '', + '', + ]) + self.assertQuerysetEqual( + Article.objects.filter(publications__pk=self.p1.id), + [ + '', + '', + ]) + self.assertQuerysetEqual( + Article.objects.filter(publications=self.p1.id), + [ + '', + '', + ]) + self.assertQuerysetEqual( + Article.objects.filter(publications=self.p1), + [ + '', + '', + ]) + self.assertQuerysetEqual( + Article.objects.filter(publications__title__startswith="Science"), + [ + '', + '', + '', + '', + ]) + self.assertQuerysetEqual( + Article.objects.filter(publications__title__startswith="Science").distinct(), + [ + '', + '', + '', + ]) + + # The count() function respects distinct() as well. + self.assertEqual(Article.objects.filter(publications__title__startswith="Science").count(), 4) + self.assertEqual(Article.objects.filter(publications__title__startswith="Science").distinct().count(), 3) + self.assertQuerysetEqual( + Article.objects.filter(publications__in=[self.p1.id,self.p2.id]).distinct(), + [ + '', + '', + '', + '', + ]) + self.assertQuerysetEqual( + Article.objects.filter(publications__in=[self.p1.id,self.p2]).distinct(), + [ + '', + '', + '', + '', + ]) + self.assertQuerysetEqual( + Article.objects.filter(publications__in=[self.p1,self.p2]).distinct(), + [ + '', + '', + '', + '', + ]) + + # Excluding a related item works as you would expect, too (although the SQL + # involved is a little complex). + self.assertQuerysetEqual(Article.objects.exclude(publications=self.p2), + ['']) + + def test_reverse_selects(self): + # Reverse m2m queries are supported (i.e., starting at the table that + # doesn't have a ManyToManyField). + self.assertQuerysetEqual(Publication.objects.filter(id__exact=self.p1.id), + ['']) + self.assertQuerysetEqual(Publication.objects.filter(pk=self.p1.id), + ['']) + self.assertQuerysetEqual( + Publication.objects.filter(article__headline__startswith="NASA"), + [ + '', + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(Publication.objects.filter(article__id__exact=self.a1.id), + ['']) + self.assertQuerysetEqual(Publication.objects.filter(article__pk=self.a1.id), + ['']) + self.assertQuerysetEqual(Publication.objects.filter(article=self.a1.id), + ['']) + self.assertQuerysetEqual(Publication.objects.filter(article=self.a1), + ['']) + + self.assertQuerysetEqual( + Publication.objects.filter(article__in=[self.a1.id,self.a2.id]).distinct(), + [ + '', + '', + '', + '', + ]) + self.assertQuerysetEqual( + Publication.objects.filter(article__in=[self.a1.id,self.a2]).distinct(), + [ + '', + '', + '', + '', + ]) + self.assertQuerysetEqual( + Publication.objects.filter(article__in=[self.a1,self.a2]).distinct(), + [ + '', + '', + '', + '', + ]) + + def test_delete(self): + # If we delete a Publication, its Articles won't be able to access it. + self.p1.delete() + self.assertQuerysetEqual(Publication.objects.all(), + [ + '', + '', + '', + ]) + self.assertQuerysetEqual(self.a1.publications.all(), []) + # If we delete an Article, its Publications won't be able to access it. + self.a2.delete() + self.assertQuerysetEqual(Article.objects.all(), + [ + '', + '', + '', + ]) + self.assertQuerysetEqual(self.p2.article_set.all(), + [ + '', + '', + ]) + + def test_bulk_delete(self): + # Bulk delete some Publications - references to deleted publications should go + Publication.objects.filter(title__startswith='Science').delete() + self.assertQuerysetEqual(Publication.objects.all(), + [ + '', + '', + ]) + self.assertQuerysetEqual(Article.objects.all(), + [ + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(self.a2.publications.all(), + [ + '', + '', + ]) + + # Bulk delete some articles - references to deleted objects should go + q = Article.objects.filter(headline__startswith='Django') + self.assertQuerysetEqual(q, ['']) + q.delete() + # After the delete, the QuerySet cache needs to be cleared, + # and the referenced objects should be gone + self.assertQuerysetEqual(q, []) + self.assertQuerysetEqual(self.p1.article_set.all(), + ['']) + + def test_remove(self): + # Removing publication from an article: + self.assertQuerysetEqual(self.p2.article_set.all(), + [ + '', + '', + '', + ]) + self.a4.publications.remove(self.p2) + self.assertQuerysetEqual(self.p2.article_set.all(), + [ + '', + '', + ]) + self.assertQuerysetEqual(self.a4.publications.all(), []) + # And from the other end + self.p2.article_set.remove(self.a3) + self.assertQuerysetEqual(self.p2.article_set.all(), + [ + '', + ]) + self.assertQuerysetEqual(self.a3.publications.all(), []) + + def test_assign(self): + # Relation sets can be assigned. Assignment clears any existing set members + self.p2.article_set = [self.a4, self.a3] + self.assertQuerysetEqual(self.p2.article_set.all(), + [ + '', + '', + ]) + self.assertQuerysetEqual(self.a4.publications.all(), + ['']) + self.a4.publications = [self.p3.id] + self.assertQuerysetEqual(self.p2.article_set.all(), + ['']) + self.assertQuerysetEqual(self.a4.publications.all(), + ['']) + + # An alternate to calling clear() is to assign the empty set + self.p2.article_set = [] + self.assertQuerysetEqual(self.p2.article_set.all(), []) + self.a4.publications = [] + self.assertQuerysetEqual(self.a4.publications.all(), []) + + def test_assign_ids(self): + # Relation sets can also be set using primary key values + self.p2.article_set = [self.a4.id, self.a3.id] + self.assertQuerysetEqual(self.p2.article_set.all(), + [ + '', + '', + ]) + self.assertQuerysetEqual(self.a4.publications.all(), + ['']) + self.a4.publications = [self.p3.id] + self.assertQuerysetEqual(self.p2.article_set.all(), + ['']) + self.assertQuerysetEqual(self.a4.publications.all(), + ['']) + + def test_clear(self): + # Relation sets can be cleared: + self.p2.article_set.clear() + self.assertQuerysetEqual(self.p2.article_set.all(), []) + self.assertQuerysetEqual(self.a4.publications.all(), []) + + # And you can clear from the other end + self.p2.article_set.add(self.a3, self.a4) + self.assertQuerysetEqual(self.p2.article_set.all(), + [ + '', + '', + ]) + self.assertQuerysetEqual(self.a4.publications.all(), + [ + '', + ]) + self.a4.publications.clear() + self.assertQuerysetEqual(self.a4.publications.all(), []) + self.assertQuerysetEqual(self.p2.article_set.all(), + ['']) From 6a369d2ff385edd3bbc2b8e0ab659f8fdef767d3 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Tue, 19 Oct 2010 19:42:36 +0000 Subject: [PATCH 356/902] [1.2.X] Fixed errors introduced in r14280 when running Django tests under Python < 2.6. Backport of [14287] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14289 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/queries/tests.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index a5ab88927927..7302e700e4e9 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -1430,7 +1430,8 @@ def test_evaluated_queryset_as_argument(self): class EmptyQuerySetTests(TestCase): def test_emptyqueryset_values(self): - "#14366 -- calling .values() on an EmptyQuerySet and then cloning that should not cause an error" + # #14366 -- calling .values() on an EmptyQuerySet and then cloning that + # should not cause an error self.assertEqual(list(Number.objects.none().values('num').order_by('num')), []) def test_values_subquery(self): @@ -1483,11 +1484,19 @@ def test_ticket_7302(self): # the way Python handles list() calls internally. Thus, we skip the tests for # Python 2.6. if sys.version_info[:2] != (2, 6): - class OrderingLoopTests(TestCase): + class OrderingLoopTests(BaseQuerysetTest): + def setUp(self): + generic = NamedCategory.objects.create(name="Generic") + t1 = Tag.objects.create(name='t1', category=generic) + t2 = Tag.objects.create(name='t2', parent=t1, category=generic) + t3 = Tag.objects.create(name='t3', parent=t1) + t4 = Tag.objects.create(name='t4', parent=t3) + t5 = Tag.objects.create(name='t5', parent=t3) + def test_infinite_loop(self): # If you're not careful, it's possible to introduce infinite loops via # default ordering on foreign keys in a cycle. We detect that. - self.assertRaises( + self.assertRaisesMessage( FieldError, 'Infinite loop caused by ordering.', LoopX.objects.all From a7a91c2d40e31eb2e054a5c33e63324a80fe2d59 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 19 Oct 2010 21:45:51 +0000 Subject: [PATCH 357/902] [1.2.X] Fixed #14498 - Forms passed to FormWizard.process_step are not guaranteed to have cleaned_data Thanks to stas for the report. Backport of [14290] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14291 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/formtools/tests.py | 35 +++++++++++++++++++ django/contrib/formtools/wizard.py | 55 ++++++++++++++++++------------ 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/django/contrib/formtools/tests.py b/django/contrib/formtools/tests.py index bc65a60fbea1..7816c15bf511 100644 --- a/django/contrib/formtools/tests.py +++ b/django/contrib/formtools/tests.py @@ -1,5 +1,6 @@ import unittest from django import forms +from django.conf import settings from django.contrib.formtools import preview, wizard, utils from django import http from django.test import TestCase @@ -145,6 +146,9 @@ class WizardPageOneForm(forms.Form): class WizardPageTwoForm(forms.Form): field = forms.CharField() +class WizardPageThreeForm(forms.Form): + field = forms.CharField() + class WizardClass(wizard.FormWizard): def render_template(self, *args, **kw): return http.HttpResponse("") @@ -161,6 +165,15 @@ def __init__(self, POST=None): self._dont_enforce_csrf_checks = True class WizardTests(TestCase): + + def setUp(self): + # Use a known SECRET_KEY to make security_hash tests deterministic + self.old_SECRET_KEY = settings.SECRET_KEY + settings.SECRET_KEY = "123" + + def tearDown(self): + settings.SECRET_KEY = self.old_SECRET_KEY + def test_step_starts_at_zero(self): """ step should be zero for the first form @@ -179,3 +192,25 @@ def test_step_increments(self): response = wizard(request) self.assertEquals(1, wizard.step) + def test_14498(self): + """ + Regression test for ticket #14498. + """ + that = self + reached = [False] + + class WizardWithProcessStep(WizardClass): + def process_step(self, request, form, step): + reached[0] = True + that.assertTrue(hasattr(form, 'cleaned_data')) + + wizard = WizardWithProcessStep([WizardPageOneForm, + WizardPageTwoForm, + WizardPageThreeForm]) + data = {"0-field": "test", + "1-field": "test2", + "hash_0": "2fdbefd4c0cad51509478fbacddf8b13", + "wizard_step": "1"} + wizard(DummyRequest(POST=data)) + self.assertTrue(reached[0]) + diff --git a/django/contrib/formtools/wizard.py b/django/contrib/formtools/wizard.py index 32e27df57451..97d2fb8e342d 100644 --- a/django/contrib/formtools/wizard.py +++ b/django/contrib/formtools/wizard.py @@ -68,39 +68,50 @@ def __call__(self, request, *args, **kwargs): if current_step >= self.num_steps(): raise Http404('Step %s does not exist' % current_step) - # For each previous step, verify the hash and process. - # TODO: Move "hash_%d" to a method to make it configurable. - for i in range(current_step): - form = self.get_form(i, request.POST) - if request.POST.get("hash_%d" % i, '') != self.security_hash(request, form): - return self.render_hash_failure(request, i) - self.process_step(request, form, i) - # Process the current step. If it's valid, go to the next step or call # done(), depending on whether any steps remain. if request.method == 'POST': form = self.get_form(current_step, request.POST) else: form = self.get_form(current_step) + if form.is_valid(): + # Validate all the forms. If any of them fail validation, that + # must mean the validator relied on some other input, such as + # an external Web site. + + # It is also possible that validation might fail under certain + # attack situations: an attacker might be able to bypass previous + # stages, and generate correct security hashes for all the + # skipped stages by virtue of: + # 1) having filled out an identical form which doesn't have the + # validation (and does something different at the end), + # 2) or having filled out a previous version of the same form + # which had some validation missing, + # 3) or previously having filled out the form when they had + # more privileges than they do now. + # + # Since the hashes only take into account values, and not other + # other validation the form might do, we must re-do validation + # now for security reasons. + current_form_list = [self.get_form(i, request.POST) for i in range(current_step)] + + for i, f in enumerate(current_form_list): + if request.POST.get("hash_%d" % i, '') != self.security_hash(request, f): + return self.render_hash_failure(request, i) + + if not f.is_valid(): + return self.render_revalidation_failure(request, i, f) + else: + self.process_step(request, f, i) + + # Now progress to processing this step: self.process_step(request, form, current_step) next_step = current_step + 1 - # If this was the last step, validate all of the forms one more - # time, as a sanity check, and call done(). - num = self.num_steps() - if next_step == num: - final_form_list = [self.get_form(i, request.POST) for i in range(num)] - - # Validate all the forms. If any of them fail validation, that - # must mean the validator relied on some other input, such as - # an external Web site. - for i, f in enumerate(final_form_list): - if not f.is_valid(): - return self.render_revalidation_failure(request, i, f) - return self.done(request, final_form_list) - # Otherwise, move along to the next step. + if current_step == self.num_steps(): + return self.done(request, final_form_list) else: form = self.get_form(next_step) self.step = current_step = next_step From f71f31228339fadd6a4d9c11d8755cfb420152bf Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 20 Oct 2010 08:02:07 +0000 Subject: [PATCH 358/902] [1.2.X] Fixed bug and test failure introducted in [14290] Thanks Russell for alerting me. Backport of [14294] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14295 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/formtools/wizard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/contrib/formtools/wizard.py b/django/contrib/formtools/wizard.py index 97d2fb8e342d..f86dbad70b7f 100644 --- a/django/contrib/formtools/wizard.py +++ b/django/contrib/formtools/wizard.py @@ -110,8 +110,8 @@ def __call__(self, request, *args, **kwargs): next_step = current_step + 1 - if current_step == self.num_steps(): - return self.done(request, final_form_list) + if next_step == self.num_steps(): + return self.done(request, current_form_list) else: form = self.get_form(next_step) self.step = current_step = next_step From c2cb82b3c713ddcd33e547049edd3b850cee960c Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Wed, 20 Oct 2010 21:08:50 +0000 Subject: [PATCH 359/902] [1.2.X] Fixed #14493 -- Corrected use of the wrong attribute in the model validation example pseudo-code. Thanks to wogan for the report and patch. Backport of [14300] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14301 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/instances.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index b11a7e193d61..fb951ce6a7bb 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -70,7 +70,7 @@ Example:: try: article.full_clean() except ValidationError, e: - # Do something based on the errors contained in e.error_dict. + # Do something based on the errors contained in e.message_dict. # Display them to a user, or handle them programatically. The first step ``full_clean()`` performs is to clean each individual field. From 1160d81e44876f02cdb42ce7500ba1dd43067fae Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Wed, 20 Oct 2010 21:19:50 +0000 Subject: [PATCH 360/902] [1.2.X] Fixed #14432 -- Added an import statement which was previously implied in tutorial 3's URLconf to make it as explicit as possible for new users. Thanks to gorus for the report and andrews for the patch. Backport of [14302] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14303 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial03.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index af686bcbdee4..5b2bf0ccec32 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -498,9 +498,12 @@ Here's what happens if a user goes to "/polls/34/" in this system: remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for further processing. -Now that we've decoupled that, we need to decouple the 'polls.urls' +Now that we've decoupled that, we need to decouple the ``polls.urls`` URLconf by removing the leading "polls/" from each line, and removing the -lines registering the admin site:: +lines registering the admin site. Your ``polls.urls`` file should now look like +this:: + + from django.conf.urls.defaults import * urlpatterns = patterns('polls.views', (r'^$', 'index'), From b855dbf676a496c2d3ac1ea6bec5ebe8e4cc5590 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Wed, 20 Oct 2010 22:15:26 +0000 Subject: [PATCH 361/902] [1.2.X] Fixed #14477 -- Corrects several instances of "add" and "remove" m2m_changed signal actions not having been updated to "pre_add", "post_add", etc. Thanks to slink and andrews for reporting several instances of it, and andrews for the draft patch. Backport of [14304] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14305 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/signals.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 9badf0eea5a2..4bc1f3f59cb9 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -213,18 +213,19 @@ Arguments sent with this signal: Sent *after* the relation is cleared ``reverse`` - Indicates which side of the relation is updated (i.e., if it is the - forward or reverse relation that is being modified). + Indicates which side of the relation is updated (i.e., if it is the + forward or reverse relation that is being modified). ``model`` The class of the objects that are added to, removed from or cleared from the relation. ``pk_set`` - With the ``"add"`` and ``"remove"`` action, this is a list of - primary key values that have been added to or removed from the relation. + For the ``pre_add``, ``post_add``, ``pre_remove`` and ``post_remove`` + actions, this is a list of primary key values that have been added to + or removed from the relation. - For the ``"clear"`` action, this is ``None``. + For the ``pre_clear`` and ``post_clear`` actions, this is ``None``. For example, if a ``Pizza`` can have multiple ``Topping`` objects, modeled like this: @@ -255,7 +256,7 @@ the arguments sent to a :data:`m2m_changed` handler would be: ``instance`` ``p`` (the ``Pizza`` instance being modified) - ``action`` ``"add"`` + ``action`` ``"pre_add"`` (followed by a separate signal with ``"post_add"``) ``reverse`` ``False`` (``Pizza`` contains the :class:`ManyToManyField`, so this call modifies the forward relation) @@ -281,7 +282,7 @@ the arguments sent to a :data:`m2m_changed` handler would be: ``instance`` ``t`` (the ``Topping`` instance being modified) - ``action`` ``"remove"`` + ``action`` ``"pre_remove"`` (followed by a separate signal with ``"post_remove"``) ``reverse`` ``True`` (``Pizza`` contains the :class:`ManyToManyField`, so this call modifies the reverse relation) From 0df4c1c411db33bd5785cf29486320287b206c59 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 21 Oct 2010 17:37:38 +0000 Subject: [PATCH 362/902] [1.2.X] Ensure the mutliple_database tests leave the settings in the same state they found them. Backport of [14313]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14314 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/multiple_database/tests.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py index 2547e1eae2d5..f27d8b5a28f5 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/regressiontests/multiple_database/tests.py @@ -1547,13 +1547,17 @@ def test_dumpdata(self): command_output = new_io.getvalue().strip() self.assertTrue('"email": "alice@example.com",' in command_output) +_missing = object() class UserProfileTestCase(TestCase): def setUp(self): - self.old_auth_profile_module = getattr(settings, 'AUTH_PROFILE_MODULE', None) + self.old_auth_profile_module = getattr(settings, 'AUTH_PROFILE_MODULE', _missing) settings.AUTH_PROFILE_MODULE = 'multiple_database.UserProfile' def tearDown(self): - settings.AUTH_PROFILE_MODULE = self.old_auth_profile_module + if self.old_auth_profile_module is _missing: + del settings.AUTH_PROFILE_MODULE + else: + settings.AUTH_PROFILE_MODULE = self.old_auth_profile_module def test_user_profiles(self): From 7f404e26289e4676478f9096a61b7bc2cf56cdf2 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Thu, 21 Oct 2010 21:21:43 +0000 Subject: [PATCH 363/902] [1.2.X] Revert [13850], this was a new feature not a bugfix. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14316 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/defaultfilters.py | 16 +++------------- django/utils/http.py | 4 ++-- docs/ref/templates/builtins.txt | 13 ------------- tests/regressiontests/templates/filters.py | 4 ---- 4 files changed, 5 insertions(+), 32 deletions(-) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 55e19472e867..80002c3b0b9e 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -291,20 +291,10 @@ def upper(value): upper.is_safe = False upper = stringfilter(upper) -def urlencode(value, safe=None): - """ - Escapes a value for use in a URL. - - Takes an optional ``safe`` parameter used to determine the characters which - should not be escaped by Django's ``urlquote`` method. If not provided, the - default safe characters will be used (but an empty string can be provided - when *all* characters should be escaped). - """ +def urlencode(value): + """Escapes a value for use in a URL.""" from django.utils.http import urlquote - kwargs = {} - if safe is not None: - kwargs['safe'] = safe - return urlquote(value, **kwargs) + return urlquote(value) urlencode.is_safe = False urlencode = stringfilter(urlencode) diff --git a/django/utils/http.py b/django/utils/http.py index 610fcbf685b4..f0b1af9c586d 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -14,7 +14,7 @@ def urlquote(url, safe='/'): can safely be used as part of an argument to a subsequent iri_to_uri() call without double-quoting occurring. """ - return force_unicode(urllib.quote(smart_str(url), smart_str(safe))) + return force_unicode(urllib.quote(smart_str(url), safe)) urlquote = allow_lazy(urlquote, unicode) @@ -25,7 +25,7 @@ def urlquote_plus(url, safe=''): returned string can safely be used as part of an argument to a subsequent iri_to_uri() call without double-quoting occurring. """ - return force_unicode(urllib.quote_plus(smart_str(url), smart_str(safe))) + return force_unicode(urllib.quote_plus(smart_str(url), safe)) urlquote_plus = allow_lazy(urlquote_plus, unicode) def urlencode(query, doseq=0): diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index c0ae8cf36d23..c40d71a322af 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1970,19 +1970,6 @@ For example:: If ``value`` is ``"http://www.example.org/foo?a=b&c=d"``, the output will be ``"http%3A//www.example.org/foo%3Fa%3Db%26c%3Dd"``. -.. versionadded:: 1.1 - -An optional argument containing the characters which should not be escaped can -be provided. - -If not provided, the '/' character is assumed safe. An empty string can be -provided when *all* characters should be escaped. For example:: - - {{ value|urlencode:"" }} - -If ``value`` is ``"http://www.example.org/"``, the output will be -``"http%3A%2F%2Fwww.example.org%2F"``. - .. templatefilter:: urlize urlize diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py index 9ada4fb26e83..af34c58a9f86 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/regressiontests/templates/filters.py @@ -265,10 +265,6 @@ def get_filter_tests(): 'filter-iriencode03': ('{{ url|iriencode }}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'), 'filter-iriencode04': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'), - # urlencode - 'filter-urlencode01': ('{{ url|urlencode }}', {'url': '/test&"/me?/'}, '/test%26%22/me%3F/'), - 'filter-urlencode02': ('/test/{{ urlbit|urlencode:"" }}/', {'urlbit': 'escape/slash'}, '/test/escape%2Fslash/'), - # Chaining a bunch of safeness-preserving filters should not alter # the safe status either way. 'chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), From 54cb6ab1f36078453769cd03d3485525b1c5f25f Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Thu, 21 Oct 2010 21:22:11 +0000 Subject: [PATCH 364/902] [1.2.X] Fixed #14513 -- check fields with underscores for validity when ordering. Bonus points to Klaas van Schelven. Backport from trunk (r14315) git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14317 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/validation.py | 2 +- tests/modeltests/invalid_models/models.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 047acb049535..feb3744781e8 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -257,7 +257,7 @@ def get_validation_errors(outfile, app=None): continue # Skip ordering in the format field1__field2 (FIXME: checking # this format would be nice, but it's a little fiddly). - if '_' in field_name: + if '__' in field_name: continue try: opts.get_field(field_name, many_to_many=False) diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index b263390c3974..ef3edd8563f6 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/modeltests/invalid_models/models.py @@ -206,6 +206,9 @@ class UniqueFKTarget2(models.Model): """ Model to test for unique FK target in previously seen model: expect no error """ tgt = models.ForeignKey(FKTarget, to_field='good') +class NonExistingOrderingWithSingleUnderscore(models.Model): + class Meta: + ordering = ("does_not_exist",) model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer. invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer. @@ -311,4 +314,5 @@ class UniqueFKTarget2(models.Model): invalid_models.uniquem2m: ManyToManyFields cannot be unique. Remove the unique argument on 'unique_people'. invalid_models.nonuniquefktarget1: Field 'bad' under model 'FKTarget' must have a unique=True constraint. invalid_models.nonuniquefktarget2: Field 'bad' under model 'FKTarget' must have a unique=True constraint. +invalid_models.nonexistingorderingwithsingleunderscore: "ordering" refers to "does_not_exist", a field that doesn't exist. """ From 7377e4f92d8f13ec649f478cac41cab35039eb40 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 22 Oct 2010 21:20:30 +0000 Subject: [PATCH 365/902] [1.2.X] Fixed #14534 -- updated an out of date link in the docs. Thanks to d0ugal for the report and Frank Wiles for the patch. Backport of [14318]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14319 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/queries.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 0ee417d86c35..18a96459fcab 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -701,7 +701,7 @@ precede the definition of any keyword arguments. For example:: The `OR lookups examples`_ in the Django unit tests show some possible uses of ``Q``. - .. _OR lookups examples: http://code.djangoproject.com/browser/django/trunk/tests/modeltests/or_lookups/models.py + .. _OR lookups examples: http://code.djangoproject.com/browser/django/trunk/tests/modeltests/or_lookups/tests.py Comparing objects ================= From f6de03b3a2cc8bb48369a58db59001bcd25c0988 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 23 Oct 2010 00:11:50 +0000 Subject: [PATCH 366/902] [1.2.X] Fixed #14223 -- Extended unification of exception raised in presence of integrity constraint violations. The unification had been introduced in r12352 and native backend exceptions still slipped through in cases that end in connection.commit() call. Thanks Alex, Jacob and Carl for reviewing. Backport of [14320] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14321 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/postgresql/base.py | 7 +++ .../db/backends/postgresql_psycopg2/base.py | 7 +++ tests/regressiontests/backends/models.py | 16 +++++- tests/regressiontests/backends/tests.py | 50 +++++++++++++++++-- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index f84ad1da6105..5f4d791f02f8 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -150,6 +150,13 @@ def _cursor(self): cursor.execute("SET client_encoding to 'UNICODE'") return UnicodeCursorWrapper(cursor, 'utf-8') + def _commit(self): + if self.connection is not None: + try: + return self.connection.commit() + except Database.IntegrityError, e: + raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + def typecast_string(s): """ Cast all returned strings to unicode strings. diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index ce4e48330eb5..c9f1af1669f0 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -189,3 +189,10 @@ def _set_isolation_level(self, level): finally: self.isolation_level = level self.features.uses_savepoints = bool(level) + + def _commit(self): + if self.connection is not None: + try: + return self.connection.commit() + except Database.IntegrityError, e: + raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] diff --git a/tests/regressiontests/backends/models.py b/tests/regressiontests/backends/models.py index 4ab00414db38..ea7ff96b91d1 100644 --- a/tests/regressiontests/backends/models.py +++ b/tests/regressiontests/backends/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.db import connection class Square(models.Model): root = models.IntegerField() @@ -21,3 +20,18 @@ class SchoolClass(models.Model): last_updated = models.DateTimeField() +class Reporter(models.Model): + first_name = models.CharField(max_length=30) + last_name = models.CharField(max_length=30) + + def __unicode__(self): + return u"%s %s" % (self.first_name, self.last_name) + + +class Article(models.Model): + headline = models.CharField(max_length=100) + pub_date = models.DateField() + reporter = models.ForeignKey(Reporter) + + def __unicode__(self): + return self.headline diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index d955a098afe2..005fc4d86e97 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- # Unit and doctests for specific database backends. import datetime -import models import unittest -from django.db import backend, connection, DEFAULT_DB_ALIAS + +from django.conf import settings +from django.db import backend, connection, DEFAULT_DB_ALIAS, IntegrityError from django.db.backends.signals import connection_created from django.db.backends.postgresql import version as pg_version -from django.conf import settings -from django.test import TestCase +from django.test import TestCase, TransactionTestCase + +import models class Callproc(unittest.TestCase): @@ -141,6 +143,46 @@ def test_cursor_executemany(self): cursor.executemany(query, []) self.assertEqual(models.Square.objects.count(), 11) + +# We don't make these tests conditional because that means we would need to +# check and differentiate between: +# * MySQL+InnoDB, MySQL+MYISAM (something we currently can't do). +# * if sqlite3 (if/once we get #14204 fixed) has referential integrity turned +# on or not, something that would be controlled by runtime support and user +# preference. +# verify if its type is django.database.db.IntegrityError. + +class FkConstraintsTests(TransactionTestCase): + + def setUp(self): + # Create a Reporter. + self.r = models.Reporter.objects.create(first_name='John', last_name='Smith') + + def test_integrity_checks_on_creation(self): + """ + Try to create a model instance that violates a FK constraint. If it + fails it should fail with IntegrityError. + """ + a = models.Article(headline="This is a test", pub_date=datetime.datetime(2005, 7, 27), reporter_id=30) + try: + a.save() + except IntegrityError: + pass + + def test_integrity_checks_on_update(self): + """ + Try to update a model instance introducing a FK constraint violation. + If it fails it should fail with IntegrityError. + """ + # Create an Article. + models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r) + # Retrive it from the DB + a = models.Article.objects.get(headline="Test article") + a.reporter_id = 30 + try: + a.save() + except IntegrityError: + pass def test_unicode_fetches(self): #6254: fetchone, fetchmany, fetchall return strings as unicode objects qn = connection.ops.quote_name From 67ba914b8718b00b44c07d5350ee8e468a92a9e2 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 23 Oct 2010 14:53:18 +0000 Subject: [PATCH 367/902] [1.2.X] Fixed #14463 -- Fixed links to new location of date/time format specifiers docs. Thanks epicserve for the report. Backport of [14325] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14326 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index b2540a2edde0..cfb3f1ea8dfd 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -96,9 +96,9 @@ APPEND_SLASH Default: ``True`` -When set to ``True``, if the request URL does not match any of the patterns +When set to ``True``, if the request URL does not match any of the patterns in the URLconf and it doesn't end in a slash, an HTTP redirect is issued to the -same URL with a slash appended. Note that the redirect may cause any data +same URL with a slash appended. Note that the redirect may cause any data submitted in a POST request to be lost. The ``APPEND_SLASH`` setting is only used if @@ -447,7 +447,7 @@ Default: ``'N j, Y'`` (e.g. ``Feb. 4, 2003``) The default formatting to use for displaying date fields in any part of the system. Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See -:ttag:`allowed date format strings `. +:tfilter:`allowed date format strings `. .. versionchanged:: 1.2 This setting can now be overriden by setting ``USE_L10N`` to ``True``. @@ -487,7 +487,7 @@ Default: ``'N j, Y, P'`` (e.g. ``Feb. 4, 2003, 4 p.m.``) The default formatting to use for displaying datetime fields in any part of the system. Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See -:ttag:`allowed date format strings `. +:tfilter:`allowed date format strings `. .. versionchanged:: 1.2 This setting can now be overriden by setting ``USE_L10N`` to ``True``. @@ -1161,7 +1161,7 @@ drilldown, the header for a given day displays the day and month. Different locales have different formats. For example, U.S. English would say "January 1," whereas Spanish might say "1 Enero." -See :ttag:`allowed date format strings `. See also ``DATE_FORMAT``, +See :tfilter:`allowed date format strings `. See also ``DATE_FORMAT``, ``DATETIME_FORMAT``, ``TIME_FORMAT`` and ``YEAR_MONTH_FORMAT``. .. setting:: NUMBER_GROUPING @@ -1403,7 +1403,7 @@ Default: ``m/d/Y`` (e.g. ``12/31/2003``) An available formatting that can be used for displaying date fields on templates. Note that if :setting:`USE_L10N` is set to ``True``, then the corresponding locale-dictated format has higher precedence and will be applied. -See :ttag:`allowed date format strings `. +See :tfilter:`allowed date format strings `. See also ``DATE_FORMAT`` and ``SHORT_DATETIME_FORMAT``. @@ -1419,7 +1419,7 @@ Default: ``m/d/Y P`` (e.g. ``12/31/2003 4 p.m.``) An available formatting that can be used for displaying datetime fields on templates. Note that if :setting:`USE_L10N` is set to ``True``, then the corresponding locale-dictated format has higher precedence and will be applied. -See :ttag:`allowed date format strings `. +See :tfilter:`allowed date format strings `. See also ``DATE_FORMAT`` and ``SHORT_DATETIME_FORMAT``. @@ -1559,7 +1559,7 @@ Default: ``'P'`` (e.g. ``4 p.m.``) The default formatting to use for displaying time fields in any part of the system. Note that if :setting:`USE_L10N` is set to ``True``, then the locale-dictated format has higher precedence and will be applied instead. See -:ttag:`allowed date format strings `. +:tfilter:`allowed date format strings `. .. versionchanged:: 1.2 This setting can now be overriden by setting ``USE_L10N`` to ``True``. @@ -1713,7 +1713,7 @@ drilldown, the header for a given month displays the month and the year. Different locales have different formats. For example, U.S. English would say "January 2006," whereas another locale might say "2006/January." -See :ttag:`allowed date format strings `. See also ``DATE_FORMAT``, +See :tfilter:`allowed date format strings `. See also ``DATE_FORMAT``, ``DATETIME_FORMAT``, ``TIME_FORMAT`` and ``MONTH_DAY_FORMAT``. Deprecated settings From af3de6a712117cebe20afe05ffe96fd7c8c163d5 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 23 Oct 2010 16:43:42 +0000 Subject: [PATCH 368/902] [1.2.X] Fixed #14173 -- Added sections about contrib apps-provided commands to django-admin.py docs. Added documentation about changepassword, pointers to GeoDjango and sitemaps commands, grouped them by app. Backport of [14327] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14328 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/sitemaps.txt | 14 ++-- docs/ref/django-admin.txt | 136 +++++++++++++++++++++++----------- docs/topics/auth.txt | 15 ++-- docs/topics/testing.txt | 2 +- 4 files changed, 111 insertions(+), 56 deletions(-) diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index db80b0c9084c..50bc10c4b645 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -33,12 +33,12 @@ To install the sitemap app, follow these steps: 1. Add ``'django.contrib.sitemaps'`` to your :setting:`INSTALLED_APPS` setting. - + 2. Make sure ``'django.template.loaders.app_directories.Loader'`` is in your :setting:`TEMPLATE_LOADERS` setting. It's in there by default, so you'll only need to change this if you've changed that setting. - 3. Make sure you've installed the + 3. Make sure you've installed the :mod:`sites framework `. (Note: The sitemap application doesn't install any database tables. The only @@ -294,7 +294,7 @@ Pinging Google ============== You may want to "ping" Google when your sitemap changes, to let it know to -reindex your site. The sitemaps framework provides a function to do just +reindex your site. The sitemaps framework provides a function to do just that: :func:`django.contrib.sitemaps.ping_google()`. .. function:: ping_google @@ -313,14 +313,14 @@ that: :func:`django.contrib.sitemaps.ping_google()`. The :func:`ping_google` command only works if you have registered your site with `Google Webmaster Tools`_. - + .. _`Google Webmaster Tools`: http://www.google.com/webmasters/tools/ - + One useful way to call :func:`ping_google` is from a model's ``save()`` method:: from django.contrib.sitemaps import ping_google - + class Entry(models.Model): # ... def save(self, force_insert=False, force_update=False): @@ -340,6 +340,8 @@ each time you call ``save()``. Pinging Google via `manage.py` ------------------------------ +.. django-admin:: ping_google + .. versionadded:: 1.0 Once the sitemaps application is added to your project, you may also diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 9442e5f28bd8..0a1cd25cae9b 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -37,26 +37,26 @@ Usage .. code-block:: bash - django-admin.py [options] - manage.py [options] + django-admin.py [options] + manage.py [options] -``subcommand`` should be one of the subcommands listed in this document. +``command`` should be one of the commands listed in this document. ``options``, which is optional, should be zero or more of the options available -for the given subcommand. +for the given command. Getting runtime help -------------------- .. django-admin-option:: --help -Run ``django-admin.py help`` to display a list of all available subcommands. -Run ``django-admin.py help `` to display a description of the -given subcommand and a list of its available options. +Run ``django-admin.py help`` to display a list of all available commands. +Run ``django-admin.py help `` to display a description of the +given command and a list of its available options. App names --------- -Many subcommands take a list of "app names." An "app name" is the basename of +Many commands take a list of "app names." An "app name" is the basename of the package containing your models. For example, if your ``INSTALLED_APPS`` contains the string ``'mysite.blog'``, the app name is ``blog``. @@ -80,8 +80,8 @@ Use :djadminopt:`--verbosity` to specify the amount of notification and debug in that ``django-admin.py`` should print to the console. For more details, see the documentation for the :djadminopt:`--verbosity` option. -Available subcommands -===================== +Available commands +================== cleanup ------- @@ -124,34 +124,6 @@ backend. See :doc:`/topics/cache` for more information. The :djadminopt:`--database` option can be used to specify the database onto which the cachetable will be installed. -createsuperuser ---------------- - -.. django-admin:: createsuperuser - -.. versionadded:: 1.0 - -Creates a superuser account (a user who has all permissions). This is -useful if you need to create an initial superuser account but did not -do so during ``syncdb``, or if you need to programmatically generate -superuser accounts for your site(s). - -When run interactively, this command will prompt for a password for -the new superuser account. When run non-interactively, no password -will be set, and the superuser account will not be able to log in until -a password has been manually set for it. - -.. django-admin-option:: --username -.. django-admin-option:: --email - -The username and e-mail address for the new account can be supplied by -using the ``--username`` and ``--email`` arguments on the command -line. If either of those is not supplied, ``createsuperuser`` will prompt for -it when running interactively. - -This command is only available if Django's :doc:`authentication system -` (``django.contrib.auth``) is installed. - dbshell ------- @@ -175,7 +147,6 @@ manually. The :djadminopt:`--database` option can be used to specify the database onto which to open a shell. - diffsettings ------------ @@ -315,7 +286,6 @@ only works in PostgreSQL and with certain types of MySQL tables. The :djadminopt:`--database` option may be used to specify the database to introspect. - loaddata ------------------------------ @@ -870,7 +840,7 @@ templates. Use ``--addrport`` to specify a different port, or IP address and port, from the default of 127.0.0.1:8000. This value follows exactly the same format and -serves exactly the same function as the argument to the ``runserver`` subcommand. +serves exactly the same function as the argument to the ``runserver`` command. Examples: @@ -895,10 +865,92 @@ validate Validates all installed models (according to the ``INSTALLED_APPS`` setting) and prints validation errors to standard output. +Commands provided by applications +================================= + +Some commands are only available when the ``django.contrib`` application that +:doc:`implements ` them has been +:setting:`enabled `. This section describes them grouped by +their application. + +``django.contrib.auth`` +----------------------- + +changepassword +~~~~~~~~~~~~~~ + +.. django-admin:: changepassword + +.. versionadded:: 1.2 + +This command is only available if Django's :doc:`authentication system +` (``django.contrib.auth``) is installed. + +Allows changing a user's password. It prompts you to enter twice the password of +the user given as parameter. If they both match, the new password will be +changed immediately. If you do not supply a user, the command will attempt to +change the password whose username matches the current user. + +Example usage:: + + django-admin.py changepassword ringo + +createsuperuser +~~~~~~~~~~~~~~~ + +.. django-admin:: createsuperuser + +.. versionadded:: 1.0 + +This command is only available if Django's :doc:`authentication system +` (``django.contrib.auth``) is installed. + +Creates a superuser account (a user who has all permissions). This is +useful if you need to create an initial superuser account but did not +do so during ``syncdb``, or if you need to programmatically generate +superuser accounts for your site(s). + +When run interactively, this command will prompt for a password for +the new superuser account. When run non-interactively, no password +will be set, and the superuser account will not be able to log in until +a password has been manually set for it. + +.. django-admin-option:: --username +.. django-admin-option:: --email + +The username and e-mail address for the new account can be supplied by +using the ``--username`` and ``--email`` arguments on the command +line. If either of those is not supplied, ``createsuperuser`` will prompt for +it when running interactively. + +``django.contrib.gis`` +---------------------- + +ogrinspect +~~~~~~~~~~ + +This command is only available if :doc:`GeoDjango ` +(``django.contrib.gis``) is installed. + +Please refer to its :djadmin:`description ` in the GeoDjango +documentation. + +``django.contrib.sitemaps`` +--------------------------- + +ping_google +~~~~~~~~~~~ + +This command is only available if the :doc:`Sitemaps framework +` (``django.contrib.sitemaps``) is installed. + +Please refer to its :djadmin:`description ` in the Sitemaps +documentation. + Default options =============== -Although some subcommands may allow their own custom options, every subcommand +Although some commands may allow their own custom options, every command allows for the following options: .. django-admin-option:: --pythonpath diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 5d1cc189f443..95b2e137dc82 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -37,11 +37,12 @@ Authentication support is bundled as a Django application in 2. Run the command ``manage.py syncdb``. Note that the default :file:`settings.py` file created by -:djadmin:`django-admin.py startproject` includes ``'django.contrib.auth'`` and -``'django.contrib.contenttypes'`` in :setting:`INSTALLED_APPS` for convenience. -If your :setting:`INSTALLED_APPS` already contains these apps, feel free to run -:djadmin:`manage.py syncdb` again; you can run that command as many times as -you'd like, and each time it'll only install what's needed. +:djadmin:`django-admin.py startproject ` includes +``'django.contrib.auth'`` and ``'django.contrib.contenttypes'`` in +:setting:`INSTALLED_APPS` for convenience. If your :setting:`INSTALLED_APPS` +already contains these apps, feel free to run :djadmin:`manage.py syncdb +` again; you can run that command as many times as you'd like, and each +time it'll only install what's needed. The :djadmin:`syncdb` command creates the necessary database tables, creates permission objects for all installed apps that need 'em, and prompts you to @@ -358,8 +359,8 @@ Changing passwords .. versionadded:: 1.2 The ``manage.py changepassword`` command was added. -:djadmin:`manage.py changepassword ` offers a method of -changing a User's password from the command line. It prompts you to +:djadmin:`manage.py changepassword *username* ` offers a method +of changing a User's password from the command line. It prompts you to change the password of a given user which you must enter twice. If they both match, the new password will be changed immediately. If you do not supply a user, the command will attempt to change the password diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 1290e4ee008d..bbb3e2b907bc 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -240,7 +240,7 @@ in different circumstances. Running tests ============= -Once you've written tests, run them using the :djadmin:`test` subcommand of +Once you've written tests, run them using the :djadmin:`test` command of your project's ``manage.py`` utility:: $ ./manage.py test From 2380a469d71d0f6d251a058b0ce6c7f71857251b Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 23 Oct 2010 21:21:34 +0000 Subject: [PATCH 369/902] [1.2.X] Fixed #14545 -- Added ValidationError to Exceptions Reference docs and improved Sphinx metadata. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14330 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/exceptions.txt | 103 ++++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 42 deletions(-) diff --git a/docs/ref/exceptions.txt b/docs/ref/exceptions.txt index 4a4384376bf2..f1246bf5afb6 100644 --- a/docs/ref/exceptions.txt +++ b/docs/ref/exceptions.txt @@ -14,84 +14,103 @@ Django-specific Exceptions ObjectDoesNotExist and DoesNotExist ----------------------------------- +.. exception:: DoesNotExist +.. exception:: ObjectDoesNotExist -The ``DoesNotExist`` exception is raised when an object is not found -for the given parameters of a query. + The :exc:`DoesNotExist` exception is raised when an object is not found + for the given parameters of a query. -``ObjectDoesNotExist`` is defined in ``django.core.exceptions``. -``DoesNotExist`` is a subclass of the base ``ObjectDoesNotExist`` -exception that is provided on every model class as a way of -identifying the specific type of object that could not be found. + :exc:`ObjectDoesNotExist` is defined in :mod:`django.core.exceptions`. + :exc:`DoesNotExist` is a subclass of the base :exc:`ObjectDoesNotExist` + exception that is provided on every model class as a way of + identifying the specific type of object that could not be found. -See :meth:`~django.db.models.QuerySet.get()` for further information -on ``ObjectDoesNotExist`` and ``DoesNotExist``. + See :meth:`~django.db.models.QuerySet.get()` for further information + on :exc:`ObjectDoesNotExist` and :exc:`DoesNotExist`. MultipleObjectsReturned ----------------------- +.. exception:: MultipleObjectsReturned -The ``MultipleObjectsReturned`` exception is raised by a query if only -one object is expected, but multiple objects are returned. A base version -of this exception is provided in ``django.core.exceptions``; each model -class contains a subclassed version that can be used to identify the -specific object type that has returned multiple objects. + The :exc:`MultipleObjectsReturned` exception is raised by a query if only + one object is expected, but multiple objects are returned. A base version + of this exception is provided in :mod:`django.core.exceptions`; each model + class contains a subclassed version that can be used to identify the + specific object type that has returned multiple objects. -See :meth:`~django.db.models.QuerySet.get()` for further information. + See :meth:`~django.db.models.QuerySet.get()` for further information. SuspiciousOperation ------------------- +.. exception:: SuspiciousOperation -The ``SuspiciousOperation`` exception is raised when a user has performed -an operation that should be considered suspicious from a security perspective, -such as tampering with a session cookie. + The :exc:`SuspiciousOperation` exception is raised when a user has performed + an operation that should be considered suspicious from a security perspective, + such as tampering with a session cookie. PermissionDenied ---------------- +.. exception:: PermissionDenied -The ``PermissionDenied`` exception is raised when a user does not have -permission to perform the action requested. + The :exc:`PermissionDenied` exception is raised when a user does not have + permission to perform the action requested. ViewDoesNotExist ---------------- +.. exception:: ViewDoesNotExist -The ``ViewDoesNotExist`` exception is raised by -``django.core.urlresolvers`` when a requested view does not exist. + The :exc:`ViewDoesNotExist` exception is raised by + :mod:`django.core.urlresolvers` when a requested view does not exist. MiddlewareNotUsed ----------------- +.. exception:: MiddlewareNotUsed -The ``MiddlewareNotUsed`` exception is raised when a middleware is not -used in the server configuration. + The :exc:`MiddlewareNotUsed` exception is raised when a middleware is not + used in the server configuration. ImproperlyConfigured -------------------- +.. exception:: ImproperlyConfigured -The ``ImproperlyConfigured`` exception is raised when Django is -somehow improperly configured -- for example, if a value in ``settings.py`` -is incorrect or unparseable. + The :exc:`ImproperlyConfigured` exception is raised when Django is + somehow improperly configured -- for example, if a value in ``settings.py`` + is incorrect or unparseable. FieldError ---------- - -The ``FieldError`` exception is raised when there is a problem with a -model field. This can happen for several reasons: - - - A field in a model clashes with a field of the same name from an - abstract base class - - An infinite loop is caused by ordering - - A keyword cannot be parsed from the filter parameters - - If a field cannot be determined from a keyword in the query - parameters - - If a join is not permitted on the specified field - - If a field name is invalid - - If a query contains invalid order_by arguments +.. exception:: FieldError + + The :exc:`FieldError` exception is raised when there is a problem with a + model field. This can happen for several reasons: + + - A field in a model clashes with a field of the same name from an + abstract base class + - An infinite loop is caused by ordering + - A keyword cannot be parsed from the filter parameters + - A field cannot be determined from a keyword in the query + parameters + - A join is not permitted on the specified field + - A field name is invalid + - A query contains invalid order_by arguments + +ValidationError +--------------- +.. exception:: ValidationError + + The :exc:`ValidationError` exception is raised when data fails form or + model field validation. For more information about validation, see + :doc:`Form and Field Validation `, + :ref:`Model Field Validation ` and the + :doc:`Validator Reference `. Database Exceptions =================== -Django wraps the standard database exceptions ``DatabaseError`` and -``IntegrityError`` so that your Django code has a guaranteed common +Django wraps the standard database exceptions :exc:`DatabaseError` and +:exc:`IntegrityError` so that your Django code has a guaranteed common implementation of these classes. These database exceptions are -provided in ``django.db``. +provided in :mod:`django.db`. The Django wrappers for database exceptions behave exactly the same as the underlying database exceptions. See `PEP 249 - Python Database API From f0416f693fa54b1a46e0db437970fcda73a5d77f Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 23 Oct 2010 22:07:33 +0000 Subject: [PATCH 370/902] [1.2.X] Fixed #14537 -- Added documentation on where validators can be imported from to Validators Reference docs, and improved Sphinx formatting and metadata. Thanks to rfugger for the report. Backport of [14331] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14332 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/validators.txt | 110 ++++++++++++++++--------------- docs/topics/forms/modelforms.txt | 6 ++ 2 files changed, 64 insertions(+), 52 deletions(-) diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index 4937512e463d..0451f65dc065 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -10,8 +10,9 @@ Writing validators ================== A validator is a callable that takes a value and raises a -``ValidationError`` if it doesn't meet some criteria. Validators can be useful -for re-using validation logic between different types of fields. +:exc:`~django.core.exceptions.ValidationError` if it doesn't meet some +criteria. Validators can be useful for re-using validation logic between +different types of fields. For example, here's a validator that only allows even numbers:: @@ -21,7 +22,7 @@ For example, here's a validator that only allows even numbers:: if value % 2 != 0: raise ValidationError(u'%s is not an even number' % value) -You can add this to a model field via the field's ``validators`` +You can add this to a model field via the field's :attr:`~django.db.models.Field.validators` argument:: from django.db import models @@ -44,109 +45,114 @@ See the :doc:`form validation ` for more information on how validators are run in forms, and :ref:`Validating objects ` for how they're run in models. Note that validators will not be run automatically when you save a model, but if you are using a -``ModelForm``, it will run your validators on any fields that are included in -your form. See the :doc:`ModelForm documentation ` -for information on how model validation interacts with forms. +:class:`~django.forms.ModelForm`, it will run your validators on any fields +that are included in your form. See the +:doc:`ModelForm documentation ` for information on +how model validation interacts with forms. Built-in validators =================== -Django has a collection of callable validators for use with model and form -fields. They're used internally but are available for use with your own fields, -too. They can be used in addition to, or in lieu of custom ``field.clean()`` -methods. +The :mod:`django.core.validators` module contains a collection of callable +validators for use with model and form fields. They're used internally but +are available for use with your own fields, too. They can be used in addition +to, or in lieu of custom ``field.clean()`` methods. ``RegexValidator`` ------------------ +.. class:: RegexValidator(regex, [message=None, code=None]) -.. class:: RegexValidator(regex, message=None, code=None) + .. attribute:: regex -.. attribute:: regex + The regular expression pattern to search for the provided ``value``, + or a pre-compiled regular expression. Raises a + :exc:`~django.core.exceptions.ValidationError` with :attr:`.message` + and :attr:`.code` if no match is found. -The regular expression pattern to search for the provided ``value``, -or a pre-compiled regular expression. Raises a ``ValidationError`` -with ``message`` and ``code`` if no match is found. + .. attribute:: message -.. attribute:: message=None + The error message used by :exc:`~django.core.exceptions.ValidationError` + if validation fails. If no :attr:`.message` is specified, a generic + ``"Enter a valid value"`` message is used. Default value: ``None``. -The error message used by ``ValidationError`` if validation fails. If no -``message`` is specified, a generic ``"Enter a valid value"`` message is used. + .. attribute:: code -.. attribute:: code=None - -The error code used by ``ValidationError`` if validation fails. If ``code`` -is not specified, ``"invalid"`` is used. + The error code used by :exc:`~django.core.exceptions.ValidationError` + if validation fails. If :attr:`.code` is not specified, ``"invalid"`` + is used. Default value: ``None``. ``URLValidator`` ---------------- +.. class:: URLValidator([verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT]) -.. class:: URLValidator(verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT) - -A ``RegexValidator`` that ensures a value looks like a URL and optionally -verifies that the URL actually exists (i.e., doesn't return a 404 status code). -Raises an error code of ``'invalid'`` if it doesn't look like a URL, and a code -of ``'invalid_link'`` if it doesn't exist. + A :class:`RegexValidator` that ensures a value looks like a URL and + optionally verifies that the URL actually exists (i.e., doesn't return a + 404 status code). Raises an error code of ``'invalid'`` if it doesn't look + like a URL, and a code of ``'invalid_link'`` if it doesn't exist. -.. attribute:: verify_exists=False + .. attribute:: verify_exists -If ``verify_exists`` is ``True``, this validator checks that the URL actually -exists. + Default value: ``False``. If set to ``True``, this validator checks + that the URL actually exists. -.. attribute:: validator_user_agent=URL_VALIDATOR_USER_AGENT + .. attribute:: validator_user_agent -If ``verify_exists`` is ``True``, it uses ``validator_user_agent`` as the "User-agent" -for the request. This defaults to ``settings.URL_VALIDATOR_USER_AGENT``. + If :attr:`.verify_exists` is ``True``, Django uses the value of + :attr:`.validator_user_agent` as the "User-agent" for the request. This + defaults to :setting:`settings.URL_VALIDATOR_USER_AGENT `. ``validate_email`` ------------------ +.. data:: validate_email -A ``RegexValidator`` instance that ensures a value looks like an e-mail address. + A :class:`RegexValidator` instance that ensures a value looks like an + e-mail address. ``validate_slug`` ----------------- +.. data:: validate_slug -A ``RegexValidator`` instance that ensures a value consists of only letters, -numbers, underscores or hyphens. + A :class:`RegexValidator` instance that ensures a value consists of only + letters, numbers, underscores or hyphens. ``validate_ipv4_address`` ------------------------- +.. data:: validate_ipv4_address -A ``RegexValidator`` instance that ensures a value looks like an IPv4 address. + A :class:`RegexValidator` instance that ensures a value looks like an IPv4 + address. ``validate_comma_separated_integer_list`` ----------------------------------------- +.. data:: validate_comma_separated_integer_list -A ``RegexValidator`` instance that ensures a value is a comma-separated list -of integers. + A :class:`RegexValidator` instance that ensures a value is a + comma-separated list of integers. ``MaxValueValidator`` --------------------- - .. class:: MaxValueValidator(max_value) -Raises a ``ValidationError`` with a code of ``'max_value'`` if ``value`` is -greater than ``max_value``. + Raises a :exc:`~django.core.exceptions.ValidationError` with a code of + ``'max_value'`` if ``value`` is greater than ``max_value``. ``MinValueValidator`` --------------------- - .. class:: MinValueValidator(min_value) -Raises a ``ValidationError`` with a code of ``'min_value'`` if ``value`` is -less than ``min_value``. + Raises a :exc:`~django.core.exceptions.ValidationError` with a code of + ``'min_value'`` if ``value`` is less than ``min_value``. ``MaxLengthValidator`` ---------------------- - .. class:: MaxLengthValidator(max_length) -Raises a ``ValidationError`` with a code of ``'max_length'`` if the length of -``value`` is greater than ``max_length``. + Raises a :exc:`~django.core.exceptions.ValidationError` with a code of + ``'max_length'`` if the length of ``value`` is greater than ``max_length``. ``MinLengthValidator`` ---------------------- - .. class:: MinLengthValidator(min_length) -Raises a ``ValidationError`` with a code of ``'min_length'`` if the length of -``value`` is less than ``min_length``. + Raises a :exc:`~django.core.exceptions.ValidationError` with a code of + ``'min_length'`` if the length of ``value`` is less than ``min_length``. diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 203639db770e..65bc0ea2ea44 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -2,8 +2,14 @@ Creating forms from models ========================== +.. module:: django.forms.models + :synopsis: ModelForm and ModelFormset. + +.. currentmodule:: django.forms + ``ModelForm`` ============= +.. class:: ModelForm If you're building a database-driven app, chances are you'll have forms that map closely to Django models. For instance, you might have a ``BlogComment`` From 74fe39764647a4447e18fe7f44374f12ed20025f Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 23 Oct 2010 22:15:27 +0000 Subject: [PATCH 371/902] [1.2.X] Fixed #14541 -- Corrected an outdated bit of wording in the LANGUAGES setting docs. Thanks to akelm for the report and suggested fix. Backport of [14333] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14334 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index cfb3f1ea8dfd..80a2d5b900c6 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -984,7 +984,7 @@ Generally, the default value should suffice. Only set this setting if you want to restrict language selection to a subset of the Django-provided languages. If you define a custom ``LANGUAGES`` setting, it's OK to mark the languages as -translation strings (as in the default value displayed above) -- but use a +translation strings (as in the default value referred to above) -- but use a "dummy" ``gettext()`` function, not the one in ``django.utils.translation``. You should *never* import ``django.utils.translation`` from within your settings file, because that module in itself depends on the settings, and that From d269738f41d1eb2fb66d1c7bf376f44845aea22d Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 23 Oct 2010 22:29:53 +0000 Subject: [PATCH 372/902] [1.2.X] Fixed #14526 -- Updated wording regarding the minimum Python version required for GeoDjango. Thanks to PaulM for the report. Backport of [14335] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14336 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/contrib/gis/install.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/contrib/gis/install.txt b/docs/ref/contrib/gis/install.txt index a62e6aaf8032..fa8e34c268c7 100644 --- a/docs/ref/contrib/gis/install.txt +++ b/docs/ref/contrib/gis/install.txt @@ -36,10 +36,10 @@ Requirements Python 2.4+ ----------- -Because of heavy use of the decorator syntax, Python 2.4 is minimum -version supported by GeoDjango. Python 2.5+ is recommended because the -`ctypes`__ module comes included; otherwise, 2.4 users will need to -`download and install ctypes`__. + +Python 2.4 is the minimum version supported by Django, however Python 2.5+ is +recommended because the `ctypes`__ module comes included; otherwise, 2.4 users +will need to `download and install ctypes`__. __ http://docs.python.org/lib/module-ctypes.html __ http://sourceforge.net/projects/ctypes/files/ From 92435e1e48514d013bc58ce294b310bec62657bf Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 24 Oct 2010 08:54:51 +0000 Subject: [PATCH 373/902] [1.2.X] Fixed #11441 -- Improved signal topic guide, particularly regarding the weak and dispatch_uid parameters to the Signal.connect method. Thanks to Mike_A and sayane for the report, and gremmie for the draft patch. Backport of [14337] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14338 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/signals.txt | 88 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index 35dc3f4c0910..35e111d93538 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -46,15 +46,38 @@ You can also `define and send your own custom signals`_; see below. Listening to signals ==================== -To receive a signal, you need to register a *receiver* function that gets called -when the signal is sent. Let's see how this works by registering a signal that +To receive a signal, you need to register a *receiver* function that gets +called when the signal is sent by using the +:meth:`.Signal.connect` method: + +.. method:: Signal.connect(receiver, [sender=None, weak=True, dispatch_uid=None]) + + :param receiver: The callback function which will be connected to this + signal. See :ref:`receiver-functions` for more information. + + :param sender: Specifies a particular sender to receive signals from. See + :ref:`connecting-to-specific-signals` for more information. + + :param weak: Django stores signal handlers as weak references by + default. Thus, if your receiver is a local function, it may be + garbage collected. To prevent this, pass ``weak=False`` when you call + the signal's ``connect()`` method. + + :param dispatch_uid: A unique identifier for a signal receiver in cases + where duplicate signals may be sent. See + :ref:`preventing-duplicate-signals` for more information. + +Let's see how this works by registering a signal that gets called after each HTTP request is finished. We'll be connecting to the :data:`~django.core.signals.request_finished` signal. +.. _receiver-functions: + Receiver functions ------------------ -First, we need to define a receiver function. A receiver can be any Python function or method: +First, we need to define a receiver function. A receiver can be any Python +function or method: .. code-block:: python @@ -77,6 +100,8 @@ This would be wrong -- in fact, Django will throw an error if you do so. That's because at any point arguments could get added to the signal and your receiver must be able to handle those new arguments. +.. _connecting-receiver-functions: + Connecting receiver functions ----------------------------- @@ -98,6 +123,8 @@ Now, our ``my_callback`` function will be called each time a request finishes. to be sent. This makes your app's ``models.py`` a good place to put registration of signal handlers. +.. _connecting-to-specific-signals: + Connecting to signals sent by specific senders ---------------------------------------------- @@ -129,10 +156,34 @@ Different signals use different objects as their senders; you'll need to consult the :doc:`built-in signal documentation ` for details of each particular signal. +.. _preventing-duplicate-signals: + +Preventing duplicate signals +---------------------------- + +In some circumstances, the module in which you are connecting signals may be +imported multiple times. This can cause your receiver function to be +registered more than once, and thus called multiples times for a single signal +event. + +If this behavior is problematic (such as when using signals to +send an e-mail whenever a model is saved), pass a unique identifier as +the ``dispatch_uid`` argument to identify your receiver function. This +identifier will usually be a string, although any hashable object will +suffice. The end result is that your receiver function will only be +bound to the signal once for each unique ``dispatch_uid`` value. + +.. code-block:: python + + from django.core.signals import request_finished + + request_finished.connect(my_callback, dispatch_uid="my_unique_identifier") + Defining and sending signals ============================ -Your applications can take advantage of the signal infrastructure and provide its own signals. +Your applications can take advantage of the signal infrastructure and provide +its own signals. Defining signals ---------------- @@ -159,9 +210,14 @@ Remember that you're allowed to change this list of arguments at any time, so ge Sending signals --------------- +There are two ways to send send signals in Django. + .. method:: Signal.send(sender, **kwargs) +.. method:: Signal.send_robust(sender, **kwargs) -To send a signal, call :meth:`Signal.send`. You must provide the ``sender`` argument, and may provide as many other keyword arguments as you like. +To send a signal, call either :meth:`Signal.send` or :meth:`Signal.send_robust`. +You must provide the ``sender`` argument, and may provide as many other keyword +arguments as you like. For example, here's how sending our ``pizza_done`` signal might look: @@ -174,4 +230,26 @@ For example, here's how sending our ``pizza_done`` signal might look: pizza_done.send(sender=self, toppings=toppings, size=size) ... +Both ``send()`` and ``send_robust()`` return a list of tuple pairs +``[(receiver, response), ... ]``, representing the list of called receiver +functions and their response values. + +``send()`` differs from ``send_robust()`` in how exceptions raised by receiver +functions are handled. ``send()`` does *not* catch any exceptions raised by +receivers; it simply allows errors to propagate. Thus not all receivers may +be notified of a signal in the face of an error. + +``send_robust()`` catches all errors derived from Python's ``Exception`` class, +and ensures all receivers are notified of the signal. If an error occurs, the +error instance is returned in the tuple pair for the receiver that raised the error. + +Disconnecting signals +===================== + +.. method:: Signal.disconnect([receiver=None, sender=None, weak=True, dispatch_uid=None]) + +To disconnect a receiver from a signal, call :meth:`Signal.disconnect`. The +arguments are as described in :meth:`.Signal.connect`. +The *receiver* argument indicates the registered receiver to disconnect. It may +be ``None`` if ``dispatch_uid`` is used to identify the receiver. From 4b61183840e43eee612de507e4b12470b61b5383 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 24 Oct 2010 09:13:56 +0000 Subject: [PATCH 374/902] [1.2.X] Fixed #13040 -- Added info on where to import File class from to File reference docs, and improved Sphinx formatting. Thanks to stherrien for the report. Backport of [14339] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14340 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/files/file.txt | 112 +++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt index f4ae59f241e0..146ab4dd7e71 100644 --- a/docs/ref/files/file.txt +++ b/docs/ref/files/file.txt @@ -3,100 +3,106 @@ The ``File`` object .. currentmodule:: django.core.files -.. class:: File(file_object) - ``File`` attributes and methods ------------------------------- -Django's ``File`` has the following attributes and methods: +The :mod:`django.core.files` module contains a built-in class for basic file +handling in Django. The :class:`File` class has the following attributes and +methods: + +.. class:: File(file_object) -.. attribute:: File.name + .. attribute:: name - The name of file including the relative path from :setting:`MEDIA_ROOT`. + The name of file including the relative path from :setting:`MEDIA_ROOT`. -.. attribute:: File.path + .. attribute:: path - The absolute path to the file's location on a local filesystem. + The absolute path to the file's location on a local filesystem. - :doc:`Custom file storage systems ` may not store - files locally; files stored on these systems will have a ``path`` of - ``None``. + :doc:`Custom file storage systems ` may not store + files locally; files stored on these systems will have a ``path`` of + ``None``. -.. attribute:: File.url + .. attribute:: url - The URL where the file can be retrieved. This is often useful in - :doc:`templates `; for example, a bit of a template for - displaying a ``Car`` (see above) might look like: - - .. code-block:: html+django + The URL where the file can be retrieved. This is often useful in + :doc:`templates `; for example, a bit of a template for + displaying a ``Car`` (see above) might look like: + + .. code-block:: html+django + + {{ car.name }} - {{ car.name }} + .. attribute:: size -.. attribute:: File.size + The size of the file in bytes. - The size of the file in bytes. + .. method:: open([mode=None]) -.. method:: File.open(mode=None) + Open or reopen the file (which by definition also does ``File.seek(0)``). + The ``mode`` argument allows the same values as Python's standard + ``open()``. - Open or reopen the file (which by definition also does ``File.seek(0)``). - The ``mode`` argument allows the same values as Python's standard - ``open()``. + When reopening a file, ``mode`` will override whatever mode the file was + originally opened with; ``None`` means to reopen with the original mode. - When reopening a file, ``mode`` will override whatever mode the file was - originally opened with; ``None`` means to reopen with the original mode. + .. method:: read([num_bytes=None]) -.. method:: File.read(num_bytes=None) + Read content from the file. The optional ``size`` is the number of bytes to + read; if not specified, the file will be read to the end. - Read content from the file. The optional ``size`` is the number of bytes to - read; if not specified, the file will be read to the end. + .. method:: __iter__() -.. method:: File.__iter__() + Iterate over the file yielding one line at a time. - Iterate over the file yielding one line at a time. + .. method:: chunks([chunk_size=None]) -.. method:: File.chunks(chunk_size=None) + Iterate over the file yielding "chunks" of a given size. ``chunk_size`` + defaults to 64 KB. - Iterate over the file yielding "chunks" of a given size. ``chunk_size`` - defaults to 64 KB. + This is especially useful with very large files since it allows them to be + streamed off disk and avoids storing the whole file in memory. - This is especially useful with very large files since it allows them to be - streamed off disk and avoids storing the whole file in memory. + .. method:: multiple_chunks([chunk_size=None]) -.. method:: File.multiple_chunks(chunk_size=None) + Returns ``True`` if the file is large enough to require multiple chunks to + access all of its content give some ``chunk_size``. - Returns ``True`` if the file is large enough to require multiple chunks to - access all of its content give some ``chunk_size``. + .. method:: write([content]) -.. method:: File.write(content) + Writes the specified content string to the file. Depending on the storage + system behind the scenes, this content might not be fully committed until + ``close()`` is called on the file. - Writes the specified content string to the file. Depending on the storage - system behind the scenes, this content might not be fully committed until - ``close()`` is called on the file. + .. method:: close() -.. method:: File.close() + Close the file. - Close the file. +.. currentmodule:: django.core.files.images Additional ``ImageField`` attributes ------------------------------------ -.. attribute:: File.width - - Width of the image. +.. class:: ImageFile(file_object) + + .. attribute:: width + + Width of the image. -.. attribute:: File.height + .. attribute:: height - Height of the image. + Height of the image. + +.. currentmodule:: django.core.files Additional methods on files attached to objects ----------------------------------------------- -.. highlight:: pycon - Any :class:`File` that's associated with an object (as with ``Car.photo``, above) will also have a couple of extra methods: -.. method:: File.save(name, content, save=True) +.. method:: File.save(name, content, [save=True]) Saves a new file with the file name and contents provided. This will not replace the existing file, but will create a new file and update the object @@ -113,7 +119,7 @@ above) will also have a couple of extra methods: Note that the ``content`` argument must be an instance of :class:`File` or of a subclass of :class:`File`. -.. method:: File.delete(save=True) +.. method:: File.delete([save=True]) Remove the file from the model instance and delete the underlying file. The ``save`` argument works as above. From 2d4ddc0416993725098a5f3016213e63fc1e4b70 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sun, 24 Oct 2010 09:41:08 +0000 Subject: [PATCH 375/902] [1.2.X] Fixed #13617 -- `OSMGeoAdmin` now works again when `USE_L10N` (or `LANGUAGE_CODE`) is set. Thanks, Federico Hlawaczek, for workaround and piquadrat for patch. Backport of r14341 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14342 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/admin/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/gis/admin/options.py b/django/contrib/gis/admin/options.py index a1da28108d73..1814933f7b7e 100644 --- a/django/contrib/gis/admin/options.py +++ b/django/contrib/gis/admin/options.py @@ -119,6 +119,6 @@ class OSMGeoAdmin(GeoModelAdmin): num_zoom = 20 map_srid = 900913 max_extent = '-20037508,-20037508,20037508,20037508' - max_resolution = 156543.0339 + max_resolution = '156543.0339' point_zoom = num_zoom - 6 units = 'm' From 552acc652d5cad1e2664809a437702792a73b1e8 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 24 Oct 2010 17:29:49 +0000 Subject: [PATCH 376/902] [1.2.X] Fixed #14551 -- corrected a type in the remote user auth documentation. Thanks to mag for the report and fix. Backport of [14344]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14345 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/auth-remote-user.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/howto/auth-remote-user.txt b/docs/howto/auth-remote-user.txt index 9dbde29e5c13..deab794cb11a 100644 --- a/docs/howto/auth-remote-user.txt +++ b/docs/howto/auth-remote-user.txt @@ -2,7 +2,7 @@ Authentication using ``REMOTE_USER`` ==================================== -.. currentmodule:: django.contrib.backends +.. currentmodule:: django.contrib.auth.backends This document describes how to make use of external authentication sources (where the Web server sets the ``REMOTE_USER`` environment variable) in your @@ -68,7 +68,7 @@ If your authentication mechanism uses a custom HTTP header and not ``RemoteUserBackend`` ===================== -.. class:: django.contrib.backends.RemoteUserBackend +.. class:: django.contrib.auth.backends.RemoteUserBackend If you need more control, you can create your own authentication backend that inherits from ``RemoteUserBackend`` and overrides certain parts: From 188a8e39fd1619b2e60f1846348ba237b892cc9f Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Mon, 25 Oct 2010 12:57:36 +0000 Subject: [PATCH 377/902] [1.2.X] Removed obsolete 'date' filter -> 'now' tag link. Backport of [14346] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14347 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/templates/builtins.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index c40d71a322af..f65e94b825ed 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1151,17 +1151,17 @@ If ``value`` is a ``datetime`` object (e.g., the result of ``datetime.datetime.now()``), the output will be the string ``'Wed 09 Jan 2008'``. -Given format can be one of the predefined ones ``DATE_FORMAT``, -``DATETIME_FORMAT``, ``SHORT_DATE_FORMAT`` or ``SHORT_DATETIME_FORMAT``, -or a custom format, same as the :ttag:`now` tag. Note that predefined formats -may vary depending on the current locale. +The format passed can be one of the predefined ones ``DATE_FORMAT``, +``DATETIME_FORMAT``, ``SHORT_DATE_FORMAT`` or ``SHORT_DATETIME_FORMAT``, or a +custom format that uses the format specifiers shown in the table above. Note +that predefined formats may vary depending on the current locale. Assuming that :setting:`USE_L10N` is ``True`` and :setting:`LANGUAGE_CODE` is, for example, ``"es"``, then for:: {{ value|date:"SHORT_DATE_FORMAT" }} -the output will be the string ``"09/01/2008"`` (The ``"SHORT_DATE_FORMAT"`` +the output would be the string ``"09/01/2008"`` (the ``"SHORT_DATE_FORMAT"`` format specifier for the ``es`` locale as shipped with Django is ``"d/m/Y"``). When used without a format string:: From 1826039e002694c1804eead4786789d0369758d6 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 25 Oct 2010 18:21:39 +0000 Subject: [PATCH 378/902] [1.2.X] Converted model_inheritance doctests to unittests. We have always been at war with doctests. Backport of [14348]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14349 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/model_inheritance/models.py | 234 ---------------- tests/modeltests/model_inheritance/tests.py | 277 +++++++++++++++++++ 2 files changed, 277 insertions(+), 234 deletions(-) create mode 100644 tests/modeltests/model_inheritance/tests.py diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py index 95bf5abf9925..6cee512fb3e0 100644 --- a/tests/modeltests/model_inheritance/models.py +++ b/tests/modeltests/model_inheritance/models.py @@ -143,237 +143,3 @@ class Copy(NamedURL): def __unicode__(self): return self.content - -__test__ = {'API_TESTS':""" -# The Student and Worker models both have 'name' and 'age' fields on them and -# inherit the __unicode__() method, just as with normal Python subclassing. -# This is useful if you want to factor out common information for programming -# purposes, but still completely independent separate models at the database -# level. - ->>> w = Worker(name='Fred', age=35, job='Quarry worker') ->>> w.save() ->>> w2 = Worker(name='Barney', age=34, job='Quarry worker') ->>> w2.save() ->>> s = Student(name='Pebbles', age=5, school_class='1B') ->>> s.save() ->>> unicode(w) -u'Worker Fred' ->>> unicode(s) -u'Student Pebbles' - -# The children inherit the Meta class of their parents (if they don't specify -# their own). ->>> Worker.objects.values('name') -[{'name': u'Barney'}, {'name': u'Fred'}] - -# Since Student does not subclass CommonInfo's Meta, it has the effect of -# completely overriding it. So ordering by name doesn't take place for Students. ->>> Student._meta.ordering -[] - -# However, the CommonInfo class cannot be used as a normal model (it doesn't -# exist as a model). ->>> CommonInfo.objects.all() -Traceback (most recent call last): - ... -AttributeError: type object 'CommonInfo' has no attribute 'objects' - -# A StudentWorker which does not exist is both a Student and Worker which does not exist. ->>> try: -... StudentWorker.objects.get(id=1) -... except Student.DoesNotExist: -... pass ->>> try: -... StudentWorker.objects.get(id=1) -... except Worker.DoesNotExist: -... pass - -# MultipleObjectsReturned is also inherited. ->>> sw1 = StudentWorker() ->>> sw1.name = 'Wilma' ->>> sw1.age = 35 ->>> sw1.save() ->>> sw2 = StudentWorker() ->>> sw2.name = 'Betty' ->>> sw2.age = 34 ->>> sw2.save() ->>> try: -... StudentWorker.objects.get(id__lt=10) -... except Student.MultipleObjectsReturned: -... pass -... except Worker.MultipleObjectsReturned: -... pass - -# Create a Post ->>> post = Post(title='Lorem Ipsum') ->>> post.save() - -# The Post model has distinct accessors for the Comment and Link models. ->>> post.attached_comment_set.create(content='Save $ on V1agr@', is_spam=True) - ->>> post.attached_link_set.create(content='The Web framework for perfectionists with deadlines.', url='http://www.djangoproject.com/') - - -# The Post model doesn't have an attribute called 'attached_%(class)s_set'. ->>> getattr(post, 'attached_%(class)s_set') -Traceback (most recent call last): - ... -AttributeError: 'Post' object has no attribute 'attached_%(class)s_set' - -# The Place/Restaurant/ItalianRestaurant models all exist as independent -# models. However, the subclasses also have transparent access to the fields of -# their ancestors. - -# Create a couple of Places. ->>> p1 = Place(name='Master Shakes', address='666 W. Jersey') ->>> p1.save() ->>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland') ->>> p2.save() - -Test constructor for Restaurant. ->>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton',serves_hot_dogs=True, serves_pizza=False, rating=2) ->>> r.save() - -# Test the constructor for ItalianRestaurant. ->>> c = Chef(name="Albert") ->>> c.save() ->>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c) ->>> ir.save() ->>> ItalianRestaurant.objects.filter(address='1234 W. Ash') -[] - ->>> ir.address = '1234 W. Elm' ->>> ir.save() ->>> ItalianRestaurant.objects.filter(address='1234 W. Elm') -[] - -# Make sure Restaurant and ItalianRestaurant have the right fields in the right -# order. ->>> [f.name for f in Restaurant._meta.fields] -['id', 'name', 'address', 'place_ptr', 'rating', 'serves_hot_dogs', 'serves_pizza', 'chef'] ->>> [f.name for f in ItalianRestaurant._meta.fields] -['id', 'name', 'address', 'place_ptr', 'rating', 'serves_hot_dogs', 'serves_pizza', 'chef', 'restaurant_ptr', 'serves_gnocchi'] ->>> Restaurant._meta.ordering -['-rating'] - -# Even though p.supplier for a Place 'p' (a parent of a Supplier), a Restaurant -# object cannot access that reverse relation, since it's not part of the -# Place-Supplier Hierarchy. ->>> Place.objects.filter(supplier__name='foo') -[] ->>> Restaurant.objects.filter(supplier__name='foo') -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'supplier' into field. Choices are: address, chef, id, italianrestaurant, lot, name, place_ptr, provider, rating, serves_hot_dogs, serves_pizza - -# Parent fields can be used directly in filters on the child model. ->>> Restaurant.objects.filter(name='Demon Dogs') -[] ->>> ItalianRestaurant.objects.filter(address='1234 W. Elm') -[] - -# Filters against the parent model return objects of the parent's type. ->>> Place.objects.filter(name='Demon Dogs') -[] - -# Since the parent and child are linked by an automatically created -# OneToOneField, you can get from the parent to the child by using the child's -# name. ->>> place = Place.objects.get(name='Demon Dogs') ->>> place.restaurant - - ->>> Place.objects.get(name='Ristorante Miron').restaurant.italianrestaurant - ->>> Restaurant.objects.get(name='Ristorante Miron').italianrestaurant - - -# This won't work because the Demon Dogs restaurant is not an Italian -# restaurant. ->>> place.restaurant.italianrestaurant -Traceback (most recent call last): - ... -DoesNotExist: ItalianRestaurant matching query does not exist. - -# An ItalianRestaurant which does not exist is also a Place which does not exist. ->>> try: -... ItalianRestaurant.objects.get(name='The Noodle Void') -... except Place.DoesNotExist: -... pass - -# MultipleObjectsReturned is also inherited. ->>> try: -... Restaurant.objects.get(id__lt=10) -... except Place.MultipleObjectsReturned: -... pass - -# Related objects work just as they normally do. - ->>> s1 = Supplier(name="Joe's Chickens", address='123 Sesame St') ->>> s1.save() ->>> s1.customers = [r, ir] ->>> s2 = Supplier(name="Luigi's Pasta", address='456 Sesame St') ->>> s2.save() ->>> s2.customers = [ir] - -# This won't work because the Place we select is not a Restaurant (it's a -# Supplier). ->>> p = Place.objects.get(name="Joe's Chickens") ->>> p.restaurant -Traceback (most recent call last): - ... -DoesNotExist: Restaurant matching query does not exist. - -# But we can descend from p to the Supplier child, as expected. ->>> p.supplier - - ->>> ir.provider.order_by('-name') -[, ] - ->>> Restaurant.objects.filter(provider__name__contains="Chickens") -[, ] ->>> ItalianRestaurant.objects.filter(provider__name__contains="Chickens") -[] - ->>> park1 = ParkingLot(name='Main St', address='111 Main St', main_site=s1) ->>> park1.save() ->>> park2 = ParkingLot(name='Well Lit', address='124 Sesame St', main_site=ir) ->>> park2.save() - ->>> Restaurant.objects.get(lot__name='Well Lit') - - -# The update() command can update fields in parent and child classes at once -# (although it executed multiple SQL queries to do so). ->>> Restaurant.objects.filter(serves_hot_dogs=True, name__contains='D').update(name='Demon Puppies', serves_hot_dogs=False) -1 ->>> r1 = Restaurant.objects.get(pk=r.pk) ->>> r1.serves_hot_dogs == False -True ->>> r1.name -u'Demon Puppies' - -# The values() command also works on fields from parent models. ->>> d = {'rating': 4, 'name': u'Ristorante Miron'} ->>> list(ItalianRestaurant.objects.values('name', 'rating')) == [d] -True - -# select_related works with fields from the parent object as if they were a -# normal part of the model. ->>> from django import db ->>> from django.conf import settings ->>> settings.DEBUG = True ->>> db.reset_queries() ->>> ItalianRestaurant.objects.all()[0].chef - ->>> len(db.connection.queries) -2 ->>> ItalianRestaurant.objects.select_related('chef')[0].chef - ->>> len(db.connection.queries) -3 ->>> settings.DEBUG = False - -"""} diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/modeltests/model_inheritance/tests.py new file mode 100644 index 000000000000..b94ce2670005 --- /dev/null +++ b/tests/modeltests/model_inheritance/tests.py @@ -0,0 +1,277 @@ +from operator import attrgetter + +from django.conf import settings +from django.core.exceptions import FieldError +from django.db import connection +from django.test import TestCase + +from models import (Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place, + Post, Restaurant, Student, StudentWorker, Supplier, Worker) + + +class ModelInheritanceTests(TestCase): + def test_abstract(self): + # The Student and Worker models both have 'name' and 'age' fields on + # them and inherit the __unicode__() method, just as with normal Python + # subclassing. This is useful if you want to factor out common + # information for programming purposes, but still completely + # independent separate models at the database level. + w1 = Worker.objects.create(name="Fred", age=35, job="Quarry worker") + w2 = Worker.objects.create(name="Barney", age=34, job="Quarry worker") + + s = Student.objects.create(name="Pebbles", age=5, school_class="1B") + + self.assertEqual(unicode(w1), "Worker Fred") + self.assertEqual(unicode(s), "Student Pebbles") + + # The children inherit the Meta class of their parents (if they don't + # specify their own). + self.assertQuerysetEqual( + Worker.objects.values("name"), [ + {"name": "Barney"}, + {"name": "Fred"}, + ], + lambda o: o + ) + + # Since Student does not subclass CommonInfo's Meta, it has the effect + # of completely overriding it. So ordering by name doesn't take place + # for Students. + self.assertEqual(Student._meta.ordering, []) + + # However, the CommonInfo class cannot be used as a normal model (it + # doesn't exist as a model). + self.assertRaises(AttributeError, lambda: CommonInfo.objects.all()) + + # A StudentWorker which does not exist is both a Student and Worker + # which does not exist. + self.assertRaises(Student.DoesNotExist, + StudentWorker.objects.get, pk=12321321 + ) + self.assertRaises(Worker.DoesNotExist, + StudentWorker.objects.get, pk=12321321 + ) + + # MultipleObjectsReturned is also inherited. + # This is written out "long form", rather than using __init__/create() + # because of a bug with diamond inheritance (#10808) + sw1 = StudentWorker() + sw1.name = "Wilma" + sw1.age = 35 + sw1.save() + sw2 = StudentWorker() + sw2.name = "Betty" + sw2.age = 24 + sw2.save() + + self.assertRaises(Student.MultipleObjectsReturned, + StudentWorker.objects.get, pk__lt=sw2.pk + 100 + ) + self.assertRaises(Worker.MultipleObjectsReturned, + StudentWorker.objects.get, pk__lt=sw2.pk + 100 + ) + + def test_multiple_table(self): + post = Post.objects.create(title="Lorem Ipsum") + # The Post model has distinct accessors for the Comment and Link models. + post.attached_comment_set.create(content="Save $ on V1agr@", is_spam=True) + post.attached_link_set.create( + content="The Web framework for perfections with deadlines.", + url="http://www.djangoproject.com/" + ) + + # The Post model doesn't have an attribute called + # 'attached_%(class)s_set'. + self.assertRaises(AttributeError, + getattr, post, "attached_%(class)s_set" + ) + + # The Place/Restaurant/ItalianRestaurant models all exist as + # independent models. However, the subclasses also have transparent + # access to the fields of their ancestors. + # Create a couple of Places. + p1 = Place.objects.create(name="Master Shakes", address="666 W. Jersey") + p2 = Place.objects.create(name="Ace Harware", address="1013 N. Ashland") + + # Test constructor for Restaurant. + r = Restaurant.objects.create( + name="Demon Dogs", + address="944 W. Fullerton", + serves_hot_dogs=True, + serves_pizza=False, + rating=2 + ) + # Test the constructor for ItalianRestaurant. + c = Chef.objects.create(name="Albert") + ir = ItalianRestaurant.objects.create( + name="Ristorante Miron", + address="1234 W. Ash", + serves_hot_dogs=False, + serves_pizza=False, + serves_gnocchi=True, + rating=4, + chef=c + ) + self.assertQuerysetEqual( + ItalianRestaurant.objects.filter(address="1234 W. Ash"), [ + "Ristorante Miron", + ], + attrgetter("name") + ) + ir.address = "1234 W. Elm" + ir.save() + self.assertQuerysetEqual( + ItalianRestaurant.objects.filter(address="1234 W. Elm"), [ + "Ristorante Miron", + ], + attrgetter("name") + ) + + # Make sure Restaurant and ItalianRestaurant have the right fields in + # the right order. + self.assertEqual( + [f.name for f in Restaurant._meta.fields], + ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs", "serves_pizza", "chef"] + ) + self.assertEqual( + [f.name for f in ItalianRestaurant._meta.fields], + ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs", "serves_pizza", "chef", "restaurant_ptr", "serves_gnocchi"], + ) + self.assertEqual(Restaurant._meta.ordering, ["-rating"]) + + # Even though p.supplier for a Place 'p' (a parent of a Supplier), a + # Restaurant object cannot access that reverse relation, since it's not + # part of the Place-Supplier Hierarchy. + self.assertQuerysetEqual(Place.objects.filter(supplier__name="foo"), []) + self.assertRaises(FieldError, + Restaurant.objects.filter, supplier__name="foo" + ) + + # Parent fields can be used directly in filters on the child model. + self.assertQuerysetEqual( + Restaurant.objects.filter(name="Demon Dogs"), [ + "Demon Dogs", + ], + attrgetter("name") + ) + self.assertQuerysetEqual( + ItalianRestaurant.objects.filter(address="1234 W. Elm"), [ + "Ristorante Miron", + ], + attrgetter("name") + ) + + # Filters against the parent model return objects of the parent's type. + p = Place.objects.get(name="Demon Dogs") + self.assertTrue(type(p) is Place) + + # Since the parent and child are linked by an automatically created + # OneToOneField, you can get from the parent to the child by using the + # child's name. + self.assertEqual( + p.restaurant, Restaurant.objects.get(name="Demon Dogs") + ) + self.assertEqual( + Place.objects.get(name="Ristorante Miron").restaurant.italianrestaurant, + ItalianRestaurant.objects.get(name="Ristorante Miron") + ) + self.assertEqual( + Restaurant.objects.get(name="Ristorante Miron").italianrestaurant, + ItalianRestaurant.objects.get(name="Ristorante Miron") + ) + + # This won't work because the Demon Dogs restaurant is not an Italian + # restaurant. + self.assertRaises(ItalianRestaurant.DoesNotExist, + lambda: p.restaurant.italianrestaurant + ) + # An ItalianRestaurant which does not exist is also a Place which does + # not exist. + self.assertRaises(Place.DoesNotExist, + ItalianRestaurant.objects.get, name="The Noodle Void" + ) + # MultipleObjectsReturned is also inherited. + self.assertRaises(Place.MultipleObjectsReturned, + Restaurant.objects.get, id__lt=12321 + ) + + # Related objects work just as they normally do. + s1 = Supplier.objects.create(name="Joe's Chickens", address="123 Sesame St") + s1.customers = [r, ir] + s2 = Supplier.objects.create(name="Luigi's Pasta", address="456 Sesame St") + s2.customers = [ir] + + # This won't work because the Place we select is not a Restaurant (it's + # a Supplier). + p = Place.objects.get(name="Joe's Chickens") + self.assertRaises(Restaurant.DoesNotExist, + lambda: p.restaurant + ) + + self.assertEqual(p.supplier, s1) + self.assertQuerysetEqual( + ir.provider.order_by("-name"), [ + "Luigi's Pasta", + "Joe's Chickens" + ], + attrgetter("name") + ) + self.assertQuerysetEqual( + Restaurant.objects.filter(provider__name__contains="Chickens"), [ + "Ristorante Miron", + "Demon Dogs", + ], + attrgetter("name") + ) + self.assertQuerysetEqual( + ItalianRestaurant.objects.filter(provider__name__contains="Chickens"), [ + "Ristorante Miron", + ], + attrgetter("name"), + ) + + park1 = ParkingLot.objects.create( + name="Main St", address="111 Main St", main_site=s1 + ) + park2 = ParkingLot.objects.create( + name="Well Lit", address="124 Sesame St", main_site=ir + ) + + self.assertEqual( + Restaurant.objects.get(lot__name="Well Lit").name, + "Ristorante Miron" + ) + + # The update() command can update fields in parent and child classes at + # once (although it executed multiple SQL queries to do so). + rows = Restaurant.objects.filter( + serves_hot_dogs=True, name__contains="D" + ).update( + name="Demon Puppies", serves_hot_dogs=False + ) + self.assertEqual(rows, 1) + + r1 = Restaurant.objects.get(pk=r.pk) + self.assertFalse(r1.serves_hot_dogs) + self.assertEqual(r1.name, "Demon Puppies") + + # The values() command also works on fields from parent models. + self.assertQuerysetEqual( + ItalianRestaurant.objects.values("name", "rating"), [ + {"rating": 4, "name": "Ristorante Miron"} + ], + lambda o: o + ) + + # select_related works with fields from the parent object as if they + # were a normal part of the model. + old_DEBUG = settings.DEBUG + starting_queries = len(connection.queries) + settings.DEBUG = True + + ItalianRestaurant.objects.all()[0].chef + self.assertEqual(len(connection.queries) - starting_queries, 2) + + starting_queries = len(connection.queries) + ItalianRestaurant.objects.select_related("chef")[0].chef + self.assertEqual(len(connection.queries) - starting_queries, 1) From da5f6ef9e04e6f5d43b4d93699cd56c82f7c2917 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Mon, 25 Oct 2010 20:52:04 +0000 Subject: [PATCH 379/902] [1.2.X] Fixed #10545 -- Mentioned that template context variables only exist in the scope of th block in which they're assigned. Thanks to yaniv.haber for the report and timo for the patch. Backport of [14350] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14351 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/custom-template-tags.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index c4d2315bd480..95ce27446029 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -790,7 +790,7 @@ difference between this case and the previous ``inclusion_tag`` example. Setting a variable in the context ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The above example simply output a value. Generally, it's more flexible if your +The above examples simply output a value. Generally, it's more flexible if your template tags set template variables instead of outputting values. That way, template authors can reuse the values that your template tags create. @@ -816,6 +816,13 @@ Here's how you'd use this new version of the tag: {% current_time "%Y-%M-%d %I:%M %p" %}

        The time is {{ current_time }}.

        +.. admonition:: Variable scope in context + + Any variable set in the context will only be available in the same ``block`` + of the template in which it was assigned. This behaviour is intentional; + it provides a scope for variables so that they don't conflict with + context in other blocks. + But, there's a problem with ``CurrentTimeNode2``: The variable name ``current_time`` is hard-coded. This means you'll need to make sure your template doesn't use ``{{ current_time }}`` anywhere else, because the From 4527535a935f3369c8c039e694ef823dfed7f566 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Mon, 25 Oct 2010 21:06:11 +0000 Subject: [PATCH 380/902] [1.2.X] Fixed #14047 -- Updated the CommonMiddleware docs to include sending broken link email notifications. Thanks to Leon Matthews for the report and patch. Backport of [14532] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14353 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/middleware.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index de2a99f90298..fa275d92d709 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -66,6 +66,9 @@ Adds a few conveniences for perfectionists: indexer would treat them as separate URLs -- so it's best practice to normalize URLs. + * Sends broken link notification emails to :setting:`MANAGERS` if + :setting:`SEND_BROKEN_LINK_EMAILS` is set to ``True``. + * Handles ETags based on the :setting:`USE_ETAGS` setting. If :setting:`USE_ETAGS` is set to ``True``, Django will calculate an ETag for each request by MD5-hashing the page content, and it'll take care of From cbc110a4675e987de5de075aa23b32296f5343ac Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 26 Oct 2010 18:11:22 +0000 Subject: [PATCH 381/902] [1.2.X] Fixed #14565 - No csrf_token on 404 pages Thanks to gvangool for report and patch. Backport of [14356] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14357 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/defaults.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/django/views/defaults.py b/django/views/defaults.py index 68b9ad697c6d..08c49e0ca48a 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -1,6 +1,8 @@ from django import http from django.template import Context, RequestContext, loader +from django.views.decorators.csrf import csrf_protect +@csrf_protect def page_not_found(request, template_name='404.html'): """ Default 404 handler. @@ -13,6 +15,7 @@ def page_not_found(request, template_name='404.html'): t = loader.get_template(template_name) # You need to create a 404.html template. return http.HttpResponseNotFound(t.render(RequestContext(request, {'request_path': request.path}))) +@csrf_protect def server_error(request, template_name='500.html'): """ 500 error handler. From 99fbf0a46da331fbf6799756efa7460536fdff93 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Wed, 27 Oct 2010 16:04:33 +0000 Subject: [PATCH 382/902] [1.2.X] Fixed #14398 -- Changed runfcgi command to interpret the umask option argument as an octal value. Thanks petteyg for report and aptiko for help with the fix. Backport of [14360] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14362 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/servers/fastcgi.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/django/core/servers/fastcgi.py b/django/core/servers/fastcgi.py index da52064fd6d2..7e724c251067 100644 --- a/django/core/servers/fastcgi.py +++ b/django/core/servers/fastcgi.py @@ -27,22 +27,22 @@ Optional Fcgi settings: (setting=value) protocol=PROTOCOL fcgi, scgi, ajp, ... (default fcgi) - host=HOSTNAME hostname to listen on.. + host=HOSTNAME hostname to listen on. port=PORTNUM port to listen on. socket=FILE UNIX socket to listen on. - method=IMPL prefork or threaded (default prefork) - maxrequests=NUMBER number of requests a child handles before it is + method=IMPL prefork or threaded (default prefork). + maxrequests=NUMBER number of requests a child handles before it is killed and a new child is forked (0 = no limit). - maxspare=NUMBER max number of spare processes / threads + maxspare=NUMBER max number of spare processes / threads. minspare=NUMBER min number of spare processes / threads. - maxchildren=NUMBER hard limit number of processes / threads + maxchildren=NUMBER hard limit number of processes / threads. daemonize=BOOL whether to detach from terminal. pidfile=FILE write the spawned process-id to this file. workdir=DIRECTORY change to this directory when daemonizing. - debug=BOOL set to true to enable flup tracebacks + debug=BOOL set to true to enable flup tracebacks. outlog=FILE write stdout to this file. errlog=FILE write stderr to this file. - umask=UMASK umask to use when daemonizing (default 022). + umask=UMASK umask to use when daemonizing, in octal notation (default 022). Examples: Run a "standard" fastcgi process on a file-descriptor @@ -166,7 +166,7 @@ def runfastcgi(argset=[], **kwargs): if options['errlog']: daemon_kwargs['err_log'] = options['errlog'] if options['umask']: - daemon_kwargs['umask'] = int(options['umask']) + daemon_kwargs['umask'] = int(options['umask'], 8) if daemonize: from django.utils.daemonize import become_daemon From f2d6c6f54d3dc05a022ede85dc15462d5fb0ea09 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Wed, 27 Oct 2010 16:10:48 +0000 Subject: [PATCH 383/902] [1.2.X] Documented options accepted by the runfcgi management command. Also, modified our custom management command option docs xref parser to also process those without a ``--`` prefix. -- Refs #14398. Backport of [14361] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14363 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/_ext/djangodocs.py | 14 ++++ docs/howto/deployment/fastcgi.txt | 20 +++--- docs/ref/django-admin.txt | 109 ++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 10 deletions(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index 325ed76cdc23..022ec3acd3ae 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -2,6 +2,7 @@ Sphinx plugins for Django documentation. """ import os +import re from docutils import nodes, transforms try: @@ -21,6 +22,9 @@ from sphinx.util.console import bold from sphinx.util.compat import Directive +# RE for option descriptions without a '--' prefix +simple_option_desc_re = re.compile( + r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') def setup(app): app.add_crossref_type( @@ -207,6 +211,16 @@ def parse_django_adminopt_node(env, sig, signode): if not count: firstname = optname count += 1 + if not count: + for m in simple_option_desc_re.finditer(sig): + optname, args = m.groups() + if count: + signode += addnodes.desc_addname(', ', ', ') + signode += addnodes.desc_name(optname, optname) + signode += addnodes.desc_addname(args, args) + if not count: + firstname = optname + count += 1 if not firstname: raise ValueError return firstname diff --git a/docs/howto/deployment/fastcgi.txt b/docs/howto/deployment/fastcgi.txt index 3bf231fb1999..ea14b97ff117 100644 --- a/docs/howto/deployment/fastcgi.txt +++ b/docs/howto/deployment/fastcgi.txt @@ -80,19 +80,19 @@ your :doc:`manage.py ` is), and then run the If you specify ``help`` as the only option after :djadmin:`runfcgi`, it'll display a list of all the available options. -You'll need to specify either a ``socket``, a ``protocol`` or both ``host`` and -``port``. Then, when you set up your Web server, you'll just need to point it at -the host/port or socket you specified when starting the FastCGI server. See the -examples_, below. +You'll need to specify either a :djadminopt:`socket`, a :djadminopt:`protocol` +or both :djadminopt:`host` and :djadminopt:`port`. Then, when you set up your +Web server, you'll just need to point it at the host/port or socket you +specified when starting the FastCGI server. See the examples_, below. Protocols --------- Django supports all the protocols that flup_ does, namely fastcgi_, `SCGI`_ and `AJP1.3`_ (the Apache JServ Protocol, version 1.3). Select your preferred -protocol by using the ``protocol=`` option with ``./manage.py -runfcgi`` -- where ```` may be one of: ``fcgi`` (the default), -``scgi`` or ``ajp``. For example:: +protocol by using the :djadminopt:`protocol=\ ` option +with ``./manage.py runfcgi`` -- where ```` may be one of: +``fcgi`` (the default), ``scgi`` or ``ajp``. For example:: ./manage.py runfcgi protocol=scgi @@ -132,8 +132,8 @@ Simply hitting ``Ctrl-C`` will stop and quit the FastCGI server. However, when you're dealing with background processes, you'll need to resort to the Unix ``kill`` command. -If you specify the ``pidfile`` option to :djadmin:`runfcgi`, you can kill the -running FastCGI daemon like this:: +If you specify the :djadminopt:`pidfile` option to :djadmin:`runfcgi`, you can +kill the running FastCGI daemon like this:: kill `cat $PIDFILE` @@ -390,7 +390,7 @@ prefix automatically. In the cases where Django cannot work out the prefix correctly and where you want the original value to be used in URLs, you can set the -``FORCE_SCRIPT_NAME`` setting in your main ``settings`` file. This sets the +:setting:`FORCE_SCRIPT_NAME` setting in your main ``settings`` file. This sets the script name uniformly for every URL served via that settings file. Thus you'll need to use different settings files if you want different sets of URLs to have different script names in this case, but that is a rare situation. diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 0a1cd25cae9b..7be09dcaf78a 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -498,6 +498,115 @@ supports the FastCGI protocol. See the :doc:`FastCGI deployment documentation .. _flup: http://www.saddi.com/software/flup/ +The options accepted by this command are passed to the FastCGI library and +don't use the ``'--'`` prefix as is usual for other Django management commands. + +.. django-admin-option:: protocol + +``protocol=PROTOCOL`` + +Protocol to use. *PROTOCOL* can be ``fcgi``, ``scgi``, ``ajp``, etc. +(default is ``fcgi``) + +.. django-admin-option:: host + +``host=HOSTNAME`` + +Hostname to listen on. + +.. django-admin-option:: port + +``port=PORTNUM`` + +Port to listen on. + +.. django-admin-option:: socket + +``socket=FILE`` + +UNIX socket to listen on. + +.. django-admin-option:: method + +``method=IMPL`` + +Possible values: ``prefork`` or ``threaded`` (default ``prefork``) + +.. django-admin-option:: maxrequests + +``maxrequests=NUMBER`` + +Number of requests a child handles before it is killed and a new child is +forked (0 means no limit). + +.. django-admin-option:: maxspare + +``maxspare=NUMBER`` + +Max number of spare processes / threads. + +.. django-admin-option:: minspare + +``minspare=NUMBER`` + +Min number of spare processes / threads. + +.. django-admin-option:: maxchildren + +``maxchildren=NUMBER`` + +Hard limit number of processes / threads. + +.. django-admin-option:: daemonize + +``daemonize=BOOL`` + +Whether to detach from terminal. + +.. django-admin-option:: pidfile + +``pidfile=FILE`` + +Write the spawned process-id to file *FILE*. + +.. django-admin-option:: workdir + +``workdir=DIRECTORY`` + +Change to directory *DIRECTORY* when daemonizing. + +.. django-admin-option:: debug + +``debug=BOOL`` + +Set to true to enable flup tracebacks. + +.. django-admin-option:: outlog + +``outlog=FILE`` + +Write stdout to the *FILE* file. + +.. django-admin-option:: errlog + +``errlog=FILE`` + +Write stderr to the *FILE* file. + +.. django-admin-option:: umask + +``umask=UMASK`` + +Umask to use when daemonizing. The value is interpeted as an octal number +(default value is ``022``). + +Example usage:: + + django-admin.py runfcgi socket=/tmp/fcgi.sock method=prefork daemonize=true \ + pidfile=/var/run/django-fcgi.pid + +Run a FastCGI server as a daemon and write the spawned PID in a file. + runserver [port or ipaddr:port] ------------------------------- From e8c413a3e24f9828253eaadde3bdd66149912234 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 27 Oct 2010 20:08:54 +0000 Subject: [PATCH 384/902] [1.2.X] Fixed #14577 -- fixed a docstring typo. Thanks to dauerbaustelle for the report and patch. Backport of [14364]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14365 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/files/uploadhandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py index 6c769780e501..2afb79e0513e 100755 --- a/django/core/files/uploadhandler.py +++ b/django/core/files/uploadhandler.py @@ -109,7 +109,7 @@ def file_complete(self, file_size): Signal that a file has completed. File size corresponds to the actual size accumulated by all the chunks. - Subclasses must should return a valid ``UploadedFile`` object. + Subclasses should return a valid ``UploadedFile`` object. """ raise NotImplementedError() From 12652baca69cb263382cb4992155db224f44400e Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Thu, 28 Oct 2010 01:56:41 +0000 Subject: [PATCH 385/902] [1.2.X] Fixed #14578 -- Corrected a typo in the Models topic docs. Thanks to tobych for the report and patch. Backport of [14369] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14370 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/models.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 22870535533c..a5370b6c590d 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -1076,7 +1076,7 @@ attribute when you use the proxy. This is easy:: ordering = ["username"] proxy = True -Now normal ``User`` queries will be unorderd and ``OrderedUser`` queries will +Now normal ``User`` queries will be unordered and ``OrderedUser`` queries will be ordered by ``username``. QuerySets still return the model that was requested From fcc283a52de2185787d5ae4b0a04164f2a1e8882 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 28 Oct 2010 11:58:30 +0000 Subject: [PATCH 386/902] [1.2.X] Reverted changeset [14356] That fix for #14565 introduced test failures. A better fix will follow shortly. Refs #14565 Backport of [14376] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14379 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/views/defaults.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/django/views/defaults.py b/django/views/defaults.py index 08c49e0ca48a..68b9ad697c6d 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -1,8 +1,6 @@ from django import http from django.template import Context, RequestContext, loader -from django.views.decorators.csrf import csrf_protect -@csrf_protect def page_not_found(request, template_name='404.html'): """ Default 404 handler. @@ -15,7 +13,6 @@ def page_not_found(request, template_name='404.html'): t = loader.get_template(template_name) # You need to create a 404.html template. return http.HttpResponseNotFound(t.render(RequestContext(request, {'request_path': request.path}))) -@csrf_protect def server_error(request, template_name='500.html'): """ 500 error handler. From 36dd74446047570bd6bc1a8bdb8e2eaf3eb39a44 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 28 Oct 2010 11:58:51 +0000 Subject: [PATCH 387/902] [1.2.X] Fixed #14565 - No csrf_token on 404 page. This solution doesn't have the negative side-effects of [14356]. Backport of [14377] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14380 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/middleware/csrf.py | 38 ++++++++++--------- django/views/decorators/csrf.py | 16 ++++++++ django/views/defaults.py | 8 ++++ tests/regressiontests/csrf_tests/tests.py | 10 ++++- tests/regressiontests/views/tests/defaults.py | 17 +++++++-- 5 files changed, 68 insertions(+), 21 deletions(-) diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index 4a80428e556e..67b02f04f163 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -84,18 +84,22 @@ class CsrfViewMiddleware(object): This middleware should be used in conjunction with the csrf_token template tag. """ + # The _accept and _reject methods currently only exist for the sake of the + # requires_csrf_token decorator. + def _accept(self, request): + # Avoid checking the request twice by adding a custom attribute to + # request. This will be relevant when both decorator and middleware + # are used. + request.csrf_processing_done = True + return None + + def _reject(self, request, reason): + return _get_failure_view()(request, reason=reason) + def process_view(self, request, callback, callback_args, callback_kwargs): if getattr(request, 'csrf_processing_done', False): return None - reject = lambda s: _get_failure_view()(request, reason=s) - def accept(): - # Avoid checking the request twice by adding a custom attribute to - # request. This will be relevant when both decorator and middleware - # are used. - request.csrf_processing_done = True - return None - # If the user doesn't have a CSRF cookie, generate one and store it in the # request, so it's available to the view. We'll store it in a cookie when # we reach the response. @@ -124,7 +128,7 @@ def accept(): # the creation of CSRF cookies, so that everything else continues to # work exactly the same (e.g. cookies are sent etc), but before the # any branches that call reject() - return accept() + return self._accept(request) if request.is_ajax(): # .is_ajax() is based on the presence of X-Requested-With. In @@ -149,20 +153,20 @@ def accept(): # allowing the cross-domain POST request. # # So in all cases, it is safe to allow these requests through. - return accept() + return self._accept(request) if request.is_secure(): # Strict referer checking for HTTPS referer = request.META.get('HTTP_REFERER') if referer is None: - return reject(REASON_NO_REFERER) + return self._reject(request, REASON_NO_REFERER) # The following check ensures that the referer is HTTPS, # the domains match and the ports match. This might be too strict. good_referer = 'https://%s/' % request.get_host() if not referer.startswith(good_referer): - return reject(REASON_BAD_REFERER % - (referer, good_referer)) + return self._reject(request, REASON_BAD_REFERER % + (referer, good_referer)) # If the user didn't already have a CSRF cookie, then fall back to # the Django 1.1 method (hash of session ID), so a request is not @@ -176,7 +180,7 @@ def accept(): # No CSRF cookie and no session cookie. For POST requests, # we insist on a CSRF cookie, and in this way we can avoid # all CSRF attacks, including login CSRF. - return reject(REASON_NO_COOKIE) + return self._reject(request, REASON_NO_COOKIE) else: csrf_token = request.META["CSRF_COOKIE"] @@ -185,11 +189,11 @@ def accept(): if request_csrf_token != csrf_token: if cookie_is_new: # probably a problem setting the CSRF cookie - return reject(REASON_NO_CSRF_COOKIE) + return self._reject(request, REASON_NO_CSRF_COOKIE) else: - return reject(REASON_BAD_TOKEN) + return self._reject(request, REASON_BAD_TOKEN) - return accept() + return self._accept(request) def process_response(self, request, response): if getattr(response, 'csrf_processing_done', False): diff --git a/django/views/decorators/csrf.py b/django/views/decorators/csrf.py index 782cb1ee89a9..89f676fe1669 100644 --- a/django/views/decorators/csrf.py +++ b/django/views/decorators/csrf.py @@ -14,6 +14,22 @@ using the decorator multiple times, is harmless and efficient. """ + +class _EnsureCsrfToken(CsrfViewMiddleware): + # We need this to behave just like the CsrfViewMiddleware, but not reject + # requests. + def _reject(self, request, reason): + return None + + +requires_csrf_token = decorator_from_middleware(_EnsureCsrfToken) +requires_csrf_token.__name__ = 'requires_csrf_token' +csrf_protect.__doc__ = """ +Use this decorator on views that need a correct csrf_token available to +RequestContext, but without the CSRF protection that csrf_protect +enforces. +""" + def csrf_response_exempt(view_func): """ Modifies a view function so that its response is exempt diff --git a/django/views/defaults.py b/django/views/defaults.py index 68b9ad697c6d..29cdf8244d00 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -1,6 +1,11 @@ from django import http +from django.views.decorators.csrf import requires_csrf_token from django.template import Context, RequestContext, loader + +# This can be called when CsrfViewMiddleware.process_view has not run, therefore +# need @requires_csrf_token in case the template needs {% csrf_token %}. +@requires_csrf_token def page_not_found(request, template_name='404.html'): """ Default 404 handler. @@ -13,6 +18,8 @@ def page_not_found(request, template_name='404.html'): t = loader.get_template(template_name) # You need to create a 404.html template. return http.HttpResponseNotFound(t.render(RequestContext(request, {'request_path': request.path}))) + +@requires_csrf_token def server_error(request, template_name='500.html'): """ 500 error handler. @@ -23,6 +30,7 @@ def server_error(request, template_name='500.html'): t = loader.get_template(template_name) # You need to create a 500.html template. return http.HttpResponseServerError(t.render(Context({}))) + def shortcut(request, content_type_id, object_id): # TODO: Remove this in Django 2.0. # This is a legacy view that depends on the contenttypes framework. diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py index 9030d397ab65..9f74fc5a0ac4 100644 --- a/tests/regressiontests/csrf_tests/tests.py +++ b/tests/regressiontests/csrf_tests/tests.py @@ -3,7 +3,7 @@ from django.test import TestCase from django.http import HttpRequest, HttpResponse from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware -from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt +from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, requires_csrf_token from django.core.context_processors import csrf from django.contrib.sessions.middleware import SessionMiddleware from django.utils.importlib import import_module @@ -322,6 +322,14 @@ def test_get_token_for_exempt_view(self): resp = token_view(req) self._check_token_present(resp) + def test_get_token_for_requires_csrf_token_view(self): + """ + Check that get_token works for a view decorated solely with requires_csrf_token + """ + req = self._get_GET_csrf_cookie_request() + resp = requires_csrf_token(token_view)(req) + self._check_token_present(resp) + def test_token_node_with_new_csrf_cookie(self): """ Check that CsrfTokenNode works when a CSRF cookie is created by diff --git a/tests/regressiontests/views/tests/defaults.py b/tests/regressiontests/views/tests/defaults.py index bc5b223e5604..5783a824cd4d 100644 --- a/tests/regressiontests/views/tests/defaults.py +++ b/tests/regressiontests/views/tests/defaults.py @@ -9,6 +9,8 @@ class DefaultsTests(TestCase): """Test django views in django/views/defaults.py""" fixtures = ['testdata.json'] + non_existing_urls = ['/views/non_existing_url/', # this is in urls.py + '/views/other_non_existing_url/'] # this NOT in urls.py def test_shortcut_with_absolute_url(self): "Can view a shortcut for an Author object that has a get_absolute_url method" @@ -49,12 +51,21 @@ def test_bad_content_type(self): def test_page_not_found(self): "A 404 status is returned by the page_not_found view" - non_existing_urls = ['/views/non_existing_url/', # this is in urls.py - '/views/other_non_existing_url/'] # this NOT in urls.py - for url in non_existing_urls: + for url in self.non_existing_urls: response = self.client.get(url) self.assertEquals(response.status_code, 404) + def test_csrf_token_in_404(self): + """ + The 404 page should have the csrf_token available in the context + """ + # See ticket #14565 + for url in self.non_existing_urls: + response = self.client.get(url) + csrf_token = response.context['csrf_token'] + self.assertNotEqual(str(csrf_token), 'NOTPROVIDED') + self.assertNotEqual(str(csrf_token), '') + def test_server_error(self): "The server_error view raises a 500 status" response = self.client.get('/views/server_error/') From 4b8a391a3bd4eb605d999515c904840d7c72e97e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Oct 2010 12:15:05 +0000 Subject: [PATCH 388/902] [1.2.X] Fixed #14395 -- Fixed typo in error message in db/__init__.py. Thanks, agabel Backport of r13990 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14381 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/__init__.py b/django/db/__init__.py index 4c4faef694be..7136b2f25832 100644 --- a/django/db/__init__.py +++ b/django/db/__init__.py @@ -32,7 +32,7 @@ } if DEFAULT_DB_ALIAS not in settings.DATABASES: - raise ImproperlyConfigured("You must default a '%s' database" % DEFAULT_DB_ALIAS) + raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS) for alias, database in settings.DATABASES.items(): if 'ENGINE' not in database: From 5d0f4edb8bf6f3e77fd3e1f25e04d8efddcac1c8 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Oct 2010 12:15:27 +0000 Subject: [PATCH 389/902] [1.2.X] Fixed #14409 -- Fixed typo in docs/internals/documentation.txt. Thanks, kurtmckee Backport of r13991 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14382 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/documentation.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/internals/documentation.txt b/docs/internals/documentation.txt index 5185ec7feb08..36270eafb7e4 100644 --- a/docs/internals/documentation.txt +++ b/docs/internals/documentation.txt @@ -15,10 +15,10 @@ Sphinx -- ``easy_install Sphinx`` should do the trick. .. note:: - Generation of the Django documentation will work with Sphinx version 0.6 - or newer, but we recommend going straigh to Sphinx 1.0.2 or newer. + The Django documentation can be generated with Sphinx version 0.6 or + newer, but we recommend using Sphinx 1.0.2 or newer. -Then, building the html is easy; just ``make html`` from the ``docs`` directory. +Then, building the HTML is easy; just ``make html`` from the ``docs`` directory. To get started contributing, you'll want to read the `reStructuredText Primer`__. After that, you'll want to read about the `Sphinx-specific markup`__ From 4890ff129200f3cbb86027a173ff9ba4cedc11c4 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Oct 2010 12:15:46 +0000 Subject: [PATCH 390/902] [1.2.X] Negligible formatting improvement to an error in management/sql.py Backport of r13992 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14383 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/sql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 17c145e92755..86d91fa8b787 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -15,7 +15,7 @@ def sql_create(app, style, connection): raise CommandError("Django doesn't know which syntax to use for your SQL statements,\n" + "because you haven't specified the ENGINE setting for the database.\n" + "Edit your settings file and change DATBASES['default']['ENGINE'] to something like\n" + - "'django.db.backends.postgresql' or 'django.db.backends.mysql'") + "'django.db.backends.postgresql' or 'django.db.backends.mysql'.") # Get installed models, so we generate REFERENCES right. # We trim models from the current app so that the sqlreset command does not From c120794b9d1efa20732ec3bf389e7487a7408e8b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Oct 2010 12:16:04 +0000 Subject: [PATCH 391/902] [1.2.X] Fixed the ugly spacing of 'BEGIN;' and 'COMMIT;' in the output of the sql/sqlall commands. They're no longer smooshed with the rest of the SQL. Backport of r13993 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14384 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/core/management/base.py b/django/core/management/base.py index 3e20e2db29f2..282fbd7fb4cf 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -225,10 +225,10 @@ def execute(self, *args, **options): from django.db import connections, DEFAULT_DB_ALIAS connection = connections[options.get('database', DEFAULT_DB_ALIAS)] if connection.ops.start_transaction_sql(): - self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql())) + self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) + '\n') self.stdout.write(output) if self.output_transaction: - self.stdout.write(self.style.SQL_KEYWORD("COMMIT;") + '\n') + self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;") + '\n') except CommandError, e: self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e))) sys.exit(1) From 181f26428abf3679e2b747fd49b88b0f104918f2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Oct 2010 12:16:21 +0000 Subject: [PATCH 392/902] [1.2.X] Fixed #14414 -- Improved contenttypes shortcut() view to check that the ContentType has a model_class(). Thanks, subsume Backport of r13994 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14385 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/contenttypes/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django/contrib/contenttypes/views.py b/django/contrib/contenttypes/views.py index ba82564974f4..ac0feffe7a0d 100644 --- a/django/contrib/contenttypes/views.py +++ b/django/contrib/contenttypes/views.py @@ -8,6 +8,8 @@ def shortcut(request, content_type_id, object_id): # Look up the object, making sure it's got a get_absolute_url() function. try: content_type = ContentType.objects.get(pk=content_type_id) + if not content_type.model_class(): + raise http.Http404("Content type %s object has no associated model" % content_type_id) obj = content_type.get_object_for_this_type(pk=object_id) except (ObjectDoesNotExist, ValueError): raise http.Http404("Content type %s object %s doesn't exist" % (content_type_id, object_id)) From 3a679cc1c458c4e31fc22e511e9bc51ed939f102 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Oct 2010 12:16:39 +0000 Subject: [PATCH 393/902] [1.2.X] Fixed #14412 -- Pointed contrib.comments comments-url-redirect URLpattern at the actual view instead of the deprecated view. Thanks, subsume Backport of r13995 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14386 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/comments/urls.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/django/contrib/comments/urls.py b/django/contrib/comments/urls.py index 2bfefa3e2da9..d9037799ddee 100644 --- a/django/contrib/comments/urls.py +++ b/django/contrib/comments/urls.py @@ -12,6 +12,5 @@ ) urlpatterns += patterns('', - url(r'^cr/(\d+)/(.+)/$', 'django.views.defaults.shortcut', name='comments-url-redirect'), + url(r'^cr/(\d+)/(.+)/$', 'django.contrib.contenttypes.views.shortcut', name='comments-url-redirect'), ) - From 29ea1bef215040d34fb0f03c7f39f3a82515febf Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 28 Oct 2010 13:00:08 +0000 Subject: [PATCH 394/902] [1.2.X] Fixed #14471 -- Corrected a regression in the use of methods on custom managers on related querysets. Thanks to Lucky for the report. Backport of r14389 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14390 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/related.py | 8 +++---- .../multiple_database/models.py | 15 ++++++++++++- .../multiple_database/tests.py | 22 +++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 232d5d5e74ff..079b32169211 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -420,7 +420,7 @@ def add(self, *objs): def create(self, **kwargs): kwargs.update({rel_field.name: instance}) db = router.db_for_write(rel_model, instance=instance) - return super(RelatedManager, self).using(db).create(**kwargs) + return super(RelatedManager, self.db_manager(db)).create(**kwargs) create.alters_data = True def get_or_create(self, **kwargs): @@ -428,7 +428,7 @@ def get_or_create(self, **kwargs): # ForeignRelatedObjectsDescriptor knows about. kwargs.update({rel_field.name: instance}) db = router.db_for_write(rel_model, instance=instance) - return super(RelatedManager, self).using(db).get_or_create(**kwargs) + return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs) get_or_create.alters_data = True # remove() and clear() are only provided if the ForeignKey can have a value of null. @@ -517,7 +517,7 @@ def create(self, **kwargs): opts = through._meta raise AttributeError("Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)) db = router.db_for_write(self.instance.__class__, instance=self.instance) - new_obj = super(ManyRelatedManager, self).using(db).create(**kwargs) + new_obj = super(ManyRelatedManager, self.db_manager(db)).create(**kwargs) self.add(new_obj) return new_obj create.alters_data = True @@ -525,7 +525,7 @@ def create(self, **kwargs): def get_or_create(self, **kwargs): db = router.db_for_write(self.instance.__class__, instance=self.instance) obj, created = \ - super(ManyRelatedManager, self).using(db).get_or_create(**kwargs) + super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs) # We only need to add() if created because if we got an object back # from get() then the relationship already exists. if created: diff --git a/tests/regressiontests/multiple_database/models.py b/tests/regressiontests/multiple_database/models.py index e0be761ca06c..ce7182870027 100644 --- a/tests/regressiontests/multiple_database/models.py +++ b/tests/regressiontests/multiple_database/models.py @@ -30,7 +30,21 @@ def __unicode__(self): class Meta: ordering = ('name',) +# This book manager doesn't do anything interesting; it just +# exists to strip out the 'extra_arg' argument to certain +# calls. This argument is used to establish that the BookManager +# is actually getting used when it should be. +class BookManager(models.Manager): + def create(self, *args, **kwargs): + kwargs.pop('extra_arg', None) + return super(BookManager, self).create(*args, **kwargs) + + def get_or_create(self, *args, **kwargs): + kwargs.pop('extra_arg', None) + return super(BookManager, self).get_or_create(*args, **kwargs) + class Book(models.Model): + objects = BookManager() title = models.CharField(max_length=100) published = models.DateField() authors = models.ManyToManyField(Person) @@ -60,4 +74,3 @@ class UserProfile(models.Model): class Meta: ordering = ('flavor',) - diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py index f27d8b5a28f5..484cac6adab6 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/regressiontests/multiple_database/tests.py @@ -890,6 +890,28 @@ def test_subquery(self): except ValueError: pass + def test_related_manager(self): + "Related managers return managers, not querysets" + mark = Person.objects.using('other').create(name="Mark Pilgrim") + + # extra_arg is removed by the BookManager's implementation of + # create(); but the BookManager's implementation won't get called + # unless edited returns a Manager, not a queryset + mark.book_set.create(title="Dive into Python", + published=datetime.date(2009, 5, 4), + extra_arg=True) + + mark.book_set.get_or_create(title="Dive into Python", + published=datetime.date(2009, 5, 4), + extra_arg=True) + + mark.edited.create(title="Dive into Water", + published=datetime.date(2009, 5, 4), + extra_arg=True) + + mark.edited.get_or_create(title="Dive into Water", + published=datetime.date(2009, 5, 4), + extra_arg=True) class TestRouter(object): # A test router. The behaviour is vaguely master/slave, but the From 20c6f3f3d1a58960fcc141eb461f244ebdf266d0 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 31 Oct 2010 02:35:18 +0000 Subject: [PATCH 395/902] [1.2.X] Fixed #13503 -- Corrected misleading custom permission example in the docs. Thanks Daniel Moisset for the report. Backport of [14403] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14404 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 95b2e137dc82..6c144d9d4f0b 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -1181,19 +1181,23 @@ Custom permissions To create custom permissions for a given model object, use the ``permissions`` :ref:`model Meta attribute `. -This example model creates three custom permissions:: +This example Task model creates three custom permissions, i.e., actions users +can or cannot do with Task instances, specific to your appication:: - class USCitizen(models.Model): - # ... + class Task(models.Model): + ... class Meta: permissions = ( - ("can_drive", "Can drive"), - ("can_vote", "Can vote in elections"), - ("can_drink", "Can drink alcohol"), + ("can_view", "Can see available tasks"), + ("can_change_status", "Can change the status of tasks"), + ("can_close", "Can remove a task by setting its status as closed"), ) The only thing this does is create those extra permissions when you run -:djadmin:`manage.py syncdb `. +:djadmin:`manage.py syncdb `. Your code is in charge of checking the +value of these permissions when an user is trying to access the functionality +provided by the application (viewing tasks, changing the status of tasks, +closing tasks.) API reference ------------- From 3a25098c25a30d2eb7bf7d0f9e14d6db74a99dcf Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 31 Oct 2010 16:26:04 +0000 Subject: [PATCH 396/902] [1.2.X] Fixed the auth tests so they work when the AUTHENTICATION_BACKENDS setting is a list. Thanks to Patrick Altman for the report. Backport of [14406]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14407 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/tests/auth_backends.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django/contrib/auth/tests/auth_backends.py b/django/contrib/auth/tests/auth_backends.py index 8eaf2cb3e7dc..bc61e99c6b68 100644 --- a/django/contrib/auth/tests/auth_backends.py +++ b/django/contrib/auth/tests/auth_backends.py @@ -148,7 +148,7 @@ class RowlevelBackendTest(TestCase): def setUp(self): self.curr_auth = settings.AUTHENTICATION_BACKENDS - settings.AUTHENTICATION_BACKENDS = self.curr_auth + (self.backend,) + settings.AUTHENTICATION_BACKENDS = tuple(self.curr_auth) + (self.backend,) self.user1 = User.objects.create_user('test', 'test@example.com', 'test') self.user2 = User.objects.create_user('test2', 'test2@example.com', 'test') self.user3 = User.objects.create_user('test3', 'test3@example.com', 'test') @@ -226,7 +226,7 @@ class NoAnonymousUserBackendTest(TestCase): def setUp(self): self.curr_auth = settings.AUTHENTICATION_BACKENDS - settings.AUTHENTICATION_BACKENDS = self.curr_auth + (self.backend,) + settings.AUTHENTICATION_BACKENDS = tuple(self.curr_auth) + (self.backend,) self.user1 = AnonymousUser() def tearDown(self): From d1175f815bcef8a0f25f803f914e707cc67be0c3 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sun, 31 Oct 2010 23:46:14 +0000 Subject: [PATCH 397/902] [1.2.X] Reverted r14196, restoring the 1.2 test suite to a passing state. Refs #14455. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14410 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/localflavor/id/id_choices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/localflavor/id/id_choices.py b/django/contrib/localflavor/id/id_choices.py index 5b2a0d7d2668..ed1ea017b90e 100644 --- a/django/contrib/localflavor/id/id_choices.py +++ b/django/contrib/localflavor/id/id_choices.py @@ -6,7 +6,6 @@ # I decided to use unambiguous and consistent (some are common) 3-letter codes. PROVINCE_CHOICES = ( - ('ACE', _('Aceh')), ('BLI', _('Bali')), ('BTN', _('Banten')), ('BKL', _('Bengkulu')), @@ -26,6 +25,7 @@ ('LPG', _('Lampung')), ('MLK', _('Maluku')), ('MUT', _('Maluku Utara')), + ('NAD', _('Nanggroe Aceh Darussalam')), ('NTB', _('Nusa Tenggara Barat')), ('NTT', _('Nusa Tenggara Timur')), ('PPA', _('Papua')), From 2854336cc287a05531d1775e9bf80016329b60cd Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Mon, 1 Nov 2010 22:12:31 +0000 Subject: [PATCH 398/902] [1.2.X] Fixed regression introduced in r13755 that prevented the running of the GEOS/GDAL test suites without configuring Django settings; moved reference geometry data from Python module to compressed JSON fixture; put in workaround in tests for GDAL bug #3783. Backport of r14414 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14415 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/gdal/tests/test_ds.py | 49 ++--- django/contrib/gis/gdal/tests/test_geom.py | 85 ++++----- django/contrib/gis/geometry/test_data.py | 100 ++++++++++ django/contrib/gis/geos/tests/test_geos.py | 144 +++++++------- .../contrib/gis/tests/data/geometries.json.gz | Bin 0 -> 9100 bytes django/contrib/gis/tests/geometries.py | 180 ------------------ 6 files changed, 232 insertions(+), 326 deletions(-) create mode 100644 django/contrib/gis/geometry/test_data.py create mode 100644 django/contrib/gis/tests/data/geometries.json.gz delete mode 100644 django/contrib/gis/tests/geometries.py diff --git a/django/contrib/gis/gdal/tests/test_ds.py b/django/contrib/gis/gdal/tests/test_ds.py index 1abea785cacc..e1083b2a35d3 100644 --- a/django/contrib/gis/gdal/tests/test_ds.py +++ b/django/contrib/gis/gdal/tests/test_ds.py @@ -1,20 +1,7 @@ import os, os.path, unittest -from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError +from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError, GDAL_VERSION from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString -from django.contrib import gis - -# Path for SHP files -data_path = os.path.join(os.path.dirname(gis.__file__), 'tests' + os.sep + 'data') -def get_ds_file(name, ext): - return os.sep.join([data_path, name, name + '.%s' % ext]) - -# Test SHP data source object -class TestDS: - def __init__(self, name, **kwargs): - ext = kwargs.pop('ext', 'shp') - self.ds = get_ds_file(name, ext) - for key, value in kwargs.items(): - setattr(self, key, value) +from django.contrib.gis.geometry.test_data import get_ds_file, TestDS # List of acceptable data sources. ds_list = (TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile', @@ -28,7 +15,7 @@ def __init__(self, name, **kwargs): extent=(1.0, 2.0, 100.0, 523.5), # Min/Max from CSV field_values={'POINT_X' : ['1.0', '5.0', '100.0'], 'POINT_Y' : ['2.0', '23.0', '523.5'], 'NUM' : ['5', '17', '23']}, fids=range(1,4)), - TestDS('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3, + TestDS('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3, driver='ESRI Shapefile', fields={'float' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,}, extent=(-1.01513,-0.558245,0.161876,0.839637), # Got extent from QGIS @@ -63,7 +50,7 @@ def test01_valid_shp(self): pass else: self.fail('Expected an IndexError!') - + def test02_invalid_shp(self): "Testing invalid SHP files for the Data Source." for source in bad_ds: @@ -76,7 +63,7 @@ def test03a_layers(self): ds = DataSource(source.ds) # Incrementing through each layer, this tests DataSource.__iter__ - for layer in ds: + for layer in ds: # Making sure we get the number of features we expect self.assertEqual(len(layer), source.nfeat) @@ -85,16 +72,22 @@ def test03a_layers(self): self.assertEqual(source.nfld, len(layer.fields)) # Testing the layer's extent (an Envelope), and it's properties - self.assertEqual(True, isinstance(layer.extent, Envelope)) - self.assertAlmostEqual(source.extent[0], layer.extent.min_x, 5) - self.assertAlmostEqual(source.extent[1], layer.extent.min_y, 5) - self.assertAlmostEqual(source.extent[2], layer.extent.max_x, 5) - self.assertAlmostEqual(source.extent[3], layer.extent.max_y, 5) + if source.driver == 'VRT' and (GDAL_VERSION > (1, 7, 0) and GDAL_VERSION < (1, 7, 3)): + # There's a known GDAL regression with retrieving the extent + # of a VRT layer in versions 1.7.0-1.7.2: + # http://trac.osgeo.org/gdal/ticket/3783 + pass + else: + self.assertEqual(True, isinstance(layer.extent, Envelope)) + self.assertAlmostEqual(source.extent[0], layer.extent.min_x, 5) + self.assertAlmostEqual(source.extent[1], layer.extent.min_y, 5) + self.assertAlmostEqual(source.extent[2], layer.extent.max_x, 5) + self.assertAlmostEqual(source.extent[3], layer.extent.max_y, 5) # Now checking the field names. flds = layer.fields for f in flds: self.assertEqual(True, f in source.fields) - + # Negative FIDs are not allowed. self.assertRaises(OGRIndexError, layer.__getitem__, -1) self.assertRaises(OGRIndexError, layer.__getitem__, 50000) @@ -115,7 +108,7 @@ def test03a_layers(self): for fld_name in fld_names: self.assertEqual(source.field_values[fld_name][i], feat.get(fld_name)) print "\nEND - expecting out of range feature id error; safe to ignore." - + def test03b_layer_slice(self): "Test indexing and slicing on Layers." # Using the first data-source because the same slice @@ -146,7 +139,7 @@ def get_layer(): # Making sure we can call OGR routines on the Layer returned. lyr = get_layer() self.assertEqual(source.nfeat, len(lyr)) - self.assertEqual(source.gtype, lyr.geom_type.num) + self.assertEqual(source.gtype, lyr.geom_type.num) def test04_features(self): "Testing Data Source Features." @@ -170,7 +163,7 @@ def test04_features(self): # Testing Feature.__iter__ for fld in feat: self.assertEqual(True, fld.name in source.fields.keys()) - + def test05_geometries(self): "Testing Geometries from Data Source Features." for source in ds_list: @@ -223,7 +216,7 @@ def test06_spatial_filter(self): # should indicate that there are 3 features in the Layer. lyr.spatial_filter = None self.assertEqual(3, len(lyr)) - + def suite(): s = unittest.TestSuite() s.addTest(unittest.makeSuite(DataSourceTest)) diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index 5aa1f244ae8c..f3d1ffb929c3 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -2,9 +2,9 @@ from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, \ OGRException, OGRIndexError, SpatialReference, CoordTransform, \ gdal_version -from django.contrib.gis.tests.geometries import * +from django.contrib.gis.geometry.test_data import TestDataMixin -class OGRGeomTest(unittest.TestCase): +class OGRGeomTest(unittest.TestCase, TestDataMixin): "This tests the OGR Geometry." def test00a_geomtype(self): @@ -55,7 +55,7 @@ def test00b_geomtype_25d(self): def test01a_wkt(self): "Testing WKT output." - for g in wkt_out: + for g in self.geometries.wkt_out: geom = OGRGeometry(g.wkt) self.assertEqual(g.wkt, geom.wkt) @@ -72,13 +72,13 @@ def test01a_ewkt(self): def test01b_gml(self): "Testing GML output." - for g in wkt_out: + for g in self.geometries.wkt_out: geom = OGRGeometry(g.wkt) self.assertEqual(g.gml, geom.gml) def test01c_hex(self): "Testing HEX input/output." - for g in hex_wkt: + for g in self.geometries.hex_wkt: geom1 = OGRGeometry(g.wkt) self.assertEqual(g.hex, geom1.hex) # Constructing w/HEX @@ -88,7 +88,7 @@ def test01c_hex(self): def test01d_wkb(self): "Testing WKB input/output." from binascii import b2a_hex - for g in hex_wkt: + for g in self.geometries.hex_wkt: geom1 = OGRGeometry(g.wkt) wkb = geom1.wkb self.assertEqual(b2a_hex(wkb).upper(), g.hex) @@ -100,7 +100,7 @@ def test01e_json(self): "Testing GeoJSON input/output." from django.contrib.gis.gdal.prototypes.geom import GEOJSON if not GEOJSON: return - for g in json_geoms: + for g in self.geometries.json_geoms: geom = OGRGeometry(g.wkt) if not hasattr(g, 'not_equal'): self.assertEqual(g.json, geom.json) @@ -111,7 +111,7 @@ def test02_points(self): "Testing Point objects." prev = OGRGeometry('POINT(0 0)') - for p in points: + for p in self.geometries.points: if not hasattr(p, 'z'): # No 3D pnt = OGRGeometry(p.wkt) self.assertEqual(1, pnt.geom_type) @@ -122,8 +122,7 @@ def test02_points(self): def test03_multipoints(self): "Testing MultiPoint objects." - - for mp in multipoints: + for mp in self.geometries.multipoints: mgeom1 = OGRGeometry(mp.wkt) # First one from WKT self.assertEqual(4, mgeom1.geom_type) self.assertEqual('MULTIPOINT', mgeom1.geom_name) @@ -134,38 +133,38 @@ def test03_multipoints(self): mgeom3.add(g.wkt) # should take WKT as well self.assertEqual(mgeom1, mgeom2) # they should equal self.assertEqual(mgeom1, mgeom3) - self.assertEqual(mp.points, mgeom2.tuple) + self.assertEqual(mp.coords, mgeom2.coords) self.assertEqual(mp.n_p, mgeom2.point_count) def test04_linestring(self): "Testing LineString objects." prev = OGRGeometry('POINT(0 0)') - for ls in linestrings: + for ls in self.geometries.linestrings: linestr = OGRGeometry(ls.wkt) self.assertEqual(2, linestr.geom_type) self.assertEqual('LINESTRING', linestr.geom_name) self.assertEqual(ls.n_p, linestr.point_count) - self.assertEqual(ls.tup, linestr.tuple) + self.assertEqual(ls.coords, linestr.tuple) self.assertEqual(True, linestr == OGRGeometry(ls.wkt)) self.assertEqual(True, linestr != prev) self.assertRaises(OGRIndexError, linestr.__getitem__, len(linestr)) prev = linestr # Testing the x, y properties. - x = [tmpx for tmpx, tmpy in ls.tup] - y = [tmpy for tmpx, tmpy in ls.tup] + x = [tmpx for tmpx, tmpy in ls.coords] + y = [tmpy for tmpx, tmpy in ls.coords] self.assertEqual(x, linestr.x) self.assertEqual(y, linestr.y) def test05_multilinestring(self): "Testing MultiLineString objects." prev = OGRGeometry('POINT(0 0)') - for mls in multilinestrings: + for mls in self.geometries.multilinestrings: mlinestr = OGRGeometry(mls.wkt) self.assertEqual(5, mlinestr.geom_type) self.assertEqual('MULTILINESTRING', mlinestr.geom_name) self.assertEqual(mls.n_p, mlinestr.point_count) - self.assertEqual(mls.tup, mlinestr.tuple) + self.assertEqual(mls.coords, mlinestr.tuple) self.assertEqual(True, mlinestr == OGRGeometry(mls.wkt)) self.assertEqual(True, mlinestr != prev) prev = mlinestr @@ -177,7 +176,7 @@ def test05_multilinestring(self): def test06_linearring(self): "Testing LinearRing objects." prev = OGRGeometry('POINT(0 0)') - for rr in linearrings: + for rr in self.geometries.linearrings: lr = OGRGeometry(rr.wkt) #self.assertEqual(101, lr.geom_type.num) self.assertEqual('LINEARRING', lr.geom_name) @@ -195,7 +194,7 @@ def test07a_polygons(self): self.assertEqual(bbox, p.extent) prev = OGRGeometry('POINT(0 0)') - for p in polygons: + for p in self.geometries.polygons: poly = OGRGeometry(p.wkt) self.assertEqual(3, poly.geom_type) self.assertEqual('POLYGON', poly.geom_name) @@ -249,7 +248,7 @@ def test07b_closepolygons(self): def test08_multipolygons(self): "Testing MultiPolygon objects." prev = OGRGeometry('POINT(0 0)') - for mp in multipolygons: + for mp in self.geometries.multipolygons: mpoly = OGRGeometry(mp.wkt) self.assertEqual(6, mpoly.geom_type) self.assertEqual('MULTIPOLYGON', mpoly.geom_name) @@ -264,7 +263,7 @@ def test08_multipolygons(self): def test09a_srs(self): "Testing OGR Geometries with Spatial Reference objects." - for mp in multipolygons: + for mp in self.geometries.multipolygons: # Creating a geometry w/spatial reference sr = SpatialReference('WGS84') mpoly = OGRGeometry(mp.wkt, sr) @@ -282,8 +281,8 @@ def test09a_srs(self): self.assertEqual(sr.wkt, ring.srs.wkt) # Ensuring SRS propagate in topological ops. - a, b = topology_geoms[0] - a, b = OGRGeometry(a.wkt, sr), OGRGeometry(b.wkt, sr) + a = OGRGeometry(self.geometries.topology_geoms[0].wkt_a, sr) + b = OGRGeometry(self.geometries.topology_geoms[0].wkt_b, sr) diff = a.difference(b) union = a.union(b) self.assertEqual(sr.wkt, diff.srs.wkt) @@ -351,11 +350,10 @@ def test09c_transform_dim(self): def test10_difference(self): "Testing difference()." - for i in xrange(len(topology_geoms)): - g_tup = topology_geoms[i] - a = OGRGeometry(g_tup[0].wkt) - b = OGRGeometry(g_tup[1].wkt) - d1 = OGRGeometry(diff_geoms[i].wkt) + for i in xrange(len(self.geometries.topology_geoms)): + a = OGRGeometry(self.geometries.topology_geoms[i].wkt_a) + b = OGRGeometry(self.geometries.topology_geoms[i].wkt_b) + d1 = OGRGeometry(self.geometries.diff_geoms[i].wkt) d2 = a.difference(b) self.assertEqual(d1, d2) self.assertEqual(d1, a - b) # __sub__ is difference operator @@ -364,11 +362,10 @@ def test10_difference(self): def test11_intersection(self): "Testing intersects() and intersection()." - for i in xrange(len(topology_geoms)): - g_tup = topology_geoms[i] - a = OGRGeometry(g_tup[0].wkt) - b = OGRGeometry(g_tup[1].wkt) - i1 = OGRGeometry(intersect_geoms[i].wkt) + for i in xrange(len(self.geometries.topology_geoms)): + a = OGRGeometry(self.geometries.topology_geoms[i].wkt_a) + b = OGRGeometry(self.geometries.topology_geoms[i].wkt_b) + i1 = OGRGeometry(self.geometries.intersect_geoms[i].wkt) self.assertEqual(True, a.intersects(b)) i2 = a.intersection(b) self.assertEqual(i1, i2) @@ -378,11 +375,10 @@ def test11_intersection(self): def test12_symdifference(self): "Testing sym_difference()." - for i in xrange(len(topology_geoms)): - g_tup = topology_geoms[i] - a = OGRGeometry(g_tup[0].wkt) - b = OGRGeometry(g_tup[1].wkt) - d1 = OGRGeometry(sdiff_geoms[i].wkt) + for i in xrange(len(self.geometries.topology_geoms)): + a = OGRGeometry(self.geometries.topology_geoms[i].wkt_a) + b = OGRGeometry(self.geometries.topology_geoms[i].wkt_b) + d1 = OGRGeometry(self.geometries.sdiff_geoms[i].wkt) d2 = a.sym_difference(b) self.assertEqual(d1, d2) self.assertEqual(d1, a ^ b) # __xor__ is symmetric difference operator @@ -391,11 +387,10 @@ def test12_symdifference(self): def test13_union(self): "Testing union()." - for i in xrange(len(topology_geoms)): - g_tup = topology_geoms[i] - a = OGRGeometry(g_tup[0].wkt) - b = OGRGeometry(g_tup[1].wkt) - u1 = OGRGeometry(union_geoms[i].wkt) + for i in xrange(len(self.geometries.topology_geoms)): + a = OGRGeometry(self.geometries.topology_geoms[i].wkt_a) + b = OGRGeometry(self.geometries.topology_geoms[i].wkt_b) + u1 = OGRGeometry(self.geometries.union_geoms[i].wkt) u2 = a.union(b) self.assertEqual(u1, u2) self.assertEqual(u1, a | b) # __or__ is union operator @@ -411,7 +406,7 @@ def test14_add(self): # GeometryCollection.add may take an OGRGeometry (if another collection # of the same type all child geoms will be added individually) or WKT. - for mp in multipolygons: + for mp in self.geometries.multipolygons: mpoly = OGRGeometry(mp.wkt) mp1 = OGRGeometry('MultiPolygon') mp2 = OGRGeometry('MultiPolygon') @@ -429,7 +424,7 @@ def test15_extent(self): mp = OGRGeometry('MULTIPOINT(5 23, 0 0, 10 50)') self.assertEqual((0.0, 0.0, 10.0, 50.0), mp.extent) # Testing on the 'real world' Polygon. - poly = OGRGeometry(polygons[3].wkt) + poly = OGRGeometry(self.geometries.polygons[3].wkt) ring = poly.shell x, y = ring.x, ring.y xmin, ymin = min(x), min(y) diff --git a/django/contrib/gis/geometry/test_data.py b/django/contrib/gis/geometry/test_data.py new file mode 100644 index 000000000000..553db471f4a6 --- /dev/null +++ b/django/contrib/gis/geometry/test_data.py @@ -0,0 +1,100 @@ +""" +This module has the mock object definitions used to hold reference geometry +for the GEOS and GDAL tests. +""" +import gzip +import os + +from django.contrib import gis +from django.utils import simplejson + + +# This global used to store reference geometry data. +GEOMETRIES = None + +# Path where reference test data is located. +TEST_DATA = os.path.join(os.path.dirname(gis.__file__), 'tests', 'data') + + +def tuplize(seq): + "Turn all nested sequences to tuples in given sequence." + if isinstance(seq, (list, tuple)): + return tuple([tuplize(i) for i in seq]) + return seq + + +def get_ds_file(name, ext): + return os.path.join(TEST_DATA, + name, + name + '.%s' % ext + ) + + +class TestObj(object): + """ + Base testing object, turns keyword args into attributes. + """ + def __init__(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + + +class TestDS(TestObj): + """ + Object for testing GDAL data sources. + """ + def __init__(self, name, **kwargs): + # Shapefile is default extension, unless specified otherwise. + ext = kwargs.pop('ext', 'shp') + self.ds = get_ds_file(name, ext) + super(TestDS, self).__init__(**kwargs) + + +class TestGeom(TestObj): + """ + Testing object used for wrapping reference geometry data + in GEOS/GDAL tests. + """ + def __init__(self, **kwargs): + # Converting lists to tuples of certain keyword args + # so coordinate test cases will match (JSON has no + # concept of tuple). + coords = kwargs.pop('coords', None) + if coords: + self.coords = tuplize(coords) + + centroid = kwargs.pop('centroid', None) + if centroid: + self.centroid = tuple(centroid) + + ext_ring_cs = kwargs.pop('ext_ring_cs', None) + if ext_ring_cs: + ext_ring_cs = tuplize(ext_ring_cs) + self.ext_ring_cs = ext_ring_cs + + super(TestGeom, self).__init__(**kwargs) + + +class TestGeomSet(object): + """ + Each attribute of this object is a list of `TestGeom` instances. + """ + def __init__(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, [TestGeom(**kwargs) for kwargs in value]) + + +class TestDataMixin(object): + """ + Mixin used for GEOS/GDAL test cases that defines a `geometries` + property, which returns and/or loads the reference geometry data. + """ + @property + def geometries(self): + global GEOMETRIES + if GEOMETRIES is None: + # Load up the test geometry data from fixture into global. + gzf = gzip.GzipFile(os.path.join(TEST_DATA, 'geometries.json.gz')) + geometries = simplejson.loads(gzf.read()) + GEOMETRIES = TestGeomSet(**geometries) + return GEOMETRIES diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 4f2e33f9a034..3cd021e8b826 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -1,9 +1,9 @@ import ctypes, random, unittest, sys from django.contrib.gis.geos import * from django.contrib.gis.geos.base import gdal, numpy, GEOSBase -from django.contrib.gis.tests.geometries import * +from django.contrib.gis.geometry.test_data import TestDataMixin -class GEOSTest(unittest.TestCase): +class GEOSTest(unittest.TestCase, TestDataMixin): @property def null_srid(self): @@ -61,13 +61,13 @@ class FakeGeom2(GEOSBase): def test01a_wkt(self): "Testing WKT output." - for g in wkt_out: + for g in self.geometries.wkt_out: geom = fromstr(g.wkt) self.assertEqual(g.ewkt, geom.wkt) def test01b_hex(self): "Testing HEX output." - for g in hex_wkt: + for g in self.geometries.hex_wkt: geom = fromstr(g.wkt) self.assertEqual(g.hex, geom.hex) @@ -75,9 +75,16 @@ def test01b_hexewkb(self): "Testing (HEX)EWKB output." from binascii import a2b_hex + # For testing HEX(EWKB). + ogc_hex = '01010000000000000000000000000000000000F03F' + # `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));` + hexewkb_2d = '0101000020E61000000000000000000000000000000000F03F' + # `SELECT ST_AsHEXEWKB(ST_GeomFromEWKT('SRID=4326;POINT(0 1 2)'));` + hexewkb_3d = '01010000A0E61000000000000000000000000000000000F03F0000000000000040' + pnt_2d = Point(0, 1, srid=4326) pnt_3d = Point(0, 1, 2, srid=4326) - + # OGC-compliant HEX will not have SRID nor Z value. self.assertEqual(ogc_hex, pnt_2d.hex) self.assertEqual(ogc_hex, pnt_3d.hex) @@ -110,13 +117,13 @@ def test01b_hexewkb(self): pass else: self.fail('Should have raised GEOSException') - + # Redundant sanity check. self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid) def test01c_kml(self): "Testing KML output." - for tg in wkt_out: + for tg in self.geometries.wkt_out: geom = fromstr(tg.wkt) kml = getattr(tg, 'kml', False) if kml: self.assertEqual(kml, geom.kml) @@ -125,7 +132,7 @@ def test01d_errors(self): "Testing the Error handlers." # string-based print "\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n" - for err in errors: + for err in self.geometries.errors: try: g = fromstr(err.wkt) except (GEOSException, ValueError): @@ -147,14 +154,14 @@ class NotAGeometry(object): def test01e_wkb(self): "Testing WKB output." from binascii import b2a_hex - for g in hex_wkt: + for g in self.geometries.hex_wkt: geom = fromstr(g.wkt) wkb = geom.wkb self.assertEqual(b2a_hex(wkb).upper(), g.hex) def test01f_create_hex(self): "Testing creation from HEX." - for g in hex_wkt: + for g in self.geometries.hex_wkt: geom_h = GEOSGeometry(g.hex) # we need to do this so decimal places get normalised geom_t = fromstr(g.wkt) @@ -163,7 +170,7 @@ def test01f_create_hex(self): def test01g_create_wkb(self): "Testing creation from WKB." from binascii import a2b_hex - for g in hex_wkt: + for g in self.geometries.hex_wkt: wkb = buffer(a2b_hex(g.hex)) geom_h = GEOSGeometry(wkb) # we need to do this so decimal places get normalised @@ -173,7 +180,7 @@ def test01g_create_wkb(self): def test01h_ewkt(self): "Testing EWKT." srid = 32140 - for p in polygons: + for p in self.geometries.polygons: ewkt = 'SRID=%d;%s' % (srid, p.wkt) poly = fromstr(ewkt) self.assertEqual(srid, poly.srid) @@ -183,7 +190,7 @@ def test01h_ewkt(self): def test01i_json(self): "Testing GeoJSON input/output (via GDAL)." if not gdal or not gdal.GEOJSON: return - for g in json_geoms: + for g in self.geometries.json_geoms: geom = GEOSGeometry(g.wkt) if not hasattr(g, 'not_equal'): self.assertEqual(g.json, geom.json) @@ -225,7 +232,7 @@ def test01k_eq(self): def test02a_points(self): "Testing Point objects." prev = fromstr('POINT(0 0)') - for p in points: + for p in self.geometries.points: # Creating the point from the WKT pnt = fromstr(p.wkt) self.assertEqual(pnt.geom_type, 'Point') @@ -279,7 +286,7 @@ def test02a_points(self): def test02b_multipoints(self): "Testing MultiPoint objects." - for mp in multipoints: + for mp in self.geometries.multipoints: mpnt = fromstr(mp.wkt) self.assertEqual(mpnt.geom_type, 'MultiPoint') self.assertEqual(mpnt.geom_typeid, 4) @@ -289,7 +296,7 @@ def test02b_multipoints(self): self.assertRaises(GEOSIndexError, mpnt.__getitem__, len(mpnt)) self.assertEqual(mp.centroid, mpnt.centroid.tuple) - self.assertEqual(mp.points, tuple(m.tuple for m in mpnt)) + self.assertEqual(mp.coords, tuple(m.tuple for m in mpnt)) for p in mpnt: self.assertEqual(p.geom_type, 'Point') self.assertEqual(p.geom_typeid, 0) @@ -299,7 +306,7 @@ def test02b_multipoints(self): def test03a_linestring(self): "Testing LineString objects." prev = fromstr('POINT(0 0)') - for l in linestrings: + for l in self.geometries.linestrings: ls = fromstr(l.wkt) self.assertEqual(ls.geom_type, 'LineString') self.assertEqual(ls.geom_typeid, 1) @@ -325,7 +332,7 @@ def test03a_linestring(self): def test03b_multilinestring(self): "Testing MultiLineString objects." prev = fromstr('POINT(0 0)') - for l in multilinestrings: + for l in self.geometries.multilinestrings: ml = fromstr(l.wkt) self.assertEqual(ml.geom_type, 'MultiLineString') self.assertEqual(ml.geom_typeid, 5) @@ -348,7 +355,7 @@ def test03b_multilinestring(self): def test04_linearring(self): "Testing LinearRing objects." - for rr in linearrings: + for rr in self.geometries.linearrings: lr = fromstr(rr.wkt) self.assertEqual(lr.geom_type, 'LinearRing') self.assertEqual(lr.geom_typeid, 2) @@ -371,7 +378,7 @@ def test05a_polygons(self): self.assertEqual(bbox, p.extent) prev = fromstr('POINT(0 0)') - for p in polygons: + for p in self.geometries.polygons: # Creating the Polygon, testing its properties. poly = fromstr(p.wkt) self.assertEqual(poly.geom_type, 'Polygon') @@ -430,7 +437,7 @@ def test05b_multipolygons(self): "Testing MultiPolygon objects." print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n" prev = fromstr('POINT (0 0)') - for mp in multipolygons: + for mp in self.geometries.multipolygons: mpoly = fromstr(mp.wkt) self.assertEqual(mpoly.geom_type, 'MultiPolygon') self.assertEqual(mpoly.geom_typeid, 6) @@ -456,7 +463,7 @@ def test06a_memory_hijinks(self): # These tests are needed to ensure sanity with writable geometries. # Getting a polygon with interior rings, and pulling out the interior rings - poly = fromstr(polygons[1].wkt) + poly = fromstr(self.geometries.polygons[1].wkt) ring1 = poly[0] ring2 = poly[1] @@ -472,12 +479,9 @@ def test06a_memory_hijinks(self): # Access to these rings is OK since they are clones. s1, s2 = str(ring1), str(ring2) - # The previous hijinks tests are now moot because only clones are - # now used =) - def test08_coord_seq(self): "Testing Coordinate Sequence objects." - for p in polygons: + for p in self.geometries.polygons: if p.ext_ring_cs: # Constructing the polygon and getting the coordinate sequence poly = fromstr(p.wkt) @@ -506,22 +510,18 @@ def test09_relate_pattern(self): "Testing relate() and relate_pattern()." g = fromstr('POINT (0 0)') self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo') - for i in xrange(len(relate_geoms)): - g_tup = relate_geoms[i] - a = fromstr(g_tup[0].wkt) - b = fromstr(g_tup[1].wkt) - pat = g_tup[2] - result = g_tup[3] - self.assertEqual(result, a.relate_pattern(b, pat)) - self.assertEqual(pat, a.relate(b)) + for rg in self.geometries.relate_geoms: + a = fromstr(rg.wkt_a) + b = fromstr(rg.wkt_b) + self.assertEqual(rg.result, a.relate_pattern(b, rg.pattern)) + self.assertEqual(rg.pattern, a.relate(b)) def test10_intersection(self): "Testing intersects() and intersection()." - for i in xrange(len(topology_geoms)): - g_tup = topology_geoms[i] - a = fromstr(g_tup[0].wkt) - b = fromstr(g_tup[1].wkt) - i1 = fromstr(intersect_geoms[i].wkt) + for i in xrange(len(self.geometries.topology_geoms)): + a = fromstr(self.geometries.topology_geoms[i].wkt_a) + b = fromstr(self.geometries.topology_geoms[i].wkt_b) + i1 = fromstr(self.geometries.intersect_geoms[i].wkt) self.assertEqual(True, a.intersects(b)) i2 = a.intersection(b) self.assertEqual(i1, i2) @@ -531,11 +531,10 @@ def test10_intersection(self): def test11_union(self): "Testing union()." - for i in xrange(len(topology_geoms)): - g_tup = topology_geoms[i] - a = fromstr(g_tup[0].wkt) - b = fromstr(g_tup[1].wkt) - u1 = fromstr(union_geoms[i].wkt) + for i in xrange(len(self.geometries.topology_geoms)): + a = fromstr(self.geometries.topology_geoms[i].wkt_a) + b = fromstr(self.geometries.topology_geoms[i].wkt_b) + u1 = fromstr(self.geometries.union_geoms[i].wkt) u2 = a.union(b) self.assertEqual(u1, u2) self.assertEqual(u1, a | b) # __or__ is union operator @@ -544,11 +543,10 @@ def test11_union(self): def test12_difference(self): "Testing difference()." - for i in xrange(len(topology_geoms)): - g_tup = topology_geoms[i] - a = fromstr(g_tup[0].wkt) - b = fromstr(g_tup[1].wkt) - d1 = fromstr(diff_geoms[i].wkt) + for i in xrange(len(self.geometries.topology_geoms)): + a = fromstr(self.geometries.topology_geoms[i].wkt_a) + b = fromstr(self.geometries.topology_geoms[i].wkt_b) + d1 = fromstr(self.geometries.diff_geoms[i].wkt) d2 = a.difference(b) self.assertEqual(d1, d2) self.assertEqual(d1, a - b) # __sub__ is difference operator @@ -557,11 +555,10 @@ def test12_difference(self): def test13_symdifference(self): "Testing sym_difference()." - for i in xrange(len(topology_geoms)): - g_tup = topology_geoms[i] - a = fromstr(g_tup[0].wkt) - b = fromstr(g_tup[1].wkt) - d1 = fromstr(sdiff_geoms[i].wkt) + for i in xrange(len(self.geometries.topology_geoms)): + a = fromstr(self.geometries.topology_geoms[i].wkt_a) + b = fromstr(self.geometries.topology_geoms[i].wkt_b) + d1 = fromstr(self.geometries.sdiff_geoms[i].wkt) d2 = a.sym_difference(b) self.assertEqual(d1, d2) self.assertEqual(d1, a ^ b) # __xor__ is symmetric difference operator @@ -570,18 +567,19 @@ def test13_symdifference(self): def test14_buffer(self): "Testing buffer()." - for i in xrange(len(buffer_geoms)): - g_tup = buffer_geoms[i] - g = fromstr(g_tup[0].wkt) + for bg in self.geometries.buffer_geoms: + g = fromstr(bg.wkt) # The buffer we expect - exp_buf = fromstr(g_tup[1].wkt) + exp_buf = fromstr(bg.buffer_wkt) + quadsegs = bg.quadsegs + width = bg.width # Can't use a floating-point for the number of quadsegs. - self.assertRaises(ctypes.ArgumentError, g.buffer, g_tup[2], float(g_tup[3])) + self.assertRaises(ctypes.ArgumentError, g.buffer, width, float(quadsegs)) # Constructing our buffer - buf = g.buffer(g_tup[2], g_tup[3]) + buf = g.buffer(width, quadsegs) self.assertEqual(exp_buf.num_coords, buf.num_coords) self.assertEqual(len(exp_buf), len(buf)) @@ -605,7 +603,7 @@ def test15_srid(self): self.assertRaises(ctypes.ArgumentError, pnt.set_srid, '4326') # Testing SRID keyword on fromstr(), and on Polygon rings. - poly = fromstr(polygons[1].wkt, srid=4269) + poly = fromstr(self.geometries.polygons[1].wkt, srid=4269) self.assertEqual(4269, poly.srid) for ring in poly: self.assertEqual(4269, ring.srid) poly.srid = 4326 @@ -636,7 +634,7 @@ def test15_srid(self): def test16_mutable_geometries(self): "Testing the mutability of Polygons and Geometry Collections." ### Testing the mutability of Polygons ### - for p in polygons: + for p in self.geometries.polygons: poly = fromstr(p.wkt) # Should only be able to use __setitem__ with LinearRing geometries. @@ -655,7 +653,7 @@ def test16_mutable_geometries(self): self.assertEqual(poly[0], new_shell) ### Testing the mutability of Geometry Collections - for tg in multipoints: + for tg in self.geometries.multipoints: mp = fromstr(tg.wkt) for i in range(len(mp)): # Creating a random point. @@ -670,7 +668,7 @@ def test16_mutable_geometries(self): # MultiPolygons involve much more memory management because each # Polygon w/in the collection has its own rings. - for tg in multipolygons: + for tg in self.geometries.multipolygons: mpoly = fromstr(tg.wkt) for i in xrange(len(mpoly)): poly = mpoly[i] @@ -791,10 +789,10 @@ def test20b_collections_of_collections(self): "Testing GeometryCollection handling of other collections." # Creating a GeometryCollection WKT string composed of other # collections and polygons. - coll = [mp.wkt for mp in multipolygons if mp.valid] - coll.extend([mls.wkt for mls in multilinestrings]) - coll.extend([p.wkt for p in polygons]) - coll.extend([mp.wkt for mp in multipoints]) + coll = [mp.wkt for mp in self.geometries.multipolygons if mp.valid] + coll.extend([mls.wkt for mls in self.geometries.multilinestrings]) + coll.extend([p.wkt for p in self.geometries.polygons]) + coll.extend([mp.wkt for mp in self.geometries.multipoints]) gc_wkt = 'GEOMETRYCOLLECTION(%s)' % ','.join(coll) # Should construct ok from WKT @@ -862,7 +860,7 @@ def test24_extent(self): # Extent of points is just the point itself repeated. self.assertEqual((5.23, 17.8, 5.23, 17.8), pnt.extent) # Testing on the 'real world' Polygon. - poly = fromstr(polygons[3].wkt) + poly = fromstr(self.geometries.polygons[3].wkt) ring = poly.shell x, y = ring.x, ring.y xmin, ymin = min(x), min(y) @@ -878,10 +876,10 @@ def test25_pickle(self): # and setting the SRID on some of them. def get_geoms(lst, srid=None): return [GEOSGeometry(tg.wkt, srid) for tg in lst] - tgeoms = get_geoms(points) - tgeoms.extend(get_geoms(multilinestrings, 4326)) - tgeoms.extend(get_geoms(polygons, 3084)) - tgeoms.extend(get_geoms(multipolygons, 900913)) + tgeoms = get_geoms(self.geometries.points) + tgeoms.extend(get_geoms(self.geometries.multilinestrings, 4326)) + tgeoms.extend(get_geoms(self.geometries.polygons, 3084)) + tgeoms.extend(get_geoms(self.geometries.multipolygons, 900913)) # The SRID won't be exported in GEOS 3.0 release candidates. no_srid = self.null_srid == -1 diff --git a/django/contrib/gis/tests/data/geometries.json.gz b/django/contrib/gis/tests/data/geometries.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..683dc83e4d7b8d3805c9836026299ff1c1113d29 GIT binary patch literal 9100 zcmV;7BXisziwFStD$h&+1MOXDj~vI5{qA4UJH!G*pr@JlVF-dHOB4j?Fl6lqLnMfi zG?KZdINafIZLOjIzVAg|)m7C!J-Q6TZi&;~Rgswyk#T2M_TPWA*_^$)e)0Xc*Dqh6 z{dV&OzHc^vKl|%nZt?Bx51)SYrw=~;WOIJ5Hfp<38?Eq$KjVX3Ru8wEbIq%~xXF0S z{jFZq>fyt)?dI&|v*+kqkMr~~H5;(n3+&L)?lvf8+qvx|CT@-8YLl+m~nL-HDu zS0KI43jgrGPA(Qm7ko-O2d|V#12S75atJnNrE8zYD{s9+*WN4BAcKm=f`Z_px4A;9 z9IOpME7{!pjN0oMax})MB#FPZ z>Im*7Um-CW;Ct{Ea}^pSFb6sw_+XA*nIokj0zp}oy&7C1Dwt50Av`fO$N-Fh4yYQO z$BZTCvvW~p)hGg{IV4AMq~te9ZM9b}K_YX9R1`(+fm-mBKEX$4a?C;L#!u~y*3kxt z^ni>BtON2ofw7Hth*AdSF+`s`BxJ*dK>32Sv_1{yIGX5En)GQgkasQt)dES{9ol2+ zy$-7L(3Bk&`ig0s};0;vt;&OoU-e(I14s8Wrs z3$bdkhMs$kISyc}?T`k-5Glo!$2vOZ)e~C_4m3%Q)NTkI)X+J)jzSS^5(a|Tf`m6n zkO|FzcERrC1{ouC2=)V9HhMr}A{-1(2J<@Y5OxO^g=|x&D0su^1olABG;8BAanTy5 zQgj^>5(`lXpbT1GA-%Q^LxWC?*CBCP;i?G+3Q{4VXwV`Rox|{hcGwlDjqytd~pRDAXf&3kEsnV6e%Nw<;tIQ=;>52KKT;f{!r15C_m%Z3xU3v@(-RyY6ky5d4n zf;q%LN*=6t_Z=7jE(Y_PY_m2gP=9DNG-kyPNJu95Vi_j*3JHyaY0y^0VnD)qz=S2J zc$GRpLJR}2g1H?QO!0rXK1hwOvXmfx9{s_s1UIqC^h0ookfu=mJM5f=WCzF$hU*kT z1E92+y@a5l;>lP6&K25?Ng7@zLWH0WP*7ED9Sj*v1KcyTFq`4E4IBv!AmkBL6^bI~ z7jX$h6o#mKSQuXVP1sK=Qh^Mf_Dew%Xmf|}aI|Wm6`Ef(E}}6a0k3$;j6Nap3T?Z>2H0~Xya+p=*ggSp~VcqT^TZ;P9ayTh7j8>=U5d;A$lcAZ68hf9>6d;-ir;_yuq}B}HAmn*KBBX|fX9InB~Pk-kE~gEc(Go5BV$+|(Ts{samPf55P__X(MU zZU(ADheXUkaR-@*{~Mk#RM0^-Xc>wFi(%R5R~X@hPCMd8O9Kqw&>;~|VsYC{VTz0=V2E{{P0d(?c4)+PjtLK47Q_M0 zvqFNi3`=Q&wd$iMi8GBPGp6bhgb*Bp#eqvRVc-cPg4s>HUu6m687NUuC5Uu`WLyg3 zIsK+u??4HI5I}C>dh+NK>=aK*`L2)AI>UwihQCL zf`yS!Nze587-9b35iko&|vFxqxS+6a!WC-hBrr0(55buI30383-I= zYRd?#(azL})dW+b@D?*z3Lc0D3v`DB984cr;8KlLBmF4!1yT%7cSzQd91945tl0z_ zgMJob0`r;%=`F2Su&jc(8iQFDeH`DR+MRYM3dHCR|JUdOSt^qYv01wJ$uSeW94ivl z) zqe@MdkaEEyB5|W~*wH6LE}(`MHJ)dP&e9Sr0H!o{NCZ(jf>Sy)OAAQk5x_OD7N-Ve zWFEp`K35BdWCKHkLYN8_(ge~_A|t~{)gY0DL2cmZ!P&{^@KcFBgc=256bWF+HfIJ! z5MXr5#0$3?Bv=g8A>VQE(~0RtYlOQsUIL_ILc=J^_DBGbkRqByMa`{{S>jwG&ZFthF^jA}oLnht}!=i5D~s*AU{={3uEW3d6$iQHKtR03E8K;U!J2E3$fJ zSlNRaNZm4E3Yl6d)?0Jy6D)^t(IUeAQ@AM8eKutbSPkYg z-gL;sa0@=ft3i=v(v#`@b+vVp-$d3EJad8usp;>f`~X{-8YFxKeF|c!)G1Q*Cg3si zC|@CwcSGX5glyeqHBJU3gb0ZS5@9HB1vLMNy0xT^j3KBSV3VJIC2?d0b*KhcP`3 zPPaCQ_2{*raWFZt)h-}2TrQw5v?J^BV!q(OFi@4H#5x#j?%=z1gCcF148YBsn)NWu z03Q)lS^AnzQD*!|_X(@f`b;bi5r4!jq4&w^GV7R}_Sz>x8pq0k}ajW*)Xg4Sq(Df|<)qX;OoX z5*b3|64Ymm4$c|H6_(g^lNJ&p$jre^WJyU?m4z3CDHW2L9Q2Es+<=5qGi6}bm|Cq8 zT$F4VksY|&A(1=dyFobB>{4dQIEo+jeQkmzHB7vrNHGwpkgyTRjgbFNB5HU8v2)-D zgso}^EVNi|Owh1(^c&3RDLgj!iW$8ND^W0NVFJktRibsRQ6&osPnR17u7CXi0B8N)5B&iCih($*o zPlytoJo07ec!OkE0@sn~-$$K+g`LQZ99%3Ean|XiH2vbB-81B%zy|~rt@>)XBxOmI zGkuOcBWJiCE$L5%l!`|HUm&>MGB93(zB*ZJth5`Z;Iu99AVWKlNT6G=k1qa3h#ev+@X3VAu@T11s&aILL5>Hn&?v2%zD*BaEeK zHGkGJJQaa;f`KPA5^VMobhSA`SmhOrB*kIqQD&^dVhG4>vc$j!$uGE>!q6j_eF?U? zTIBoaj2Ilm$@Q#9!y7uLz;K(0UmNXQ%tYwG)sm)1u!Jy(mRSOXsX{W*i_B%XJlXgO z>1Fz!Sz?Y&K4}4&XH)BYY@;OmWPOe+dzB)}G-kxGv>0@UWSx+USF}xa-njrGB?Oy? zHw0u72QLXxop;ZGO0iUlDNK_-VUgfgxJ264Ww;`qxP(SoZjcaa=3&f!bgkE6E+CydpyfxP*K_5F^4x3GgrxmoVjSAa zNM;Eixd&I&^q?IPX`+78d&SU4&#b^j#?GNbA_Ie=O;S2bjq{GlSmx?%uK{uuQdU#5 zl+GIGGZ*!76R}92P51-m_1q|+Q{yLD0pQ3*oVIQ#$rhN(m0Sp0=RC4fmh90>uaI*A z1lHTJNZkA;i!70aV`(rT`3-3hY_4we&dOe4+FEU*7grV$^bWRuwksY%HLb8zW zxe#GkFmPkVfr}`NewiV+m1SrJA}-5CzPt)hp!e(=nEZ(V+MhNJKA+o*7Ao z>T7&t4wI8CrHVS62uvVZ=oNkTRUt-+^!Wi!H#S+4f}GSZVJc0BXazBh$4bmA22WT&c9zl5=Cr=`$ z&QDAaA|=eR9g^(W7wioeQuJ6=r*kY7jm{+)N$y4Qj%4L7X=9i(w>r<3ziw2oYE>Udh3S}quwu>zhLrbU*RRkC?NQWars z;lFG36&ILrpo7E}i7~p8T?e$;EoMj(_G?jRttNdsCUV?t)ppS^_NH&AYWp!d#FD90 zr1&*il7a+n1IBMFwopm5 zA?rFcTU|mR1&X3aU)mr&w|B7cV!0&SjE^a zL*Bhh_V`xcZ!TZFzUupT4L60g9lUC%xUG(JuH^SgI-#z>G z>iWC0-@3+bqvm^{=N!NYvLawvk~4KoqSkOI00qO7fkhx1dLjUK;X%UK6LN)(Y)(Qy za-rNPmLn-~arx2M_+^cMtya!EW-p*zJvMNT3g;}eTjxDZ{_a8nFvqDSYi0eMHtOhfnNm$rb|$>3VABv$%x8x zR&fn@wO_j`aqGB-70^}swK^H{R7@Yv%!mXtZ@&fOb#?RZ^_!PpU*3HGA>@mqb$RpI zLKk#V0ACcq>FGC_uC1wGCZG2jt?5Y{>Cpm@=PzHLh**>{>WJ1(zX`{;6-!}i|1xcL z6te{)j~erqR<$yfUls&4y=@&YjIS?B>2!H#SYevN)|8s`P6`LZTMr#cqr(rTB53><6DdR()e;D z=sVnVoOUL|Bz1|ziTFEyXt`tfu4+QDVyB`-*uLqJPp4l zYuEU-nscvC!aC&Xj6EJ4-8&^ImRc7Z8cW_WO+5OyDc~=ahU_6z+(&U3HBKDgPR8bN z>hY7g)fhBo-9L&~GP0X8&i#y>IxV7|WNx+4zaJ0&q|8niiylQHODRTGv#)>koUS6n z;8M!|roP`3o8#GB3xXG%{Kc4CxLSDpk7lirxV?t&zXD^Y`Ne^3x-KF7>Gz+0^wImj z|NKKn5R)1;GW&6%S6|&eZ_4p)y?$b<3VKdSRo8l5yLMO3P(=Okn2R`lKFFg3 zw4aUjmOo$p?OBn-we^;Bpdd3})@sg_R(dWP%hzLNA0JuNV5i%S*;wSq8$avb)IRKK z!|Y#i`(M-kcRjn$z1)Y2{q@)^-Tvx&hSucBtbw!b1C*D)8~6FkICro6_k~}39VrkV zrGB6IHclh@BndeTxr+Enkn;Vc(NBlu>#o|;AyGjtTF;ZSWoyDd-x|M~zz@;Cd*;?D zy2t&K;0H!#31MeFEWW?LynnPlaDIo%ZO_lg@qD&$)i&nVqx;Bmzu|#bd<$G4>TmU; ze>k6rFeCDxR-Y%BH(FLOzjNa5n#lWpKV`f%JhQvs_ji8c_Y=lDAqYJG2g&sI$6BTB ziB^B6HL{{A(BrJh;*_v`DIvO90}5Mr7eIfPd13SoKu@0%xQ zx8J|IdV()c&LpRLg8NU-Cdtne*)KY(soS2|Zk}#_xFevy(EaVAShkBj19u1!DFPfo zh5xM~v&HobeafBjx!gPD&V=ZvtAx)Sk>;}swClFLS-zR}X8LZ}os`Y ze#F0WlCs(vd@<#E+W(l!N3;LYJUqtrY5nC0>nRJSv&HPO3uK;f$IkAU6YiKj_HR2T zho)ONc;_8GyQ5CHGZlnw$HMfxX4Wp9a3{^~m=o@pj>Q3iTnUz@c3@I1JKqyMQ{?TK zs4Qz7MPO<{?X1z8w2Q&_ zZsl6-R_@HYt@q{DA#5e3=y*0(>p7!!mC?hT=A>=X;c5FnLGs81&YvwQBJ#z-3Rg{V zBpKS&2V-8<2MI*t%klRU%pth4uqF%4a@74EiGmCNE=W8u>hxy^Di;e1?nD%}jj?9$ zp1upc&MNNfcrDt#qD|BFg#LU>f3&7N=rTciF7&!0;|gw5ffXef!0R86zh+g`EyOe#!9k>7+f6^>Fg&f6m%z9toJOn_)XOaKc1CW&Pfl z!ufsvUfvwx?>nDW{yH91x3hexE^0oFs(7f~#_?QcZRAY!EJw8S3=KYq8+gu++3}P# zJ}=ATCDF|F7LkWh^Vrg$<&nqQBJ4bm=$EH9c^tUTwT0vJzs57K$6pgT+iU@n&|Ycl zz&gbf31V##c9O^N1c?O1d}dr* zEyM@@EI?_IdFGy+Tf}1ocrIFLL5XqRq^J{E!sqd9X<_t^C%||<%?TE2c}`zi!1eef zkJu_69h-B-3O9Hh3m?7aaiCyttS#KmMC&u3Rh8rIY^5AGDiMw+7x8B$YKyQd&js=f zwZt=gd~M;;f(I)aIWU=z>ed!<$8(|-pBPPs2S7Hg!Y;)ad9Jb?9-nfpf364SfEmw6JQYc?^*JvIEZt4~oqXucY6Lb8uNzFh?<*Gh z!jF^RIO)H9vNTIe<9kP-eJu5MPDmoqKDh)^doKXdgU`8O&9brE&iP>T5|6lDFv2WI zvSzzr#ez95Z0GIZo9De7vz_B$@?yqGzQ-gT7Oj2Q%o<~w=7>1G znRCo6XyOq{Q<5To6lu;2Z#J_l#x58Qy65~_&RIyCIlIDQ`h(sqxV2qyB}-1SV8gsA zxH{*8o8#cNa~`BQEzp%Y3*ww9F3lB_2hJ`8W+^XoCM^ube73#W`cfh7cIU6pzrOu* zPt=g6fBpJOoOY@pKWvxw>$I&EciOfKgQoa{Hk@!EEGHe%3}7x1vGPz6e&dl~2&o9g zQ)PMTDt`q7EF)CpX%ivwv|$7+nNU2_n#ZWPpm-w6M(Z7)q~;M^JP_C@5izHEj;fq( z&tI1!)c&PFs98fo?RRe|WHTfPwU4SJ)ILFhP&2Y++vir-zo#@@|)o1!s-93n5&ar=>iv z&G}L{C?6MwZjqivEhWpZHCjk%Tq58X62GLxE_G+l6unTw6c+5&da2OMdCCjh;PYb6 zL!up7w`}I1W$O{!&DD#`+pG16?b+q5^pj*S?f8w!^5`WCB=yDq*%3>x@x2g%4NK=_h1dFT2PeqWPRAjKYrNu zoX*<7lghU#kI=~eRoNuDF>3E8JSW!+U~O@bQF6+~{V-mX%P*Hd}Z zZYFYj6F%*7(wA24LrLi5R0Ae({1C>trvE;v*&k(ZhBRbDtHbnm8Na=Lb^YS{+wYG_ z`NTE*wjd#&dW~%IY1~!218kW5cqvVlui07&HuV0dahIRa-%%NB{Tc~AQvJ(PiK&0@ zL`lo`Rjp;=)UvjKfGO~ln@y6x^4qbVj+pm(tT%w=TM2Wre2H?WfH}GD*j~Dd>@}B_ z*^Qpi)Mw@E=ihvDEK?lw!BEMw|N6)C>By}rmr3lwK&`Q zp#=#+M-VGG`U&oNN>n4U5?slz%qW~GTq+#XtHix|`Fye6H2xdjJ4KKwRPg literal 0 HcmV?d00001 diff --git a/django/contrib/gis/tests/geometries.py b/django/contrib/gis/tests/geometries.py deleted file mode 100644 index 6689cd710323..000000000000 --- a/django/contrib/gis/tests/geometries.py +++ /dev/null @@ -1,180 +0,0 @@ -import re - -wkt_regex = re.compile(r'^(?P[A-Z]+) ?\(') - -class TestGeom: - "The Test Geometry class container." - def __init__(self, wkt, **kwargs): - self.wkt = wkt - - self.bad = kwargs.pop('bad', False) - - if not self.bad: - m = wkt_regex.match(wkt) - if not m: - raise Exception('Improper WKT: "%s"' % wkt) - self.geo_type = m.group('type') - - for key, value in kwargs.items(): - setattr(self, key, value) - -# For the old tests -swig_geoms = (TestGeom('POLYGON ((0 0, 0 100, 100 100, 100 0, 0 0))', ncoords=5), - TestGeom('POLYGON ((0 0, 0 100, 100 100, 100 0, 0 0), (10 10, 10 90, 90 90, 90 10, 10 10) ))', ncoords=10), - ) - -# Testing WKT & HEX -hex_wkt = (TestGeom('POINT(0 1)', hex='01010000000000000000000000000000000000F03F'), - TestGeom('LINESTRING(0 1, 2 3, 4 5)', hex='0102000000030000000000000000000000000000000000F03F0000000000000040000000000000084000000000000010400000000000001440'), - TestGeom('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))', hex='010300000001000000050000000000000000000000000000000000000000000000000024400000000000000000000000000000244000000000000024400000000000000000000000000000244000000000000000000000000000000000'), - TestGeom('MULTIPOINT(0 0, 10 0, 10 10, 0 10, 0 0)', hex='010400000005000000010100000000000000000000000000000000000000010100000000000000000024400000000000000000010100000000000000000024400000000000002440010100000000000000000000000000000000002440010100000000000000000000000000000000000000'), - TestGeom('MULTILINESTRING((0 0, 10 0, 10 10, 0 10),(20 20, 30 20))', hex='01050000000200000001020000000400000000000000000000000000000000000000000000000000244000000000000000000000000000002440000000000000244000000000000000000000000000002440010200000002000000000000000000344000000000000034400000000000003E400000000000003440'), - TestGeom('MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)),((20 20, 20 30, 30 30, 30 20, 20 20),(25 25, 25 26, 26 26, 26 25, 25 25)))', hex='010600000002000000010300000001000000050000000000000000000000000000000000000000000000000024400000000000000000000000000000244000000000000024400000000000000000000000000000244000000000000000000000000000000000010300000002000000050000000000000000003440000000000000344000000000000034400000000000003E400000000000003E400000000000003E400000000000003E40000000000000344000000000000034400000000000003440050000000000000000003940000000000000394000000000000039400000000000003A400000000000003A400000000000003A400000000000003A40000000000000394000000000000039400000000000003940'), - TestGeom('GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)),((20 20, 20 30, 30 30, 30 20, 20 20),(25 25, 25 26, 26 26, 26 25, 25 25))),MULTILINESTRING((0 0, 10 0, 10 10, 0 10),(20 20, 30 20)),MULTIPOINT(0 0, 10 0, 10 10, 0 10, 0 0))', hex='010700000003000000010600000002000000010300000001000000050000000000000000000000000000000000000000000000000024400000000000000000000000000000244000000000000024400000000000000000000000000000244000000000000000000000000000000000010300000002000000050000000000000000003440000000000000344000000000000034400000000000003E400000000000003E400000000000003E400000000000003E40000000000000344000000000000034400000000000003440050000000000000000003940000000000000394000000000000039400000000000003A400000000000003A400000000000003A400000000000003A4000000000000039400000000000003940000000000000394001050000000200000001020000000400000000000000000000000000000000000000000000000000244000000000000000000000000000002440000000000000244000000000000000000000000000002440010200000002000000000000000000344000000000000034400000000000003E400000000000003440010400000005000000010100000000000000000000000000000000000000010100000000000000000024400000000000000000010100000000000000000024400000000000002440010100000000000000000000000000000000002440010100000000000000000000000000000000000000'), - ) - -# WKT, GML, KML output -wkt_out = (TestGeom('POINT (110 130)', ewkt='POINT (110.0000000000000000 130.0000000000000000)', kml='110.0,130.0,0', gml='110,130'), - TestGeom('LINESTRING (40 40,50 130,130 130)', ewkt='LINESTRING (40.0000000000000000 40.0000000000000000, 50.0000000000000000 130.0000000000000000, 130.0000000000000000 130.0000000000000000)', kml='40.0,40.0,0 50.0,130.0,0 130.0,130.0,0', gml='40,40 50,130 130,130'), - TestGeom('POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', ewkt='POLYGON ((150.0000000000000000 150.0000000000000000, 410.0000000000000000 150.0000000000000000, 280.0000000000000000 20.0000000000000000, 20.0000000000000000 20.0000000000000000, 150.0000000000000000 150.0000000000000000), (170.0000000000000000 120.0000000000000000, 330.0000000000000000 120.0000000000000000, 260.0000000000000000 50.0000000000000000, 100.0000000000000000 50.0000000000000000, 170.0000000000000000 120.0000000000000000))', kml='150.0,150.0,0 410.0,150.0,0 280.0,20.0,0 20.0,20.0,0 150.0,150.0,0170.0,120.0,0 330.0,120.0,0 260.0,50.0,0 100.0,50.0,0 170.0,120.0,0', gml='150,150 410,150 280,20 20,20 150,150170,120 330,120 260,50 100,50 170,120'), - TestGeom('MULTIPOINT (10 80,110 170,110 120)', ewkt='MULTIPOINT (10.0000000000000000 80.0000000000000000, 110.0000000000000000 170.0000000000000000, 110.0000000000000000 120.0000000000000000)', kml='10.0,80.0,0110.0,170.0,0110.0,120.0,0', gml='10,80110,170110,120'), - TestGeom('MULTILINESTRING ((110 100,40 30,180 30),(170 30,110 90,50 30))', ewkt='MULTILINESTRING ((110.0000000000000000 100.0000000000000000, 40.0000000000000000 30.0000000000000000, 180.0000000000000000 30.0000000000000000), (170.0000000000000000 30.0000000000000000, 110.0000000000000000 90.0000000000000000, 50.0000000000000000 30.0000000000000000))', kml='110.0,100.0,0 40.0,30.0,0 180.0,30.0,0170.0,30.0,0 110.0,90.0,0 50.0,30.0,0', gml='110,100 40,30 180,30170,30 110,90 50,30'), - TestGeom('MULTIPOLYGON (((110 110,70 200,150 200,110 110),(110 110,100 180,120 180,110 110)),((110 110,150 20,70 20,110 110),(110 110,120 40,100 40,110 110)))', ewkt='MULTIPOLYGON (((110.0000000000000000 110.0000000000000000, 70.0000000000000000 200.0000000000000000, 150.0000000000000000 200.0000000000000000, 110.0000000000000000 110.0000000000000000), (110.0000000000000000 110.0000000000000000, 100.0000000000000000 180.0000000000000000, 120.0000000000000000 180.0000000000000000, 110.0000000000000000 110.0000000000000000)), ((110.0000000000000000 110.0000000000000000, 150.0000000000000000 20.0000000000000000, 70.0000000000000000 20.0000000000000000, 110.0000000000000000 110.0000000000000000), (110.0000000000000000 110.0000000000000000, 120.0000000000000000 40.0000000000000000, 100.0000000000000000 40.0000000000000000, 110.0000000000000000 110.0000000000000000)))', kml='110.0,110.0,0 70.0,200.0,0 150.0,200.0,0 110.0,110.0,0110.0,110.0,0 100.0,180.0,0 120.0,180.0,0 110.0,110.0,0110.0,110.0,0 150.0,20.0,0 70.0,20.0,0 110.0,110.0,0110.0,110.0,0 120.0,40.0,0 100.0,40.0,0 110.0,110.0,0', gml='110,110 70,200 150,200 110,110110,110 100,180 120,180 110,110110,110 150,20 70,20 110,110110,110 120,40 100,40 110,110'), - TestGeom('GEOMETRYCOLLECTION (POINT (110 260),LINESTRING (110 0,110 60))', ewkt='GEOMETRYCOLLECTION (POINT (110.0000000000000000 260.0000000000000000), LINESTRING (110.0000000000000000 0.0000000000000000, 110.0000000000000000 60.0000000000000000))', kml='110.0,260.0,0110.0,0.0,0 110.0,60.0,0', gml='110,260110,0 110,60'), - ) - -# Errors -errors = (TestGeom('GEOMETR##!@#%#............a32515', bad=True, hex=False), - TestGeom('Foo.Bar', bad=True, hex=False), - TestGeom('POINT (5, 23)', bad=True, hex=False), - TestGeom('AAABBBDDDAAD##@#1113511111-098111111111111111533333333333333', bad=True, hex=True), - TestGeom('FFFFFFFFFFFFFFFFF1355555555555555555565111', bad=True, hex=True), - TestGeom('', bad=True, hex=False), - ) - -# Polygons -polygons = (TestGeom('POLYGON ((0 0, 0 100, 100 100, 100 0, 0 0), (10 10, 10 90, 90 90, 90 10, 10 10))', - n_i=1, ext_ring_cs=((0, 0), (0, 100), (100, 100), (100, 0), (0, 0)), n_p=10, area=3600.0, centroid=(50., 50.), - ), - TestGeom('POLYGON ((0 0, 0 100, 100 100, 100 0, 0 0), (10 10, 10 20, 20 20, 20 10, 10 10), (80 80, 80 90, 90 90, 90 80, 80 80))', - n_i=2, ext_ring_cs=((0, 0), (0, 100), (100, 100), (100, 0), (0, 0)), n_p=15, area=9800.0, centroid=(50., 50.), - ), - TestGeom('POLYGON ((0 0, 0 100, 100 100, 100 0, 0 0))', - n_i=0, ext_ring_cs=((0, 0), (0, 100), (100, 100), (100, 0), (0, 0)), n_p=5, area=10000.0, centroid=(50., 50.), - ), - TestGeom('POLYGON ((-95.3848703124799471 29.7056021479768511, -95.3851905195191847 29.7046588196500281, -95.3859356966379011 29.7025053545605502, -95.3860723000647539 29.7020963367038391, -95.3871517697222089 29.6989779021280995, -95.3865578518265522 29.6990856888057202, -95.3862634205175226 29.6999471753441782, -95.3861991779541967 29.6999591988978615, -95.3856773799358137 29.6998323107113578, -95.3856209915427229 29.6998005235473741, -95.3855833545501639 29.6996619391729801, -95.3855776331865002 29.6996232659570047, -95.3850162731712885 29.6997236706530536, -95.3831047357410284 29.7000847603095082, -95.3829800724914776 29.7000676365023502, -95.3828084594470909 29.6999969684031200, -95.3828131504821499 29.6999090511531065, -95.3828022942979601 29.6998152117366025, -95.3827893930918833 29.6997790953076759, -95.3825174668099862 29.6998267772748825, -95.3823521544804862 29.7000451723151606, -95.3820491918785223 29.6999682034582335, -95.3817932841505893 29.6999640407204772, -95.3815438924600443 29.7005983712500630, -95.3807812390843424 29.7007538492921590, -95.3778578936435935 29.7012966201172048, -95.3770817300034679 29.7010555145969093, -95.3772763716395957 29.7004995005932031, -95.3769891024414420 29.7005797730360186, -95.3759855007185990 29.7007754783987821, -95.3759516423090474 29.7007305400669388, -95.3765252155960042 29.6989549173240874, -95.3766842746727832 29.6985134987163164, -95.3768510987262914 29.6980530300744938, -95.3769198676258014 29.6977137204527573, -95.3769616670751930 29.6973351617272172, -95.3770309229297766 29.6969821084304186, -95.3772352596880637 29.6959751305871613, -95.3776232419333354 29.6945439060847463, -95.3776849628727064 29.6943364710766069, -95.3779699491714723 29.6926548349458947, -95.3781945479573494 29.6920088336742545, -95.3785807118394189 29.6908279316076005, -95.3787441368896651 29.6908846275832197, -95.3787903214163890 29.6907152912461640, -95.3791765069353659 29.6893335376821526, -95.3794935959513026 29.6884781789101595, -95.3796592071232112 29.6880066681407619, -95.3799788182090111 29.6873687353035081, -95.3801545516183893 29.6868782380716993, -95.3801258908302145 29.6867756621337762, -95.3801104284899566 29.6867229678809572, -95.3803803523746154 29.6863753372986459, -95.3821028558287622 29.6837392961470421, -95.3827289584682205 29.6828097375216160, -95.3827494698109035 29.6790739156259278, -95.3826022014838486 29.6776502228345507, -95.3825047356438063 29.6765773006280753, -95.3823473035336917 29.6750405250369127, -95.3824540163482055 29.6750076408228587, -95.3838984230304305 29.6745679207378679, -95.3916547074937426 29.6722459226508377, -95.3926154662749468 29.6719609085105489, -95.3967246645118081 29.6707316485589736, -95.3974588054406780 29.6705065336410989, -95.3978523748756828 29.6703795547846845, -95.3988598162279970 29.6700874981900853, -95.3995628600665952 29.6698505300412414, -95.4134721665944170 29.6656841279906232, -95.4143262068232616 29.6654291174019278, -95.4159685142480214 29.6649750989232288, -95.4180067396277565 29.6643253024318021, -95.4185886692196590 29.6641482768691063, -95.4234155309609662 29.6626925393704788, -95.4287785503196346 29.6611023620959706, -95.4310287312749352 29.6604222580752648, -95.4320295629628959 29.6603361318136720, -95.4332899683975739 29.6600560661713608, -95.4342675748811047 29.6598454934599900, -95.4343110414310871 29.6598411486215490, -95.4345576779282538 29.6598147020668499, -95.4348823041721630 29.6597875803673112, -95.4352827715209457 29.6597762346946681, -95.4355290431309982 29.6597827926562374, -95.4359197997999331 29.6598014511782715, -95.4361907884752156 29.6598444333523368, -95.4364608955807228 29.6598901433108217, -95.4367250147512323 29.6599494499910712, -95.4364898759758091 29.6601880616540186, -95.4354501111810691 29.6616378572201107, -95.4381459623171224 29.6631265631655126, -95.4367852490863129 29.6642266600024023, -95.4370040894557263 29.6643425389568769, -95.4367078350812648 29.6645492592343238, -95.4366081749871285 29.6646291473027297, -95.4358539359938192 29.6652308742342932, -95.4350327668927889 29.6658995989314462, -95.4350580905272921 29.6678812477895271, -95.4349710541447536 29.6680054925936965, -95.4349500440473548 29.6671410080890006, -95.4341492724148850 29.6678790545191688, -95.4340248868274728 29.6680353198492135, -95.4333227845797438 29.6689245624945990, -95.4331325652123326 29.6691616138940901, -95.4321314741096955 29.6704473333237253, -95.4320435792664341 29.6702578985411982, -95.4320147929883547 29.6701800936425109, -95.4319764538662980 29.6683246590817085, -95.4317490976340679 29.6684974372577166, -95.4305958185342718 29.6694049049170374, -95.4296600735653016 29.6701723430938493, -95.4284928989940937 29.6710931793380972, -95.4274630532378580 29.6719378813640091, -95.4273056811974811 29.6720684984625791, -95.4260554084574864 29.6730668861566969, -95.4253558063699643 29.6736342467365724, -95.4249278826026028 29.6739557343648919, -95.4248648873821423 29.6745400910786152, -95.4260016131471929 29.6750987014005858, -95.4258567183010911 29.6753452063069929, -95.4260238081486847 29.6754322077221353, -95.4258707374502393 29.6756647377294307, -95.4257951755816691 29.6756407098663360, -95.4257701599566985 29.6761077719536068, -95.4257726684792260 29.6761711204603955, -95.4257980187195614 29.6770219651929423, -95.4252712669032519 29.6770161558853758, -95.4249234392992065 29.6770068683962300, -95.4249574272905789 29.6779707498635759, -95.4244725881033702 29.6779825646764159, -95.4222269476429545 29.6780711474441716, -95.4223032371999267 29.6796029391538809, -95.4239133706588945 29.6795331493690355, -95.4224579084327331 29.6813706893847780, -95.4224290108823965 29.6821953228763924, -95.4230916478977349 29.6822130268724109, -95.4222928279595521 29.6832041816675343, -95.4228763710016352 29.6832087677714505, -95.4223401691637179 29.6838987872753748, -95.4211655906087088 29.6838784024852984, -95.4201984153205558 29.6851319258758082, -95.4206156387716362 29.6851623398125319, -95.4213438084897660 29.6851763011334739, -95.4212071118618752 29.6853679931624974, -95.4202651399651245 29.6865313962980508, -95.4172061157659783 29.6865816431043932, -95.4182217951255183 29.6872251197301544, -95.4178664826439160 29.6876750901471631, -95.4180678442928780 29.6877960336377207, -95.4188763472917572 29.6882826379510938, -95.4185374500596311 29.6887137897831934, -95.4182121713132290 29.6885097429738813, -95.4179857231741551 29.6888118367840086, -95.4183106010563620 29.6890048676118212, -95.4179489865331334 29.6894546700979056, -95.4175581746284820 29.6892323606815438, -95.4173439957341571 29.6894990139807007, -95.4177411199311081 29.6897435034738422, -95.4175789200209721 29.6899207529979208, -95.4170598559864800 29.6896042165807508, -95.4166733682539814 29.6900891174451367, -95.4165941362704331 29.6900347214235047, -95.4163537218065301 29.6903529467753238, -95.4126843270708775 29.6881086357212780, -95.4126604121378392 29.6880942378803496, -95.4126672298953338 29.6885951670109982, -95.4126680884821923 29.6887052446594275, -95.4158080137241882 29.6906382377959339, -95.4152061403821961 29.6910871045531586, -95.4155842583188161 29.6917382915894308, -95.4157426793520358 29.6920726941677096, -95.4154520563662203 29.6922052332446427, -95.4151389936167078 29.6923261661269571, -95.4148649784384872 29.6924343866430256, -95.4144051352401590 29.6925623927348106, -95.4146792019416665 29.6926770338507744, -95.4148824479948985 29.6928117893696388, -95.4149851734360226 29.6929823719519774, -95.4140436551925291 29.6929626643100946, -95.4140465993023241 29.6926545917254892, -95.4137269186733334 29.6927395764256090, -95.4137372859685513 29.6935432485666624, -95.4135702836218655 29.6933186678088283, -95.4133925235973237 29.6930415229852152, -95.4133017035615580 29.6928685062036166, -95.4129588921634593 29.6929391128977862, -95.4125107395559695 29.6930481664661485, -95.4102647423187307 29.6935850183258019, -95.4081931340840157 29.6940907430947760, -95.4078783596459772 29.6941703429951609, -95.4049213975000043 29.6948723732981961, -95.4045944244127071 29.6949626434239207, -95.4045865139788134 29.6954109019001358, -95.4045953345484037 29.6956972800496963, -95.4038879332535146 29.6958296089365490, -95.4040366394459340 29.6964389004769842, -95.4032774779020798 29.6965643341263892, -95.4026066501239853 29.6966646227683881, -95.4024991226393837 29.6961389766619703, -95.4011781398631911 29.6963566063186377, -95.4011524097636112 29.6962596176762190, -95.4018184046368276 29.6961399466727336, -95.4016995838361908 29.6956442609415099, -95.4007100753964608 29.6958900524002978, -95.4008032469935188 29.6962639900781404, -95.3995660267125487 29.6965636449370329, -95.3996140564775601 29.6967877962763644, -95.3996364430014410 29.6968901984825280, -95.3984003269631842 29.6968679634805746, -95.3981442026887265 29.6983660679730335, -95.3980178461957706 29.6990890276252415, -95.3977097967130163 29.7008526152273049, -95.3962347157626027 29.7009697553607630, -95.3951949050136250 29.7004740386619019, -95.3957564950617183 29.6990281830553187, -95.3965927101519924 29.6968771129030706, -95.3957496517238184 29.6970800358387095, -95.3957720559467361 29.6972264611230727, -95.3957391586571788 29.6973548894558732, -95.3956286413405365 29.6974949857280883, -95.3955111053256957 29.6975661086270186, -95.3953215342724121 29.6976022763384790, -95.3951795558443365 29.6975846977491038, -95.3950369632041060 29.6975175779330200, -95.3949401089966500 29.6974269267953304, -95.3948740281415581 29.6972903308506346, -95.3946650813866910 29.6973397326847923, -95.3947654059391112 29.6974882560192022, -95.3949627316619768 29.6980355864961858, -95.3933200807862249 29.6984590863712796, -95.3932606497523494 29.6984464798710839, -95.3932983699113350 29.6983154306484352, -95.3933058014696655 29.6982165816983610, -95.3932946347785133 29.6981089778195759, -95.3931780601756287 29.6977068906794841, -95.3929928222970602 29.6977541771878180, -95.3930873169846478 29.6980676264932946, -95.3932743746374570 29.6981249406449663, -95.3929512584706316 29.6989526513922222, -95.3919850280655197 29.7014358632108646, -95.3918950918929056 29.7014169320765724, -95.3916928317890296 29.7019232352846423, -95.3915424614970959 29.7022988712928289, -95.3901530441668939 29.7058519502930061, -95.3899656322116698 29.7059156823562418, -95.3897628748670883 29.7059900058266777, -95.3896062677805787 29.7060738276384946, -95.3893941800512266 29.7061891695242046, -95.3892150365492455 29.7062641292949436, -95.3890502563035199 29.7063339729630940, -95.3888717930715586 29.7063896908080736, -95.3886925428988945 29.7064453871994978, -95.3885376849411983 29.7064797304524149, -95.3883284158984139 29.7065153575050189, -95.3881046767627794 29.7065368368267357, -95.3878809284696132 29.7065363048447537, -95.3876046356120924 29.7065288525102424, -95.3873060894974714 29.7064822806001452, -95.3869851943158409 29.7063993367575350, -95.3865967896568065 29.7062870572919202, -95.3861785624983156 29.7061492099008184, -95.3857375009733488 29.7059887337478798, -95.3854573290902152 29.7058683664514618, -95.3848703124799471 29.7056021479768511))', - n_i=0, ext_ring_cs=False, n_p=264, area=0.00129917360654, centroid=(-95.403569179437341, 29.681772571690402), - ), - ) - -# MultiPolygons -multipolygons = (TestGeom('MULTIPOLYGON (((100 20, 180 20, 180 100, 100 100, 100 20)), ((20 100, 100 100, 100 180, 20 180, 20 100)), ((100 180, 180 180, 180 260, 100 260, 100 180)), ((180 100, 260 100, 260 180, 180 180, 180 100)))', valid=True, num_geom=4, n_p=20), - TestGeom('MULTIPOLYGON (((60 300, 320 220, 260 60, 60 100, 60 300)), ((60 300, 320 220, 260 60, 60 100, 60 300)))', valid=False), - TestGeom('MULTIPOLYGON (((180 60, 240 160, 300 60, 180 60)), ((80 80, 180 60, 160 140, 240 160, 360 140, 300 60, 420 100, 320 280, 120 260, 80 80)))', valid=True, num_geom=2, n_p=14), - ) - -# Points -points = (TestGeom('POINT (5 23)', x=5.0, y=23.0, centroid=(5.0, 23.0)), - TestGeom('POINT (-95.338492 29.723893)', x=-95.338492, y=29.723893, centroid=(-95.338492, 29.723893)), - TestGeom('POINT(1.234 5.678)', x=1.234, y=5.678, centroid=(1.234, 5.678)), - TestGeom('POINT(4.321 8.765)', x=4.321, y=8.765, centroid=(4.321, 8.765)), - TestGeom('POINT(10 10)', x=10, y=10, centroid=(10., 10.)), - TestGeom('POINT (5 23 8)', x=5.0, y=23.0, z=8.0, centroid=(5.0, 23.0)), - ) - -# MultiPoints -multipoints = (TestGeom('MULTIPOINT(10 10, 20 20 )', n_p=2, points=((10., 10.), (20., 20.)), centroid=(15., 15.)), - TestGeom('MULTIPOINT(10 10, 20 20, 10 20, 20 10)', - n_p=4, points=((10., 10.), (20., 20.), (10., 20.), (20., 10.)), - centroid=(15., 15.)), - ) - -# LineStrings -linestrings = (TestGeom('LINESTRING (60 180, 120 100, 180 180)', n_p=3, centroid=(120, 140), tup=((60, 180), (120, 100), (180, 180))), - TestGeom('LINESTRING (0 0, 5 5, 10 5, 10 10)', n_p=4, centroid=(6.1611652351681556, 4.6966991411008934), tup=((0, 0), (5, 5), (10, 5), (10, 10)),), - ) - -# Linear Rings -linearrings = (TestGeom('LINEARRING (649899.3065171393100172 4176512.3807915160432458, 649902.7294133581453934 4176512.7834989596158266, 649906.5550170192727819 4176514.3942507002502680, 649910.5820134161040187 4176516.0050024418160319, 649914.4076170771149918 4176518.0184616246260703, 649917.2264131171396002 4176519.4278986593708396, 649920.0452871860470623 4176521.6427505780011415, 649922.0587463703704998 4176522.8507948759943247, 649924.2735982896992937 4176524.4616246484220028, 649926.2870574744883925 4176525.4683542405255139, 649927.8978092158213258 4176526.8777912775985897, 649929.3072462501004338 4176528.0858355751261115, 649930.1126611357321963 4176529.4952726080082357, 649927.4951798024121672 4176506.9444361114874482, 649899.3065171393100172 4176512.3807915160432458)', n_p=15), - ) - -# MultiLineStrings -multilinestrings = (TestGeom('MULTILINESTRING ((0 0, 0 100), (100 0, 100 100))', n_p=4, centroid=(50, 50), tup=(((0, 0), (0, 100)), ((100, 0), (100, 100)))), - TestGeom('MULTILINESTRING ((20 20, 60 60), (20 -20, 60 -60), (-20 -20, -60 -60), (-20 20, -60 60), (-80 0, 0 80, 80 0, 0 -80, -80 0), (-40 20, -40 -20), (-20 40, 20 40), (40 20, 40 -20), (20 -40, -20 -40))', - n_p=21, centroid=(0, 0), tup=(((20., 20.), (60., 60.)), ((20., -20.), (60., -60.)), ((-20., -20.), (-60., -60.)), ((-20., 20.), (-60., 60.)), ((-80., 0.), (0., 80.), (80., 0.), (0., -80.), (-80., 0.)), ((-40., 20.), (-40., -20.)), ((-20., 40.), (20., 40.)), ((40., 20.), (40., -20.)), ((20., -40.), (-20., -40.)))) - ) - -# ==================================================== -# Topology Operations - -topology_geoms = ( (TestGeom('POLYGON ((-5.0 0.0, -5.0 10.0, 5.0 10.0, 5.0 0.0, -5.0 0.0))'), - TestGeom('POLYGON ((0.0 -5.0, 0.0 5.0, 10.0 5.0, 10.0 -5.0, 0.0 -5.0))') - ), - (TestGeom('POLYGON ((2 0, 18 0, 18 15, 2 15, 2 0))'), - TestGeom('POLYGON ((10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'), - ), - ) - -intersect_geoms = ( TestGeom('POLYGON ((5 5,5 0,0 0,0 5,5 5))'), - TestGeom('POLYGON ((10 1, 9 3, 7 4, 5 6, 4 8, 4 10, 5 12, 7 13, 9 12, 10 10, 11 12, 13 13, 15 12, 16 10, 16 8, 15 6, 13 4, 11 3, 10 1))'), - ) - -union_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,10 5,10 -5,0 -5,0 0,-5 0))'), - TestGeom('POLYGON ((2 0, 2 15, 18 15, 18 0, 2 0))'), - ) - -diff_geoms = ( TestGeom('POLYGON ((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0))'), - TestGeom('POLYGON ((2 0, 2 15, 18 15, 18 0, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'), - ) - -sdiff_geoms = ( TestGeom('MULTIPOLYGON (((-5 0,-5 10,5 10,5 5,0 5,0 0,-5 0)),((0 0,5 0,5 5,10 5,10 -5,0 -5,0 0)))'), - TestGeom('POLYGON ((2 0, 2 15, 18 15, 18 0, 2 0), (10 1, 11 3, 13 4, 15 6, 16 8, 16 10, 15 12, 13 13, 11 12, 10 10, 9 12, 7 13, 5 12, 4 10, 4 8, 5 6, 7 4, 9 3, 10 1))'), - ) - -relate_geoms = ( (TestGeom('MULTIPOINT(80 70, 20 20, 200 170, 140 120)'), - TestGeom('MULTIPOINT(80 170, 140 120, 200 80, 80 70)'), - '0F0FFF0F2', True,), - (TestGeom('POINT(20 20)'), TestGeom('POINT(40 60)'), - 'FF0FFF0F2', True,), - (TestGeom('POINT(110 110)'), TestGeom('LINESTRING(200 200, 110 110, 200 20, 20 20, 110 110, 20 200, 200 200)'), - '0FFFFF1F2', True,), - (TestGeom('MULTILINESTRING((20 20, 90 20, 170 20), (90 20, 90 80, 90 140))'), - TestGeom('MULTILINESTRING((90 20, 170 100, 170 140), (130 140, 130 60, 90 20, 20 90, 90 20))'), - 'FF10F0102', True,), - ) - -buffer_geoms = ( (TestGeom('POINT(0 0)'), - TestGeom('POLYGON ((5 0,4.903926402016153 -0.97545161008064,4.619397662556435 -1.913417161825447,4.157348061512728 -2.777851165098009,3.53553390593274 -3.535533905932735,2.777851165098015 -4.157348061512724,1.913417161825454 -4.619397662556431,0.975451610080648 -4.903926402016151,0.000000000000008 -5.0,-0.975451610080632 -4.903926402016154,-1.913417161825439 -4.619397662556437,-2.777851165098002 -4.157348061512732,-3.53553390593273 -3.535533905932746,-4.157348061512719 -2.777851165098022,-4.619397662556429 -1.913417161825462,-4.903926402016149 -0.975451610080656,-5.0 -0.000000000000016,-4.903926402016156 0.975451610080624,-4.619397662556441 1.913417161825432,-4.157348061512737 2.777851165097995,-3.535533905932752 3.535533905932723,-2.777851165098029 4.157348061512714,-1.913417161825468 4.619397662556426,-0.975451610080661 4.903926402016149,-0.000000000000019 5.0,0.975451610080624 4.903926402016156,1.913417161825434 4.61939766255644,2.777851165097998 4.157348061512735,3.535533905932727 3.535533905932748,4.157348061512719 2.777851165098022,4.619397662556429 1.91341716182546,4.90392640201615 0.975451610080652,5 0))'), - 5.0, 8), - (TestGeom('POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'), - TestGeom('POLYGON ((-2 0,-2 10,-1.961570560806461 10.390180644032258,-1.847759065022573 10.765366864730179,-1.662939224605091 11.111140466039204,-1.414213562373095 11.414213562373096,-1.111140466039204 11.662939224605092,-0.765366864730179 11.847759065022574,-0.390180644032256 11.961570560806461,0 12,10 12,10.390180644032256 11.961570560806461,10.765366864730179 11.847759065022574,11.111140466039204 11.66293922460509,11.414213562373096 11.414213562373096,11.66293922460509 11.111140466039204,11.847759065022574 10.765366864730179,11.961570560806461 10.390180644032256,12 10,12 0,11.961570560806461 -0.390180644032256,11.847759065022574 -0.76536686473018,11.66293922460509 -1.111140466039204,11.414213562373096 -1.414213562373095,11.111140466039204 -1.66293922460509,10.765366864730179 -1.847759065022573,10.390180644032256 -1.961570560806461,10 -2,0.0 -2.0,-0.390180644032255 -1.961570560806461,-0.765366864730177 -1.847759065022575,-1.1111404660392 -1.662939224605093,-1.41421356237309 -1.4142135623731,-1.662939224605086 -1.111140466039211,-1.84775906502257 -0.765366864730189,-1.961570560806459 -0.390180644032268,-2 0))'), - 2.0, 8), - ) - -json_geoms = (TestGeom('POINT(100 0)', json='{ "type": "Point", "coordinates": [ 100.000000, 0.000000 ] }'), - TestGeom('POLYGON((0 0, -10 0, -10 -10, 0 -10, 0 0))', json='{ "type": "Polygon", "coordinates": [ [ [ 0.000000, 0.000000 ], [ -10.000000, 0.000000 ], [ -10.000000, -10.000000 ], [ 0.000000, -10.000000 ], [ 0.000000, 0.000000 ] ] ] }'), - TestGeom('MULTIPOLYGON(((102 2, 103 2, 103 3, 102 3, 102 2)), ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)))', json='{ "type": "MultiPolygon", "coordinates": [ [ [ [ 102.000000, 2.000000 ], [ 103.000000, 2.000000 ], [ 103.000000, 3.000000 ], [ 102.000000, 3.000000 ], [ 102.000000, 2.000000 ] ] ], [ [ [ 100.000000, 0.000000 ], [ 101.000000, 0.000000 ], [ 101.000000, 1.000000 ], [ 100.000000, 1.000000 ], [ 100.000000, 0.000000 ] ], [ [ 100.200000, 0.200000 ], [ 100.800000, 0.200000 ], [ 100.800000, 0.800000 ], [ 100.200000, 0.800000 ], [ 100.200000, 0.200000 ] ] ] ] }'), - TestGeom('GEOMETRYCOLLECTION(POINT(100 0),LINESTRING(101.0 0.0, 102.0 1.0))', - json='{ "type": "GeometryCollection", "geometries": [ { "type": "Point", "coordinates": [ 100.000000, 0.000000 ] }, { "type": "LineString", "coordinates": [ [ 101.000000, 0.000000 ], [ 102.000000, 1.000000 ] ] } ] }', - ), - TestGeom('MULTILINESTRING((100.0 0.0, 101.0 1.0),(102.0 2.0, 103.0 3.0))', - json=""" - -{ "type": "MultiLineString", - "coordinates": [ - [ [100.0, 0.0], [101.0, 1.0] ], - [ [102.0, 2.0], [103.0, 3.0] ] - ] - } - -""", - not_equal=True, - ), - ) - -# For testing HEX(EWKB). -ogc_hex = '01010000000000000000000000000000000000F03F' -# `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));` -hexewkb_2d = '0101000020E61000000000000000000000000000000000F03F' -# `SELECT ST_AsHEXEWKB(ST_GeomFromEWKT('SRID=4326;POINT(0 1 2)'));` -hexewkb_3d = '01010000A0E61000000000000000000000000000000000F03F0000000000000040' From 775165b23634ff19692cd7226c40e2d38d1304ef Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Mon, 1 Nov 2010 22:18:50 +0000 Subject: [PATCH 399/902] [1.2.X] Fixed some a stale location and whitespace in GeoIP tests. Backport of r14416 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14417 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/tests/test_geoip.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/django/contrib/gis/tests/test_geoip.py b/django/contrib/gis/tests/test_geoip.py index 430d61b6d5e2..a9ab6a6ab4d9 100644 --- a/django/contrib/gis/tests/test_geoip.py +++ b/django/contrib/gis/tests/test_geoip.py @@ -5,10 +5,10 @@ # Note: Requires use of both the GeoIP country and city datasets. # The GEOIP_DATA path should be the only setting set (the directory -# should contain links or the actual database files 'GeoIP.dat' and +# should contain links or the actual database files 'GeoIP.dat' and # 'GeoLiteCity.dat'. class GeoIPTest(unittest.TestCase): - + def test01_init(self): "Testing GeoIP initialization." g1 = GeoIP() # Everything inferred from GeoIP path @@ -19,7 +19,7 @@ def test01_init(self): for g in (g1, g2, g3): self.assertEqual(True, bool(g._country)) self.assertEqual(True, bool(g._city)) - + # Only passing in the location of one database. city = os.path.join(path, 'GeoLiteCity.dat') cntry = os.path.join(path, 'GeoIP.dat') @@ -52,10 +52,10 @@ def test02_bad_query(self): def test03_country(self): "Testing GeoIP country querying methods." g = GeoIP(city='') - + fqdn = 'www.google.com' addr = '12.215.42.19' - + for query in (fqdn, addr): for func in (g.country_code, g.country_code_by_addr, g.country_code_by_name): self.assertEqual('US', func(query)) @@ -67,7 +67,7 @@ def test03_country(self): def test04_city(self): "Testing GeoIP city querying methods." g = GeoIP(country='') - + addr = '130.80.29.3' fqdn = 'chron.com' for query in (fqdn, addr): @@ -78,7 +78,7 @@ def test04_city(self): self.assertEqual('United States', func(query)) self.assertEqual({'country_code' : 'US', 'country_name' : 'United States'}, g.country(query)) - + # City information dictionary. d = g.city(query) self.assertEqual('USA', d['country_code3']) @@ -87,7 +87,7 @@ def test04_city(self): self.assertEqual(713, d['area_code']) geom = g.geos(query) self.failIf(not isinstance(geom, GEOSGeometry)) - lon, lat = (-95.4152, 29.7755) + lon, lat = (-95.3670, 29.7523) lat_lon = g.lat_lon(query) lat_lon = (lat_lon[1], lat_lon[0]) for tup in (geom.tuple, g.coords(query), g.lon_lat(query), lat_lon): From 0b0ae709d120bf54b3b8003a80f8bde7f8b9c519 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 2 Nov 2010 02:01:18 +0000 Subject: [PATCH 400/902] [1.2.X] Migrated m2m_through doctests. Thanks to the anonymous contributor. Backport of r14419 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14420 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/m2m_through/models.py | 272 -------------------- tests/modeltests/m2m_through/tests.py | 343 +++++++++++++++++++++++++ 2 files changed, 343 insertions(+), 272 deletions(-) create mode 100644 tests/modeltests/m2m_through/tests.py diff --git a/tests/modeltests/m2m_through/models.py b/tests/modeltests/m2m_through/models.py index 16f303d02e69..d41fe8d26d57 100644 --- a/tests/modeltests/m2m_through/models.py +++ b/tests/modeltests/m2m_through/models.py @@ -63,275 +63,3 @@ class Friendship(models.Model): first = models.ForeignKey(PersonSelfRefM2M, related_name="rel_from_set") second = models.ForeignKey(PersonSelfRefM2M, related_name="rel_to_set") date_friended = models.DateTimeField() - -__test__ = {'API_TESTS':""" ->>> from datetime import datetime - -### Creation and Saving Tests ### - ->>> bob = Person.objects.create(name='Bob') ->>> jim = Person.objects.create(name='Jim') ->>> jane = Person.objects.create(name='Jane') ->>> rock = Group.objects.create(name='Rock') ->>> roll = Group.objects.create(name='Roll') - -# We start out by making sure that the Group 'rock' has no members. ->>> rock.members.all() -[] - -# To make Jim a member of Group Rock, simply create a Membership object. ->>> m1 = Membership.objects.create(person=jim, group=rock) - -# We can do the same for Jane and Rock. ->>> m2 = Membership.objects.create(person=jane, group=rock) - -# Let's check to make sure that it worked. Jane and Jim should be members of Rock. ->>> rock.members.all() -[, ] - -# Now we can add a bunch more Membership objects to test with. ->>> m3 = Membership.objects.create(person=bob, group=roll) ->>> m4 = Membership.objects.create(person=jim, group=roll) ->>> m5 = Membership.objects.create(person=jane, group=roll) - -# We can get Jim's Group membership as with any ForeignKey. ->>> jim.group_set.all() -[, ] - -# Querying the intermediary model works like normal. -# In this case we get Jane's membership to Rock. ->>> m = Membership.objects.get(person=jane, group=rock) ->>> m - - -# Now we set some date_joined dates for further testing. ->>> m2.invite_reason = "She was just awesome." ->>> m2.date_joined = datetime(2006, 1, 1) ->>> m2.save() - ->>> m5.date_joined = datetime(2004, 1, 1) ->>> m5.save() - ->>> m3.date_joined = datetime(2004, 1, 1) ->>> m3.save() - -# It's not only get that works. Filter works like normal as well. ->>> Membership.objects.filter(person=jim) -[, ] - - -### Forward Descriptors Tests ### - -# Due to complications with adding via an intermediary model, -# the add method is not provided. ->>> rock.members.add(bob) -Traceback (most recent call last): -... -AttributeError: 'ManyRelatedManager' object has no attribute 'add' - -# Create is also disabled as it suffers from the same problems as add. ->>> rock.members.create(name='Anne') -Traceback (most recent call last): -... -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead. - -# Remove has similar complications, and is not provided either. ->>> rock.members.remove(jim) -Traceback (most recent call last): -... -AttributeError: 'ManyRelatedManager' object has no attribute 'remove' - -# Here we back up the list of all members of Rock. ->>> backup = list(rock.members.all()) - -# ...and we verify that it has worked. ->>> backup -[, ] - -# The clear function should still work. ->>> rock.members.clear() - -# Now there will be no members of Rock. ->>> rock.members.all() -[] - -# Assignment should not work with models specifying a through model for many of -# the same reasons as adding. ->>> rock.members = backup -Traceback (most recent call last): -... -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead. - -# Let's re-save those instances that we've cleared. ->>> m1.save() ->>> m2.save() - -# Verifying that those instances were re-saved successfully. ->>> rock.members.all() -[, ] - - -### Reverse Descriptors Tests ### - -# Due to complications with adding via an intermediary model, -# the add method is not provided. ->>> bob.group_set.add(rock) -Traceback (most recent call last): -... -AttributeError: 'ManyRelatedManager' object has no attribute 'add' - -# Create is also disabled as it suffers from the same problems as add. ->>> bob.group_set.create(name='Funk') -Traceback (most recent call last): -... -AttributeError: Cannot use create() on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead. - -# Remove has similar complications, and is not provided either. ->>> jim.group_set.remove(rock) -Traceback (most recent call last): -... -AttributeError: 'ManyRelatedManager' object has no attribute 'remove' - -# Here we back up the list of all of Jim's groups. ->>> backup = list(jim.group_set.all()) ->>> backup -[, ] - -# The clear function should still work. ->>> jim.group_set.clear() - -# Now Jim will be in no groups. ->>> jim.group_set.all() -[] - -# Assignment should not work with models specifying a through model for many of -# the same reasons as adding. ->>> jim.group_set = backup -Traceback (most recent call last): -... -AttributeError: Cannot set values on a ManyToManyField which specifies an intermediary model. Use m2m_through.Membership's Manager instead. - -# Let's re-save those instances that we've cleared. ->>> m1.save() ->>> m4.save() - -# Verifying that those instances were re-saved successfully. ->>> jim.group_set.all() -[, ] - -### Custom Tests ### - -# Let's see if we can query through our second relationship. ->>> rock.custom_members.all() -[] - -# We can query in the opposite direction as well. ->>> bob.custom.all() -[] - -# Let's create some membership objects in this custom relationship. ->>> cm1 = CustomMembership.objects.create(person=bob, group=rock) ->>> cm2 = CustomMembership.objects.create(person=jim, group=rock) - -# If we get the number of people in Rock, it should be both Bob and Jim. ->>> rock.custom_members.all() -[, ] - -# Bob should only be in one custom group. ->>> bob.custom.all() -[] - -# Let's make sure our new descriptors don't conflict with the FK related_name. ->>> bob.custom_person_related_name.all() -[] - -### SELF-REFERENTIAL TESTS ### - -# Let's first create a person who has no friends. ->>> tony = PersonSelfRefM2M.objects.create(name="Tony") ->>> tony.friends.all() -[] - -# Now let's create another person for Tony to be friends with. ->>> chris = PersonSelfRefM2M.objects.create(name="Chris") ->>> f = Friendship.objects.create(first=tony, second=chris, date_friended=datetime.now()) - -# Tony should now show that Chris is his friend. ->>> tony.friends.all() -[] - -# But we haven't established that Chris is Tony's Friend. ->>> chris.friends.all() -[] - -# So let's do that now. ->>> f2 = Friendship.objects.create(first=chris, second=tony, date_friended=datetime.now()) - -# Having added Chris as a friend, let's make sure that his friend set reflects -# that addition. ->>> chris.friends.all() -[] - -# Chris gets mad and wants to get rid of all of his friends. ->>> chris.friends.clear() - -# Now he should not have any more friends. ->>> chris.friends.all() -[] - -# Since this isn't a symmetrical relation, Tony's friend link still exists. ->>> tony.friends.all() -[] - - - -### QUERY TESTS ### - -# We can query for the related model by using its attribute name (members, in -# this case). ->>> Group.objects.filter(members__name='Bob') -[] - -# To query through the intermediary model, we specify its model name. -# In this case, membership. ->>> Group.objects.filter(membership__invite_reason="She was just awesome.") -[] - -# If we want to query in the reverse direction by the related model, use its -# model name (group, in this case). ->>> Person.objects.filter(group__name="Rock") -[, ] - -# If the m2m field has specified a related_name, using that will work. ->>> Person.objects.filter(custom__name="Rock") -[, ] - -# To query through the intermediary model in the reverse direction, we again -# specify its model name (membership, in this case). ->>> Person.objects.filter(membership__invite_reason="She was just awesome.") -[] - -# Let's see all of the groups that Jane joined after 1 Jan 2005: ->>> Group.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__person =jane) -[] - -# Queries also work in the reverse direction: Now let's see all of the people -# that have joined Rock since 1 Jan 2005: ->>> Person.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__group=rock) -[, ] - -# Conceivably, queries through membership could return correct, but non-unique -# querysets. To demonstrate this, we query for all people who have joined a -# group after 2004: ->>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)) -[, , ] - -# Jim showed up twice, because he joined two groups ('Rock', and 'Roll'): ->>> [(m.person.name, m.group.name) for m in -... Membership.objects.filter(date_joined__gt=datetime(2004, 1, 1))] -[(u'Jane', u'Rock'), (u'Jim', u'Rock'), (u'Jim', u'Roll')] - -# QuerySet's distinct() method can correct this problem. ->>> Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct() -[, ] -"""} diff --git a/tests/modeltests/m2m_through/tests.py b/tests/modeltests/m2m_through/tests.py new file mode 100644 index 000000000000..807e9529495f --- /dev/null +++ b/tests/modeltests/m2m_through/tests.py @@ -0,0 +1,343 @@ +from datetime import datetime +from operator import attrgetter + +from django.test import TestCase + +from models import Person, Group, Membership, CustomMembership, \ + TestNoDefaultsOrNulls, PersonSelfRefM2M, Friendship + + +class M2mThroughTests(TestCase): + def setUp(self): + self.bob = Person.objects.create(name='Bob') + self.jim = Person.objects.create(name='Jim') + self.jane = Person.objects.create(name='Jane') + self.rock = Group.objects.create(name='Rock') + self.roll = Group.objects.create(name='Roll') + + def test_m2m_through(self): + # We start out by making sure that the Group 'rock' has no members. + self.assertQuerysetEqual( + self.rock.members.all(), + [] + ) + # To make Jim a member of Group Rock, simply create a Membership object. + m1 = Membership.objects.create(person=self.jim, group=self.rock) + # We can do the same for Jane and Rock. + m2 = Membership.objects.create(person=self.jane, group=self.rock) + # Let's check to make sure that it worked. Jane and Jim should be members of Rock. + self.assertQuerysetEqual( + self.rock.members.all(), [ + 'Jane', + 'Jim' + ], + attrgetter("name") + ) + # Now we can add a bunch more Membership objects to test with. + m3 = Membership.objects.create(person=self.bob, group=self.roll) + m4 = Membership.objects.create(person=self.jim, group=self.roll) + m5 = Membership.objects.create(person=self.jane, group=self.roll) + # We can get Jim's Group membership as with any ForeignKey. + self.assertQuerysetEqual( + self.jim.group_set.all(), [ + 'Rock', + 'Roll' + ], + attrgetter("name") + ) + # Querying the intermediary model works like normal. + self.assertEqual( + repr(Membership.objects.get(person=self.jane, group=self.rock)), + '' + ) + # It's not only get that works. Filter works like normal as well. + self.assertQuerysetEqual( + Membership.objects.filter(person=self.jim), [ + '', + '' + ] + ) + self.rock.members.clear() + # Now there will be no members of Rock. + self.assertQuerysetEqual( + self.rock.members.all(), + [] + ) + + + + def test_forward_descriptors(self): + # Due to complications with adding via an intermediary model, + # the add method is not provided. + self.assertRaises(AttributeError, lambda: self.rock.members.add(self.bob)) + # Create is also disabled as it suffers from the same problems as add. + self.assertRaises(AttributeError, lambda: self.rock.members.create(name='Anne')) + # Remove has similar complications, and is not provided either. + self.assertRaises(AttributeError, lambda: self.rock.members.remove(self.jim)) + + m1 = Membership.objects.create(person=self.jim, group=self.rock) + m2 = Membership.objects.create(person=self.jane, group=self.rock) + + # Here we back up the list of all members of Rock. + backup = list(self.rock.members.all()) + # ...and we verify that it has worked. + self.assertEqual( + [p.name for p in backup], + ['Jane', 'Jim'] + ) + # The clear function should still work. + self.rock.members.clear() + # Now there will be no members of Rock. + self.assertQuerysetEqual( + self.rock.members.all(), + [] + ) + + # Assignment should not work with models specifying a through model for many of + # the same reasons as adding. + self.assertRaises(AttributeError, setattr, self.rock, "members", backup) + # Let's re-save those instances that we've cleared. + m1.save() + m2.save() + # Verifying that those instances were re-saved successfully. + self.assertQuerysetEqual( + self.rock.members.all(),[ + 'Jane', + 'Jim' + ], + attrgetter("name") + ) + + def test_reverse_descriptors(self): + # Due to complications with adding via an intermediary model, + # the add method is not provided. + self.assertRaises(AttributeError, lambda: self.bob.group_set.add(self.rock)) + # Create is also disabled as it suffers from the same problems as add. + self.assertRaises(AttributeError, lambda: self.bob.group_set.create(name="funk")) + # Remove has similar complications, and is not provided either. + self.assertRaises(AttributeError, lambda: self.jim.group_set.remove(self.rock)) + + m1 = Membership.objects.create(person=self.jim, group=self.rock) + m2 = Membership.objects.create(person=self.jim, group=self.roll) + + # Here we back up the list of all of Jim's groups. + backup = list(self.jim.group_set.all()) + self.assertEqual( + [g.name for g in backup], + ['Rock', 'Roll'] + ) + # The clear function should still work. + self.jim.group_set.clear() + # Now Jim will be in no groups. + self.assertQuerysetEqual( + self.jim.group_set.all(), + [] + ) + # Assignment should not work with models specifying a through model for many of + # the same reasons as adding. + self.assertRaises(AttributeError, setattr, self.jim, "group_set", backup) + # Let's re-save those instances that we've cleared. + + m1.save() + m2.save() + # Verifying that those instances were re-saved successfully. + self.assertQuerysetEqual( + self.jim.group_set.all(),[ + 'Rock', + 'Roll' + ], + attrgetter("name") + ) + + def test_custom_tests(self): + # Let's see if we can query through our second relationship. + self.assertQuerysetEqual( + self.rock.custom_members.all(), + [] + ) + # We can query in the opposite direction as well. + self.assertQuerysetEqual( + self.bob.custom.all(), + [] + ) + + cm1 = CustomMembership.objects.create(person=self.bob, group=self.rock) + cm2 = CustomMembership.objects.create(person=self.jim, group=self.rock) + + # If we get the number of people in Rock, it should be both Bob and Jim. + self.assertQuerysetEqual( + self.rock.custom_members.all(),[ + 'Bob', + 'Jim' + ], + attrgetter("name") + ) + # Bob should only be in one custom group. + self.assertQuerysetEqual( + self.bob.custom.all(),[ + 'Rock' + ], + attrgetter("name") + ) + # Let's make sure our new descriptors don't conflict with the FK related_name. + self.assertQuerysetEqual( + self.bob.custom_person_related_name.all(),[ + '' + ] + ) + + def test_self_referential_tests(self): + # Let's first create a person who has no friends. + tony = PersonSelfRefM2M.objects.create(name="Tony") + self.assertQuerysetEqual( + tony.friends.all(), + [] + ) + + chris = PersonSelfRefM2M.objects.create(name="Chris") + f = Friendship.objects.create(first=tony, second=chris, date_friended=datetime.now()) + + # Tony should now show that Chris is his friend. + self.assertQuerysetEqual( + tony.friends.all(),[ + 'Chris' + ], + attrgetter("name") + ) + # But we haven't established that Chris is Tony's Friend. + self.assertQuerysetEqual( + chris.friends.all(), + [] + ) + f2 = Friendship.objects.create(first=chris, second=tony, date_friended=datetime.now()) + + # Having added Chris as a friend, let's make sure that his friend set reflects + # that addition. + self.assertQuerysetEqual( + chris.friends.all(),[ + 'Tony' + ], + attrgetter("name") + ) + + # Chris gets mad and wants to get rid of all of his friends. + chris.friends.clear() + # Now he should not have any more friends. + self.assertQuerysetEqual( + chris.friends.all(), + [] + ) + # Since this isn't a symmetrical relation, Tony's friend link still exists. + self.assertQuerysetEqual( + tony.friends.all(),[ + 'Chris' + ], + attrgetter("name") + ) + + def test_query_tests(self): + m1 = Membership.objects.create(person=self.jim, group=self.rock) + m2 = Membership.objects.create(person=self.jane, group=self.rock) + m3 = Membership.objects.create(person=self.bob, group=self.roll) + m4 = Membership.objects.create(person=self.jim, group=self.roll) + m5 = Membership.objects.create(person=self.jane, group=self.roll) + + m2.invite_reason = "She was just awesome." + m2.date_joined = datetime(2006, 1, 1) + m2.save() + m3.date_joined = datetime(2004, 1, 1) + m3.save() + m5.date_joined = datetime(2004, 1, 1) + m5.save() + + # We can query for the related model by using its attribute name (members, in + # this case). + self.assertQuerysetEqual( + Group.objects.filter(members__name='Bob'),[ + 'Roll' + ], + attrgetter("name") + ) + + # To query through the intermediary model, we specify its model name. + # In this case, membership. + self.assertQuerysetEqual( + Group.objects.filter(membership__invite_reason="She was just awesome."),[ + 'Rock' + ], + attrgetter("name") + ) + + # If we want to query in the reverse direction by the related model, use its + # model name (group, in this case). + self.assertQuerysetEqual( + Person.objects.filter(group__name="Rock"),[ + 'Jane', + 'Jim' + ], + attrgetter("name") + ) + + cm1 = CustomMembership.objects.create(person=self.bob, group=self.rock) + cm2 = CustomMembership.objects.create(person=self.jim, group=self.rock) + # If the m2m field has specified a related_name, using that will work. + self.assertQuerysetEqual( + Person.objects.filter(custom__name="Rock"),[ + 'Bob', + 'Jim' + ], + attrgetter("name") + ) + + # To query through the intermediary model in the reverse direction, we again + # specify its model name (membership, in this case). + self.assertQuerysetEqual( + Person.objects.filter(membership__invite_reason="She was just awesome."),[ + 'Jane' + ], + attrgetter("name") + ) + + # Let's see all of the groups that Jane joined after 1 Jan 2005: + self.assertQuerysetEqual( + Group.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__person=self.jane),[ + 'Rock' + ], + attrgetter("name") + ) + + # Queries also work in the reverse direction: Now let's see all of the people + # that have joined Rock since 1 Jan 2005: + self.assertQuerysetEqual( + Person.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__group=self.rock),[ + 'Jane', + 'Jim' + ], + attrgetter("name") + ) + + # Conceivably, queries through membership could return correct, but non-unique + # querysets. To demonstrate this, we query for all people who have joined a + # group after 2004: + self.assertQuerysetEqual( + Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)),[ + 'Jane', + 'Jim', + 'Jim' + ], + attrgetter("name") + ) + + # Jim showed up twice, because he joined two groups ('Rock', and 'Roll'): + self.assertEqual( + [(m.person.name, m.group.name) for m in Membership.objects.filter(date_joined__gt=datetime(2004, 1, 1))], + [(u'Jane', u'Rock'), (u'Jim', u'Rock'), (u'Jim', u'Roll')] + ) + # QuerySet's distinct() method can correct this problem. + self.assertQuerysetEqual( + Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct(),[ + 'Jane', + 'Jim' + ], + attrgetter("name") + ) From 443423c55f692e33d0f91d4bfe6adbc2c23583c2 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 2 Nov 2010 02:48:43 +0000 Subject: [PATCH 401/902] [1.2.X] Migrated basic doctests. Thanks to Preston Timmons for the patch. Backport of r14421 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14422 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/basic/models.py | 411 ---------------------- tests/modeltests/basic/tests.py | 562 +++++++++++++++++++++++++++++++ 2 files changed, 562 insertions(+), 411 deletions(-) create mode 100644 tests/modeltests/basic/tests.py diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index ad2e96581073..97552a976f84 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -15,414 +15,3 @@ class Meta: def __unicode__(self): return self.headline - -__test__ = {'API_TESTS': """ -# No articles are in the system yet. ->>> Article.objects.all() -[] - -# Create an Article. ->>> from datetime import datetime ->>> a = Article(id=None, headline='Area man programs in Python', pub_date=datetime(2005, 7, 28)) - -# Save it into the database. You have to call save() explicitly. ->>> a.save() - -# Now it has an ID. Note it's a long integer, as designated by the trailing "L". ->>> a.id -1L - -# Models have a pk property that is an alias for the primary key attribute (by -# default, the 'id' attribute). ->>> a.pk -1L - -# Access database columns via Python attributes. ->>> a.headline -'Area man programs in Python' ->>> a.pub_date -datetime.datetime(2005, 7, 28, 0, 0) - -# Change values by changing the attributes, then calling save(). ->>> a.headline = 'Area woman programs in Python' ->>> a.save() - -# Article.objects.all() returns all the articles in the database. ->>> Article.objects.all() -[] - -# Django provides a rich database lookup API. ->>> Article.objects.get(id__exact=1) - ->>> Article.objects.get(headline__startswith='Area woman') - ->>> Article.objects.get(pub_date__year=2005) - ->>> Article.objects.get(pub_date__year=2005, pub_date__month=7) - ->>> Article.objects.get(pub_date__year=2005, pub_date__month=7, pub_date__day=28) - ->>> Article.objects.get(pub_date__week_day=5) - - -# The "__exact" lookup type can be omitted, as a shortcut. ->>> Article.objects.get(id=1) - ->>> Article.objects.get(headline='Area woman programs in Python') - - ->>> Article.objects.filter(pub_date__year=2005) -[] ->>> Article.objects.filter(pub_date__year=2004) -[] ->>> Article.objects.filter(pub_date__year=2005, pub_date__month=7) -[] - ->>> Article.objects.filter(pub_date__week_day=5) -[] ->>> Article.objects.filter(pub_date__week_day=6) -[] - -# Django raises an Article.DoesNotExist exception for get() if the parameters -# don't match any object. ->>> Article.objects.get(id__exact=2) -Traceback (most recent call last): - ... -DoesNotExist: Article matching query does not exist. - ->>> Article.objects.get(pub_date__year=2005, pub_date__month=8) -Traceback (most recent call last): - ... -DoesNotExist: Article matching query does not exist. - ->>> Article.objects.get(pub_date__week_day=6) -Traceback (most recent call last): - ... -DoesNotExist: Article matching query does not exist. - -# Lookup by a primary key is the most common case, so Django provides a -# shortcut for primary-key exact lookups. -# The following is identical to articles.get(id=1). ->>> Article.objects.get(pk=1) - - -# pk can be used as a shortcut for the primary key name in any query ->>> Article.objects.filter(pk__in=[1]) -[] - -# Model instances of the same type and same ID are considered equal. ->>> a = Article.objects.get(pk=1) ->>> b = Article.objects.get(pk=1) ->>> a == b -True - -# You can initialize a model instance using positional arguments, which should -# match the field order as defined in the model. ->>> a2 = Article(None, 'Second article', datetime(2005, 7, 29)) ->>> a2.save() ->>> a2.id -2L ->>> a2.headline -'Second article' ->>> a2.pub_date -datetime.datetime(2005, 7, 29, 0, 0) - -# ...or, you can use keyword arguments. ->>> a3 = Article(id=None, headline='Third article', pub_date=datetime(2005, 7, 30)) ->>> a3.save() ->>> a3.id -3L ->>> a3.headline -'Third article' ->>> a3.pub_date -datetime.datetime(2005, 7, 30, 0, 0) - -# You can also mix and match position and keyword arguments, but be sure not to -# duplicate field information. ->>> a4 = Article(None, 'Fourth article', pub_date=datetime(2005, 7, 31)) ->>> a4.save() ->>> a4.headline -'Fourth article' - -# Don't use invalid keyword arguments. ->>> a5 = Article(id=None, headline='Invalid', pub_date=datetime(2005, 7, 31), foo='bar') -Traceback (most recent call last): - ... -TypeError: 'foo' is an invalid keyword argument for this function - -# You can leave off the value for an AutoField when creating an object, because -# it'll get filled in automatically when you save(). ->>> a5 = Article(headline='Article 6', pub_date=datetime(2005, 7, 31)) ->>> a5.save() ->>> a5.id -5L ->>> a5.headline -'Article 6' - -# If you leave off a field with "default" set, Django will use the default. ->>> a6 = Article(pub_date=datetime(2005, 7, 31)) ->>> a6.save() ->>> a6.headline -u'Default headline' - -# For DateTimeFields, Django saves as much precision (in seconds) as you -# give it. ->>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 31, 12, 30)) ->>> a7.save() ->>> Article.objects.get(id__exact=7).pub_date -datetime.datetime(2005, 7, 31, 12, 30) - ->>> a8 = Article(headline='Article 8', pub_date=datetime(2005, 7, 31, 12, 30, 45)) ->>> a8.save() ->>> Article.objects.get(id__exact=8).pub_date -datetime.datetime(2005, 7, 31, 12, 30, 45) ->>> a8.id -8L - -# Saving an object again doesn't create a new object -- it just saves the old one. ->>> a8.save() ->>> a8.id -8L ->>> a8.headline = 'Updated article 8' ->>> a8.save() ->>> a8.id -8L - ->>> a7 == a8 -False ->>> a8 == Article.objects.get(id__exact=8) -True ->>> a7 != a8 -True ->>> Article.objects.get(id__exact=8) != Article.objects.get(id__exact=7) -True ->>> Article.objects.get(id__exact=8) == Article.objects.get(id__exact=7) -False - -# You can use 'in' to test for membership... ->>> a8 in Article.objects.all() -True - -# ... but there will often be more efficient ways if that is all you need: ->>> Article.objects.filter(id=a8.id).exists() -True - -# dates() returns a list of available dates of the given scope for the given field. ->>> Article.objects.dates('pub_date', 'year') -[datetime.datetime(2005, 1, 1, 0, 0)] ->>> Article.objects.dates('pub_date', 'month') -[datetime.datetime(2005, 7, 1, 0, 0)] ->>> Article.objects.dates('pub_date', 'day') -[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)] ->>> Article.objects.dates('pub_date', 'day', order='ASC') -[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)] ->>> Article.objects.dates('pub_date', 'day', order='DESC') -[datetime.datetime(2005, 7, 31, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 28, 0, 0)] - -# dates() requires valid arguments. - ->>> Article.objects.dates() -Traceback (most recent call last): - ... -TypeError: dates() takes at least 3 arguments (1 given) - ->>> Article.objects.dates('invalid_field', 'year') -Traceback (most recent call last): - ... -FieldDoesNotExist: Article has no field named 'invalid_field' - ->>> Article.objects.dates('pub_date', 'bad_kind') -Traceback (most recent call last): - ... -AssertionError: 'kind' must be one of 'year', 'month' or 'day'. - ->>> Article.objects.dates('pub_date', 'year', order='bad order') -Traceback (most recent call last): - ... -AssertionError: 'order' must be either 'ASC' or 'DESC'. - -# Use iterator() with dates() to return a generator that lazily requests each -# result one at a time, to save memory. ->>> for a in Article.objects.dates('pub_date', 'day', order='DESC').iterator(): -... print repr(a) -datetime.datetime(2005, 7, 31, 0, 0) -datetime.datetime(2005, 7, 30, 0, 0) -datetime.datetime(2005, 7, 29, 0, 0) -datetime.datetime(2005, 7, 28, 0, 0) - -# You can combine queries with & and |. ->>> s1 = Article.objects.filter(id__exact=1) ->>> s2 = Article.objects.filter(id__exact=2) ->>> s1 | s2 -[, ] ->>> s1 & s2 -[] - -# You can get the number of objects like this: ->>> len(Article.objects.filter(id__exact=1)) -1 - -# You can get items using index and slice notation. ->>> Article.objects.all()[0] - ->>> Article.objects.all()[1:3] -[, ] ->>> s3 = Article.objects.filter(id__exact=3) ->>> (s1 | s2 | s3)[::2] -[, ] - -# Slicing works with longs. ->>> Article.objects.all()[0L] - ->>> Article.objects.all()[1L:3L] -[, ] ->>> s3 = Article.objects.filter(id__exact=3) ->>> (s1 | s2 | s3)[::2L] -[, ] - -# And can be mixed with ints. ->>> Article.objects.all()[1:3L] -[, ] - -# Slices (without step) are lazy: ->>> Article.objects.all()[0:5].filter() -[, , , , ] - -# Slicing again works: ->>> Article.objects.all()[0:5][0:2] -[, ] ->>> Article.objects.all()[0:5][:2] -[, ] ->>> Article.objects.all()[0:5][4:] -[] ->>> Article.objects.all()[0:5][5:] -[] - -# Some more tests! ->>> Article.objects.all()[2:][0:2] -[, ] ->>> Article.objects.all()[2:][:2] -[, ] ->>> Article.objects.all()[2:][2:3] -[] - -# Using an offset without a limit is also possible. ->>> Article.objects.all()[5:] -[, , ] - -# Also, once you have sliced you can't filter, re-order or combine ->>> Article.objects.all()[0:5].filter(id=1) -Traceback (most recent call last): - ... -AssertionError: Cannot filter a query once a slice has been taken. - ->>> Article.objects.all()[0:5].order_by('id') -Traceback (most recent call last): - ... -AssertionError: Cannot reorder a query once a slice has been taken. - ->>> Article.objects.all()[0:1] & Article.objects.all()[4:5] -Traceback (most recent call last): - ... -AssertionError: Cannot combine queries once a slice has been taken. - -# Negative slices are not supported, due to database constraints. -# (hint: inverting your ordering might do what you need). ->>> Article.objects.all()[-1] -Traceback (most recent call last): - ... -AssertionError: Negative indexing is not supported. ->>> Article.objects.all()[0:-5] -Traceback (most recent call last): - ... -AssertionError: Negative indexing is not supported. - -# An Article instance doesn't have access to the "objects" attribute. -# That's only available on the class. ->>> a7.objects.all() -Traceback (most recent call last): - ... -AttributeError: Manager isn't accessible via Article instances - ->>> a7.objects -Traceback (most recent call last): - ... -AttributeError: Manager isn't accessible via Article instances - -# Bulk delete test: How many objects before and after the delete? ->>> Article.objects.all() -[, , , , , , , ] ->>> Article.objects.filter(id__lte=4).delete() ->>> Article.objects.all() -[, , , ] -"""} - -from django.conf import settings - -building_docs = getattr(settings, 'BUILDING_DOCS', False) - -if building_docs or settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.postgresql': - __test__['API_TESTS'] += """ -# In PostgreSQL, microsecond-level precision is available. ->>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180)) ->>> a9.save() ->>> Article.objects.get(id__exact=9).pub_date -datetime.datetime(2005, 7, 31, 12, 30, 45, 180) -""" - -if building_docs or settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.mysql': - __test__['API_TESTS'] += """ -# In MySQL, microsecond-level precision isn't available. You'll lose -# microsecond-level precision once the data is saved. ->>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180)) ->>> a9.save() ->>> Article.objects.get(id__exact=9).pub_date -datetime.datetime(2005, 7, 31, 12, 30, 45) -""" - -__test__['API_TESTS'] += """ - -# You can manually specify the primary key when creating a new object. ->>> a101 = Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45)) ->>> a101.save() ->>> a101 = Article.objects.get(pk=101) ->>> a101.headline -u'Article 101' - -# You can create saved objects in a single step ->>> a10 = Article.objects.create(headline="Article 10", pub_date=datetime(2005, 7, 31, 12, 30, 45)) ->>> Article.objects.get(headline="Article 10") - - -# Edge-case test: A year lookup should retrieve all objects in the given -year, including Jan. 1 and Dec. 31. ->>> a11 = Article.objects.create(headline='Article 11', pub_date=datetime(2008, 1, 1)) ->>> a12 = Article.objects.create(headline='Article 12', pub_date=datetime(2008, 12, 31, 23, 59, 59, 999999)) ->>> Article.objects.filter(pub_date__year=2008) -[, ] - -# Unicode data works, too. ->>> a = Article(headline=u'\u6797\u539f \u3081\u3050\u307f', pub_date=datetime(2005, 7, 28)) ->>> a.save() ->>> Article.objects.get(pk=a.id).headline -u'\u6797\u539f \u3081\u3050\u307f' - -# Model instances have a hash function, so they can be used in sets or as -# dictionary keys. Two models compare as equal if their primary keys are equal. ->>> s = set([a10, a11, a12]) ->>> Article.objects.get(headline='Article 11') in s -True - -# The 'select' argument to extra() supports names with dashes in them, as long -# as you use values(). ->>> dicts = Article.objects.filter(pub_date__year=2008).extra(select={'dashed-value': '1'}).values('headline', 'dashed-value') ->>> [sorted(d.items()) for d in dicts] -[[('dashed-value', 1), ('headline', u'Article 11')], [('dashed-value', 1), ('headline', u'Article 12')]] - -# If you use 'select' with extra() and names containing dashes on a query -# that's *not* a values() query, those extra 'select' values will silently be -# ignored. ->>> articles = Article.objects.filter(pub_date__year=2008).extra(select={'dashed-value': '1', 'undashedvalue': '2'}) ->>> articles[0].undashedvalue -2 -""" diff --git a/tests/modeltests/basic/tests.py b/tests/modeltests/basic/tests.py new file mode 100644 index 000000000000..bafe9a082035 --- /dev/null +++ b/tests/modeltests/basic/tests.py @@ -0,0 +1,562 @@ +from datetime import datetime +import re + +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist +from django.db import models, DEFAULT_DB_ALIAS, connection +from django.db.models.fields import FieldDoesNotExist +from django.test import TestCase + +from models import Article + + +class ModelTest(TestCase): + def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs): + self.assertRaises(error, callable, *args, **kwargs) + try: + callable(*args, **kwargs) + except error, e: + self.assertEqual(message, str(e)) + + def test_lookup(self): + # No articles are in the system yet. + self.assertQuerysetEqual(Article.objects.all(), []) + + # Create an Article. + a = Article( + id=None, + headline='Area man programs in Python', + pub_date=datetime(2005, 7, 28), + ) + + # Save it into the database. You have to call save() explicitly. + a.save() + + # Now it has an ID. + self.assertTrue(a.id != None) + + # Models have a pk property that is an alias for the primary key + # attribute (by default, the 'id' attribute). + self.assertEqual(a.pk, a.id) + + # Access database columns via Python attributes. + self.assertEqual(a.headline, 'Area man programs in Python') + self.assertEqual(a.pub_date, datetime(2005, 7, 28, 0, 0)) + + # Change values by changing the attributes, then calling save(). + a.headline = 'Area woman programs in Python' + a.save() + + # Article.objects.all() returns all the articles in the database. + self.assertQuerysetEqual(Article.objects.all(), + ['']) + + # Django provides a rich database lookup API. + self.assertEqual(Article.objects.get(id__exact=a.id), a) + self.assertEqual(Article.objects.get(headline__startswith='Area woman'), a) + self.assertEqual(Article.objects.get(pub_date__year=2005), a) + self.assertEqual(Article.objects.get(pub_date__year=2005, pub_date__month=7), a) + self.assertEqual(Article.objects.get(pub_date__year=2005, pub_date__month=7, pub_date__day=28), a) + self.assertEqual(Article.objects.get(pub_date__week_day=5), a) + + # The "__exact" lookup type can be omitted, as a shortcut. + self.assertEqual(Article.objects.get(id=a.id), a) + self.assertEqual(Article.objects.get(headline='Area woman programs in Python'), a) + + self.assertQuerysetEqual( + Article.objects.filter(pub_date__year=2005), + [''], + ) + self.assertQuerysetEqual( + Article.objects.filter(pub_date__year=2004), + [], + ) + self.assertQuerysetEqual( + Article.objects.filter(pub_date__year=2005, pub_date__month=7), + [''], + ) + + self.assertQuerysetEqual( + Article.objects.filter(pub_date__week_day=5), + [''], + ) + self.assertQuerysetEqual( + Article.objects.filter(pub_date__week_day=6), + [], + ) + + # Django raises an Article.DoesNotExist exception for get() if the + # parameters don't match any object. + self.assertRaisesErrorWithMessage( + ObjectDoesNotExist, + "Article matching query does not exist.", + Article.objects.get, + id__exact=2000, + ) + + self.assertRaisesErrorWithMessage( + ObjectDoesNotExist, + "Article matching query does not exist.", + Article.objects.get, + pub_date__year=2005, + pub_date__month=8, + ) + + self.assertRaisesErrorWithMessage( + ObjectDoesNotExist, + "Article matching query does not exist.", + Article.objects.get, + pub_date__week_day=6, + ) + + # Lookup by a primary key is the most common case, so Django + # provides a shortcut for primary-key exact lookups. + # The following is identical to articles.get(id=a.id). + self.assertEqual(Article.objects.get(pk=a.id), a) + + # pk can be used as a shortcut for the primary key name in any query. + self.assertQuerysetEqual(Article.objects.filter(pk__in=[a.id]), + [""]) + + # Model instances of the same type and same ID are considered equal. + a = Article.objects.get(pk=a.id) + b = Article.objects.get(pk=a.id) + self.assertEqual(a, b) + + def test_object_creation(self): + # Create an Article. + a = Article( + id=None, + headline='Area man programs in Python', + pub_date=datetime(2005, 7, 28), + ) + + # Save it into the database. You have to call save() explicitly. + a.save() + + # You can initialize a model instance using positional arguments, + # which should match the field order as defined in the model. + a2 = Article(None, 'Second article', datetime(2005, 7, 29)) + a2.save() + + self.assertNotEqual(a2.id, a.id) + self.assertEqual(a2.headline, 'Second article') + self.assertEqual(a2.pub_date, datetime(2005, 7, 29, 0, 0)) + + # ...or, you can use keyword arguments. + a3 = Article( + id=None, + headline='Third article', + pub_date=datetime(2005, 7, 30), + ) + a3.save() + + self.assertNotEqual(a3.id, a.id) + self.assertNotEqual(a3.id, a2.id) + self.assertEqual(a3.headline, 'Third article') + self.assertEqual(a3.pub_date, datetime(2005, 7, 30, 0, 0)) + + # You can also mix and match position and keyword arguments, but + # be sure not to duplicate field information. + a4 = Article(None, 'Fourth article', pub_date=datetime(2005, 7, 31)) + a4.save() + self.assertEqual(a4.headline, 'Fourth article') + + # Don't use invalid keyword arguments. + self.assertRaisesErrorWithMessage( + TypeError, + "'foo' is an invalid keyword argument for this function", + Article, + id=None, + headline='Invalid', + pub_date=datetime(2005, 7, 31), + foo='bar', + ) + + # You can leave off the value for an AutoField when creating an + # object, because it'll get filled in automatically when you save(). + a5 = Article(headline='Article 6', pub_date=datetime(2005, 7, 31)) + a5.save() + self.assertEqual(a5.headline, 'Article 6') + + # If you leave off a field with "default" set, Django will use + # the default. + a6 = Article(pub_date=datetime(2005, 7, 31)) + a6.save() + self.assertEqual(a6.headline, u'Default headline') + + # For DateTimeFields, Django saves as much precision (in seconds) + # as you give it. + a7 = Article( + headline='Article 7', + pub_date=datetime(2005, 7, 31, 12, 30), + ) + a7.save() + self.assertEqual(Article.objects.get(id__exact=a7.id).pub_date, + datetime(2005, 7, 31, 12, 30)) + + a8 = Article( + headline='Article 8', + pub_date=datetime(2005, 7, 31, 12, 30, 45), + ) + a8.save() + self.assertEqual(Article.objects.get(id__exact=a8.id).pub_date, + datetime(2005, 7, 31, 12, 30, 45)) + + # Saving an object again doesn't create a new object -- it just saves + # the old one. + current_id = a8.id + a8.save() + self.assertEqual(a8.id, current_id) + a8.headline = 'Updated article 8' + a8.save() + self.assertEqual(a8.id, current_id) + + # Check that != and == operators behave as expecte on instances + self.assertTrue(a7 != a8) + self.assertFalse(a7 == a8) + self.assertEqual(a8, Article.objects.get(id__exact=a8.id)) + + self.assertTrue(Article.objects.get(id__exact=a8.id) != Article.objects.get(id__exact=a7.id)) + self.assertFalse(Article.objects.get(id__exact=a8.id) == Article.objects.get(id__exact=a7.id)) + + # You can use 'in' to test for membership... + self.assertTrue(a8 in Article.objects.all()) + + # ... but there will often be more efficient ways if that is all you need: + self.assertTrue(Article.objects.filter(id=a8.id).exists()) + + # dates() returns a list of available dates of the given scope for + # the given field. + self.assertQuerysetEqual( + Article.objects.dates('pub_date', 'year'), + ["datetime.datetime(2005, 1, 1, 0, 0)"]) + self.assertQuerysetEqual( + Article.objects.dates('pub_date', 'month'), + ["datetime.datetime(2005, 7, 1, 0, 0)"]) + self.assertQuerysetEqual( + Article.objects.dates('pub_date', 'day'), + ["datetime.datetime(2005, 7, 28, 0, 0)", + "datetime.datetime(2005, 7, 29, 0, 0)", + "datetime.datetime(2005, 7, 30, 0, 0)", + "datetime.datetime(2005, 7, 31, 0, 0)"]) + self.assertQuerysetEqual( + Article.objects.dates('pub_date', 'day', order='ASC'), + ["datetime.datetime(2005, 7, 28, 0, 0)", + "datetime.datetime(2005, 7, 29, 0, 0)", + "datetime.datetime(2005, 7, 30, 0, 0)", + "datetime.datetime(2005, 7, 31, 0, 0)"]) + self.assertQuerysetEqual( + Article.objects.dates('pub_date', 'day', order='DESC'), + ["datetime.datetime(2005, 7, 31, 0, 0)", + "datetime.datetime(2005, 7, 30, 0, 0)", + "datetime.datetime(2005, 7, 29, 0, 0)", + "datetime.datetime(2005, 7, 28, 0, 0)"]) + + # dates() requires valid arguments. + self.assertRaisesErrorWithMessage( + TypeError, + "dates() takes at least 3 arguments (1 given)", + Article.objects.dates, + ) + + self.assertRaisesErrorWithMessage( + FieldDoesNotExist, + "Article has no field named 'invalid_field'", + Article.objects.dates, + "invalid_field", + "year", + ) + + self.assertRaisesErrorWithMessage( + AssertionError, + "'kind' must be one of 'year', 'month' or 'day'.", + Article.objects.dates, + "pub_date", + "bad_kind", + ) + + self.assertRaisesErrorWithMessage( + AssertionError, + "'order' must be either 'ASC' or 'DESC'.", + Article.objects.dates, + "pub_date", + "year", + order="bad order", + ) + + # Use iterator() with dates() to return a generator that lazily + # requests each result one at a time, to save memory. + dates = [] + for article in Article.objects.dates('pub_date', 'day', order='DESC').iterator(): + dates.append(article) + self.assertEqual(dates, [ + datetime(2005, 7, 31, 0, 0), + datetime(2005, 7, 30, 0, 0), + datetime(2005, 7, 29, 0, 0), + datetime(2005, 7, 28, 0, 0)]) + + # You can combine queries with & and |. + s1 = Article.objects.filter(id__exact=a.id) + s2 = Article.objects.filter(id__exact=a2.id) + self.assertQuerysetEqual(s1 | s2, + ["", + ""]) + self.assertQuerysetEqual(s1 & s2, []) + + # You can get the number of objects like this: + self.assertEqual(len(Article.objects.filter(id__exact=a.id)), 1) + + # You can get items using index and slice notation. + self.assertEqual(Article.objects.all()[0], a) + self.assertQuerysetEqual(Article.objects.all()[1:3], + ["", ""]) + + s3 = Article.objects.filter(id__exact=a3.id) + self.assertQuerysetEqual((s1 | s2 | s3)[::2], + ["", + ""]) + + # Slicing works with longs. + self.assertEqual(Article.objects.all()[0L], a) + self.assertQuerysetEqual(Article.objects.all()[1L:3L], + ["", ""]) + self.assertQuerysetEqual((s1 | s2 | s3)[::2L], + ["", + ""]) + + # And can be mixed with ints. + self.assertQuerysetEqual(Article.objects.all()[1:3L], + ["", ""]) + + # Slices (without step) are lazy: + self.assertQuerysetEqual(Article.objects.all()[0:5].filter(), + ["", + "", + "", + "", + ""]) + + # Slicing again works: + self.assertQuerysetEqual(Article.objects.all()[0:5][0:2], + ["", + ""]) + self.assertQuerysetEqual(Article.objects.all()[0:5][:2], + ["", + ""]) + self.assertQuerysetEqual(Article.objects.all()[0:5][4:], + [""]) + self.assertQuerysetEqual(Article.objects.all()[0:5][5:], []) + + # Some more tests! + self.assertQuerysetEqual(Article.objects.all()[2:][0:2], + ["", ""]) + self.assertQuerysetEqual(Article.objects.all()[2:][:2], + ["", ""]) + self.assertQuerysetEqual(Article.objects.all()[2:][2:3], + [""]) + + # Using an offset without a limit is also possible. + self.assertQuerysetEqual(Article.objects.all()[5:], + ["", + "", + ""]) + + # Also, once you have sliced you can't filter, re-order or combine + self.assertRaisesErrorWithMessage( + AssertionError, + "Cannot filter a query once a slice has been taken.", + Article.objects.all()[0:5].filter, + id=a.id, + ) + + self.assertRaisesErrorWithMessage( + AssertionError, + "Cannot reorder a query once a slice has been taken.", + Article.objects.all()[0:5].order_by, + 'id', + ) + + try: + Article.objects.all()[0:1] & Article.objects.all()[4:5] + self.fail('Should raise an AssertionError') + except AssertionError, e: + self.assertEqual(str(e), "Cannot combine queries once a slice has been taken.") + except Exception, e: + self.fail('Should raise an AssertionError, not %s' % e) + + # Negative slices are not supported, due to database constraints. + # (hint: inverting your ordering might do what you need). + try: + Article.objects.all()[-1] + self.fail('Should raise an AssertionError') + except AssertionError, e: + self.assertEqual(str(e), "Negative indexing is not supported.") + except Exception, e: + self.fail('Should raise an AssertionError, not %s' % e) + + error = None + try: + Article.objects.all()[0:-5] + except Exception, e: + error = e + self.assertTrue(isinstance(error, AssertionError)) + self.assertEqual(str(error), "Negative indexing is not supported.") + + # An Article instance doesn't have access to the "objects" attribute. + # That's only available on the class. + self.assertRaisesErrorWithMessage( + AttributeError, + "Manager isn't accessible via Article instances", + getattr, + a7, + "objects", + ) + + # Bulk delete test: How many objects before and after the delete? + self.assertQuerysetEqual(Article.objects.all(), + ["", + "", + "", + "", + "", + "", + "", + ""]) + Article.objects.filter(id__lte=a4.id).delete() + self.assertQuerysetEqual(Article.objects.all(), + ["", + "", + "", + ""]) + + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].startswith('django.db.backends.postgresql'): + def test_microsecond_precision(self): + # In PostgreSQL, microsecond-level precision is available. + a9 = Article( + headline='Article 9', + pub_date=datetime(2005, 7, 31, 12, 30, 45, 180), + ) + a9.save() + self.assertEqual(Article.objects.get(pk=a9.pk).pub_date, + datetime(2005, 7, 31, 12, 30, 45, 180)) + + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.mysql': + def test_microsecond_precision_not_supported(self): + # In MySQL, microsecond-level precision isn't available. You'll lose + # microsecond-level precision once the data is saved. + a9 = Article( + headline='Article 9', + pub_date=datetime(2005, 7, 31, 12, 30, 45, 180), + ) + a9.save() + self.assertEqual(Article.objects.get(id__exact=a9.id).pub_date, + datetime(2005, 7, 31, 12, 30, 45)) + + def test_manually_specify_primary_key(self): + # You can manually specify the primary key when creating a new object. + a101 = Article( + id=101, + headline='Article 101', + pub_date=datetime(2005, 7, 31, 12, 30, 45), + ) + a101.save() + a101 = Article.objects.get(pk=101) + self.assertEqual(a101.headline, u'Article 101') + + def test_create_method(self): + # You can create saved objects in a single step + a10 = Article.objects.create( + headline="Article 10", + pub_date=datetime(2005, 7, 31, 12, 30, 45), + ) + self.assertEqual(Article.objects.get(headline="Article 10"), a10) + + def test_year_lookup_edge_case(self): + # Edge-case test: A year lookup should retrieve all objects in + # the given year, including Jan. 1 and Dec. 31. + a11 = Article.objects.create( + headline='Article 11', + pub_date=datetime(2008, 1, 1), + ) + a12 = Article.objects.create( + headline='Article 12', + pub_date=datetime(2008, 12, 31, 23, 59, 59, 999999), + ) + self.assertQuerysetEqual(Article.objects.filter(pub_date__year=2008), + ["", ""]) + + def test_unicode_data(self): + # Unicode data works, too. + a = Article( + headline=u'\u6797\u539f \u3081\u3050\u307f', + pub_date=datetime(2005, 7, 28), + ) + a.save() + self.assertEqual(Article.objects.get(pk=a.id).headline, + u'\u6797\u539f \u3081\u3050\u307f') + + def test_hash_function(self): + # Model instances have a hash function, so they can be used in sets + # or as dictionary keys. Two models compare as equal if their primary + # keys are equal. + a10 = Article.objects.create( + headline="Article 10", + pub_date=datetime(2005, 7, 31, 12, 30, 45), + ) + a11 = Article.objects.create( + headline='Article 11', + pub_date=datetime(2008, 1, 1), + ) + a12 = Article.objects.create( + headline='Article 12', + pub_date=datetime(2008, 12, 31, 23, 59, 59, 999999), + ) + + s = set([a10, a11, a12]) + self.assertTrue(Article.objects.get(headline='Article 11') in s) + + def test_extra_method_select_argument_with_dashes_and_values(self): + # The 'select' argument to extra() supports names with dashes in + # them, as long as you use values(). + a10 = Article.objects.create( + headline="Article 10", + pub_date=datetime(2005, 7, 31, 12, 30, 45), + ) + a11 = Article.objects.create( + headline='Article 11', + pub_date=datetime(2008, 1, 1), + ) + a12 = Article.objects.create( + headline='Article 12', + pub_date=datetime(2008, 12, 31, 23, 59, 59, 999999), + ) + + dicts = Article.objects.filter( + pub_date__year=2008).extra( + select={'dashed-value': '1'} + ).values('headline', 'dashed-value') + self.assertEqual([sorted(d.items()) for d in dicts], + [[('dashed-value', 1), ('headline', u'Article 11')], [('dashed-value', 1), ('headline', u'Article 12')]]) + + def test_extra_method_select_argument_with_dashes(self): + # If you use 'select' with extra() and names containing dashes on a + # query that's *not* a values() query, those extra 'select' values + # will silently be ignored. + a10 = Article.objects.create( + headline="Article 10", + pub_date=datetime(2005, 7, 31, 12, 30, 45), + ) + a11 = Article.objects.create( + headline='Article 11', + pub_date=datetime(2008, 1, 1), + ) + a12 = Article.objects.create( + headline='Article 12', + pub_date=datetime(2008, 12, 31, 23, 59, 59, 999999), + ) + + articles = Article.objects.filter( + pub_date__year=2008).extra( + select={'dashed-value': '1', 'undashedvalue': '2'}) + self.assertEqual(articles[0].undashedvalue, 2) From 09ccdde265ad91aa69d7446c6771567360b09bbe Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 2 Nov 2010 04:38:29 +0000 Subject: [PATCH 402/902] [1.2.X] Migrated lookup doctests. Thanks to George Sakkis for the patch. Backport of r14423 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14424 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/lookup/models.py | 403 +--------------------- tests/modeltests/lookup/tests.py | 547 ++++++++++++++++++++++++++++++ 2 files changed, 548 insertions(+), 402 deletions(-) create mode 100644 tests/modeltests/lookup/tests.py diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 72b547a37682..99eec51aa475 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -4,8 +4,7 @@ This demonstrates features of the database API. """ -from django.db import models, DEFAULT_DB_ALIAS -from django.conf import settings +from django.db import models class Article(models.Model): headline = models.CharField(max_length=100) @@ -15,403 +14,3 @@ class Meta: def __unicode__(self): return self.headline - -__test__ = {'API_TESTS': r""" -# We can use .exists() to check that there are none yet ->>> Article.objects.exists() -False - -# Create a couple of Articles. ->>> from datetime import datetime ->>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26)) ->>> a1.save() ->>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27)) ->>> a2.save() ->>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27)) ->>> a3.save() ->>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28)) ->>> a4.save() ->>> a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0)) ->>> a5.save() ->>> a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0)) ->>> a6.save() ->>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27)) ->>> a7.save() - -# There should be some now! ->>> Article.objects.exists() -True -"""} - -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in ( - 'django.db.backends.postgresql', - 'django.db.backends.postgresql_pysycopg2'): - __test__['API_TESTS'] += r""" -# text matching tests for PostgreSQL 8.3 ->>> Article.objects.filter(id__iexact='1') -[] ->>> Article.objects.filter(pub_date__startswith='2005') -[, , , , , , ] -""" - -__test__['API_TESTS'] += r""" -# Each QuerySet gets iterator(), which is a generator that "lazily" returns -# results using database-level iteration. ->>> for a in Article.objects.iterator(): -... print a.headline -Article 5 -Article 6 -Article 4 -Article 2 -Article 3 -Article 7 -Article 1 - -# iterator() can be used on any QuerySet. ->>> for a in Article.objects.filter(headline__endswith='4').iterator(): -... print a.headline -Article 4 - -# count() returns the number of objects matching search criteria. ->>> Article.objects.count() -7L ->>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).count() -3L ->>> Article.objects.filter(headline__startswith='Blah blah').count() -0L - -# count() should respect sliced query sets. ->>> articles = Article.objects.all() ->>> articles.count() -7L ->>> articles[:4].count() -4 ->>> articles[1:100].count() -6L ->>> articles[10:100].count() -0 - -# Date and date/time lookups can also be done with strings. ->>> Article.objects.filter(pub_date__exact='2005-07-27 00:00:00').count() -3L - -# in_bulk() takes a list of IDs and returns a dictionary mapping IDs -# to objects. ->>> arts = Article.objects.in_bulk([1, 2]) ->>> arts[1] - ->>> arts[2] - ->>> Article.objects.in_bulk([3]) -{3: } ->>> Article.objects.in_bulk(set([3])) -{3: } ->>> Article.objects.in_bulk(frozenset([3])) -{3: } ->>> Article.objects.in_bulk((3,)) -{3: } ->>> Article.objects.in_bulk([1000]) -{} ->>> Article.objects.in_bulk([]) -{} ->>> Article.objects.in_bulk('foo') -Traceback (most recent call last): - ... -AssertionError: in_bulk() must be provided with a list of IDs. ->>> Article.objects.in_bulk() -Traceback (most recent call last): - ... -TypeError: in_bulk() takes exactly 2 arguments (1 given) ->>> Article.objects.in_bulk(headline__startswith='Blah') -Traceback (most recent call last): - ... -TypeError: in_bulk() got an unexpected keyword argument 'headline__startswith' - -# values() returns a list of dictionaries instead of object instances -- and -# you can specify which fields you want to retrieve. ->>> Article.objects.values('headline') -[{'headline': u'Article 5'}, {'headline': u'Article 6'}, {'headline': u'Article 4'}, {'headline': u'Article 2'}, {'headline': u'Article 3'}, {'headline': u'Article 7'}, {'headline': u'Article 1'}] ->>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values('id') -[{'id': 2}, {'id': 3}, {'id': 7}] ->>> list(Article.objects.values('id', 'headline')) == [{'id': 5, 'headline': 'Article 5'}, {'id': 6, 'headline': 'Article 6'}, {'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 7, 'headline': 'Article 7'}, {'id': 1, 'headline': 'Article 1'}] -True - ->>> for d in Article.objects.values('id', 'headline'): -... i = d.items() -... i.sort() -... i -[('headline', u'Article 5'), ('id', 5)] -[('headline', u'Article 6'), ('id', 6)] -[('headline', u'Article 4'), ('id', 4)] -[('headline', u'Article 2'), ('id', 2)] -[('headline', u'Article 3'), ('id', 3)] -[('headline', u'Article 7'), ('id', 7)] -[('headline', u'Article 1'), ('id', 1)] - -# You can use values() with iterator() for memory savings, because iterator() -# uses database-level iteration. ->>> for d in Article.objects.values('id', 'headline').iterator(): -... i = d.items() -... i.sort() -... i -[('headline', u'Article 5'), ('id', 5)] -[('headline', u'Article 6'), ('id', 6)] -[('headline', u'Article 4'), ('id', 4)] -[('headline', u'Article 2'), ('id', 2)] -[('headline', u'Article 3'), ('id', 3)] -[('headline', u'Article 7'), ('id', 7)] -[('headline', u'Article 1'), ('id', 1)] - -# The values() method works with "extra" fields specified in extra(select). ->>> for d in Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id', 'id_plus_one'): -... i = d.items() -... i.sort() -... i -[('id', 5), ('id_plus_one', 6)] -[('id', 6), ('id_plus_one', 7)] -[('id', 4), ('id_plus_one', 5)] -[('id', 2), ('id_plus_one', 3)] -[('id', 3), ('id_plus_one', 4)] -[('id', 7), ('id_plus_one', 8)] -[('id', 1), ('id_plus_one', 2)] ->>> data = {'id_plus_one': 'id+1', 'id_plus_two': 'id+2', 'id_plus_three': 'id+3', -... 'id_plus_four': 'id+4', 'id_plus_five': 'id+5', 'id_plus_six': 'id+6', -... 'id_plus_seven': 'id+7', 'id_plus_eight': 'id+8'} ->>> result = list(Article.objects.filter(id=1).extra(select=data).values(*data.keys()))[0] ->>> result = result.items() ->>> result.sort() ->>> result -[('id_plus_eight', 9), ('id_plus_five', 6), ('id_plus_four', 5), ('id_plus_one', 2), ('id_plus_seven', 8), ('id_plus_six', 7), ('id_plus_three', 4), ('id_plus_two', 3)] - -# However, an exception FieldDoesNotExist will be thrown if you specify a -# non-existent field name in values() (a field that is neither in the model -# nor in extra(select)). ->>> Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id', 'id_plus_two') -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'id_plus_two' into field. Choices are: headline, id, id_plus_one, pub_date - -# If you don't specify field names to values(), all are returned. ->>> list(Article.objects.filter(id=5).values()) == [{'id': 5, 'headline': 'Article 5', 'pub_date': datetime(2005, 8, 1, 9, 0)}] -True - -# values_list() is similar to values(), except that the results are returned as -# a list of tuples, rather than a list of dictionaries. Within each tuple, the -# order of the elemnts is the same as the order of fields in the values_list() -# call. ->>> Article.objects.values_list('headline') -[(u'Article 5',), (u'Article 6',), (u'Article 4',), (u'Article 2',), (u'Article 3',), (u'Article 7',), (u'Article 1',)] - ->>> Article.objects.values_list('id').order_by('id') -[(1,), (2,), (3,), (4,), (5,), (6,), (7,)] ->>> Article.objects.values_list('id', flat=True).order_by('id') -[1, 2, 3, 4, 5, 6, 7] - ->>> Article.objects.extra(select={'id_plus_one': 'id+1'}).order_by('id').values_list('id') -[(1,), (2,), (3,), (4,), (5,), (6,), (7,)] ->>> Article.objects.extra(select={'id_plus_one': 'id+1'}).order_by('id').values_list('id_plus_one', 'id') -[(2, 1), (3, 2), (4, 3), (5, 4), (6, 5), (7, 6), (8, 7)] ->>> Article.objects.extra(select={'id_plus_one': 'id+1'}).order_by('id').values_list('id', 'id_plus_one') -[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8)] - ->>> Article.objects.values_list('id', 'headline', flat=True) -Traceback (most recent call last): -... -TypeError: 'flat' is not valid when values_list is called with more than one field. - -# Every DateField and DateTimeField creates get_next_by_FOO() and -# get_previous_by_FOO() methods. -# In the case of identical date values, these methods will use the ID as a -# fallback check. This guarantees that no records are skipped or duplicated. ->>> a1.get_next_by_pub_date() - ->>> a2.get_next_by_pub_date() - ->>> a2.get_next_by_pub_date(headline__endswith='6') - ->>> a3.get_next_by_pub_date() - ->>> a4.get_next_by_pub_date() - ->>> a5.get_next_by_pub_date() -Traceback (most recent call last): - ... -DoesNotExist: Article matching query does not exist. ->>> a6.get_next_by_pub_date() - ->>> a7.get_next_by_pub_date() - - ->>> a7.get_previous_by_pub_date() - ->>> a6.get_previous_by_pub_date() - ->>> a5.get_previous_by_pub_date() - ->>> a4.get_previous_by_pub_date() - ->>> a3.get_previous_by_pub_date() - ->>> a2.get_previous_by_pub_date() - - -# Underscores and percent signs have special meaning in the underlying -# SQL code, but Django handles the quoting of them automatically. ->>> a8 = Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20)) ->>> a8.save() ->>> Article.objects.filter(headline__startswith='Article') -[, , , , , , , ] ->>> Article.objects.filter(headline__startswith='Article_') -[] - ->>> a9 = Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21)) ->>> a9.save() ->>> Article.objects.filter(headline__startswith='Article') -[, , , , , , , , ] ->>> Article.objects.filter(headline__startswith='Article%') -[] - -# exclude() is the opposite of filter() when doing lookups: ->>> Article.objects.filter(headline__contains='Article').exclude(headline__contains='with') -[, , , , , , ] ->>> Article.objects.exclude(headline__startswith="Article_") -[, , , , , , , ] ->>> Article.objects.exclude(headline="Article 7") -[, , , , , , , ] - -# Backslashes also have special meaning in the underlying SQL code, but Django -# automatically quotes them appropriately. ->>> a10 = Article(headline='Article with \\ backslash', pub_date=datetime(2005, 11, 22)) ->>> a10.save() ->>> Article.objects.filter(headline__contains='\\') -[] - -# none() returns an EmptyQuerySet that behaves like any other QuerySet object ->>> Article.objects.none() -[] ->>> Article.objects.none().filter(headline__startswith='Article') -[] ->>> Article.objects.filter(headline__startswith='Article').none() -[] ->>> Article.objects.none().count() -0 ->>> Article.objects.none().update(headline="This should not take effect") -0 ->>> [article for article in Article.objects.none().iterator()] -[] - -# using __in with an empty list should return an empty query set ->>> Article.objects.filter(id__in=[]) -[] - ->>> Article.objects.exclude(id__in=[]) -[, , , , , , , , , ] - -# Programming errors are pointed out with nice error messages ->>> Article.objects.filter(pub_date_year='2005').count() -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'pub_date_year' into field. Choices are: headline, id, pub_date - ->>> Article.objects.filter(headline__starts='Article') -Traceback (most recent call last): - ... -FieldError: Join on field 'headline' not permitted. Did you misspell 'starts' for the lookup type? - -# Create some articles with a bit more interesting headlines for testing field lookups: ->>> now = datetime.now() ->>> for a in Article.objects.all(): -... a.delete() ->>> a1 = Article(pub_date=now, headline='f') ->>> a1.save() ->>> a2 = Article(pub_date=now, headline='fo') ->>> a2.save() ->>> a3 = Article(pub_date=now, headline='foo') ->>> a3.save() ->>> a4 = Article(pub_date=now, headline='fooo') ->>> a4.save() ->>> a5 = Article(pub_date=now, headline='hey-Foo') ->>> a5.save() - -# zero-or-more ->>> Article.objects.filter(headline__regex=r'fo*') -[, , , ] ->>> Article.objects.filter(headline__iregex=r'fo*') -[, , , , ] - -# one-or-more ->>> Article.objects.filter(headline__regex=r'fo+') -[, , ] - -# wildcard ->>> Article.objects.filter(headline__regex=r'fooo?') -[, ] - -# and some more: ->>> a6 = Article(pub_date=now, headline='bar') ->>> a6.save() ->>> a7 = Article(pub_date=now, headline='AbBa') ->>> a7.save() ->>> a8 = Article(pub_date=now, headline='baz') ->>> a8.save() ->>> a9 = Article(pub_date=now, headline='baxZ') ->>> a9.save() - -# leading anchor ->>> Article.objects.filter(headline__regex=r'^b') -[, , ] ->>> Article.objects.filter(headline__iregex=r'^a') -[] - -# trailing anchor ->>> Article.objects.filter(headline__regex=r'z$') -[] ->>> Article.objects.filter(headline__iregex=r'z$') -[, ] - -# character sets ->>> Article.objects.filter(headline__regex=r'ba[rz]') -[, ] ->>> Article.objects.filter(headline__regex=r'ba.[RxZ]') -[] ->>> Article.objects.filter(headline__iregex=r'ba[RxZ]') -[, , ] - -# and yet more: ->>> a10 = Article(pub_date=now, headline='foobar') ->>> a10.save() ->>> a11 = Article(pub_date=now, headline='foobaz') ->>> a11.save() ->>> a12 = Article(pub_date=now, headline='ooF') ->>> a12.save() ->>> a13 = Article(pub_date=now, headline='foobarbaz') ->>> a13.save() ->>> a14 = Article(pub_date=now, headline='zoocarfaz') ->>> a14.save() ->>> a15 = Article(pub_date=now, headline='barfoobaz') ->>> a15.save() ->>> a16 = Article(pub_date=now, headline='bazbaRFOO') ->>> a16.save() - -# alternation ->>> Article.objects.filter(headline__regex=r'oo(f|b)') -[, , , ] ->>> Article.objects.filter(headline__iregex=r'oo(f|b)') -[, , , , ] ->>> Article.objects.filter(headline__regex=r'^foo(f|b)') -[, , ] - -# greedy matching ->>> Article.objects.filter(headline__regex=r'b.*az') -[, , , , ] ->>> Article.objects.filter(headline__iregex=r'b.*ar') -[, , , , ] -""" - - -if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql': - __test__['API_TESTS'] += r""" -# grouping and backreferences ->>> Article.objects.filter(headline__regex=r'b(.).*b\1') -[, , ] -""" diff --git a/tests/modeltests/lookup/tests.py b/tests/modeltests/lookup/tests.py new file mode 100644 index 000000000000..9e0b68e8d548 --- /dev/null +++ b/tests/modeltests/lookup/tests.py @@ -0,0 +1,547 @@ +from datetime import datetime +from operator import attrgetter + +from django.conf import settings +from django.core.exceptions import FieldError +from django.db import connection, DEFAULT_DB_ALIAS +from django.test import TestCase + +from models import Article + +class LookupTests(TestCase): + + #def setUp(self): + def setUp(self): + # Create a couple of Articles. + self.a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26)) + self.a1.save() + self.a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27)) + self.a2.save() + self.a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27)) + self.a3.save() + self.a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28)) + self.a4.save() + self.a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0)) + self.a5.save() + self.a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0)) + self.a6.save() + self.a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27)) + self.a7.save() + + def test_exists(self): + # We can use .exists() to check that there are some + self.assertTrue(Article.objects.exists()) + for a in Article.objects.all(): + a.delete() + # There should be none now! + self.assertFalse(Article.objects.exists()) + + def test_lookup_int_as_str(self): + # Integer value can be queried using string + self.assertQuerysetEqual(Article.objects.filter(id__iexact=str(self.a1.id)), + ['']) + + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in ( + 'django.db.backends.postgresql', + 'django.db.backends.postgresql_psycopg2'): + def test_lookup_date_as_str(self): + # A date lookup can be performed using a string search + self.assertQuerysetEqual(Article.objects.filter(pub_date__startswith='2005'), + [ + '', + '', + '', + '', + '', + '', + '', + ]) + + def test_iterator(self): + # Each QuerySet gets iterator(), which is a generator that "lazily" + # returns results using database-level iteration. + self.assertQuerysetEqual(Article.objects.iterator(), + [ + 'Article 5', + 'Article 6', + 'Article 4', + 'Article 2', + 'Article 3', + 'Article 7', + 'Article 1', + ], + transform=attrgetter('headline')) + # iterator() can be used on any QuerySet. + self.assertQuerysetEqual( + Article.objects.filter(headline__endswith='4').iterator(), + ['Article 4'], + transform=attrgetter('headline')) + + def test_count(self): + # count() returns the number of objects matching search criteria. + self.assertEqual(Article.objects.count(), 7) + self.assertEqual(Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).count(), 3) + self.assertEqual(Article.objects.filter(headline__startswith='Blah blah').count(), 0) + + # count() should respect sliced query sets. + articles = Article.objects.all() + self.assertEqual(articles.count(), 7) + self.assertEqual(articles[:4].count(), 4) + self.assertEqual(articles[1:100].count(), 6) + self.assertEqual(articles[10:100].count(), 0) + + # Date and date/time lookups can also be done with strings. + self.assertEqual(Article.objects.filter(pub_date__exact='2005-07-27 00:00:00').count(), 3) + + def test_in_bulk(self): + # in_bulk() takes a list of IDs and returns a dictionary mapping IDs to objects. + arts = Article.objects.in_bulk([self.a1.id, self.a2.id]) + self.assertEqual(arts[self.a1.id], self.a1) + self.assertEqual(arts[self.a2.id], self.a2) + self.assertEqual(Article.objects.in_bulk([self.a3.id]), {self.a3.id: self.a3}) + self.assertEqual(Article.objects.in_bulk(set([self.a3.id])), {self.a3.id: self.a3}) + self.assertEqual(Article.objects.in_bulk(frozenset([self.a3.id])), {self.a3.id: self.a3}) + self.assertEqual(Article.objects.in_bulk((self.a3.id,)), {self.a3.id: self.a3}) + self.assertEqual(Article.objects.in_bulk([1000]), {}) + self.assertEqual(Article.objects.in_bulk([]), {}) + self.assertRaises(AssertionError, Article.objects.in_bulk, 'foo') + self.assertRaises(TypeError, Article.objects.in_bulk) + self.assertRaises(TypeError, Article.objects.in_bulk, headline__startswith='Blah') + + def test_values(self): + # values() returns a list of dictionaries instead of object instances -- + # and you can specify which fields you want to retrieve. + identity = lambda x:x + self.assertQuerysetEqual(Article.objects.values('headline'), + [ + {'headline': u'Article 5'}, + {'headline': u'Article 6'}, + {'headline': u'Article 4'}, + {'headline': u'Article 2'}, + {'headline': u'Article 3'}, + {'headline': u'Article 7'}, + {'headline': u'Article 1'}, + ], + transform=identity) + self.assertQuerysetEqual( + Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values('id'), + [{'id': self.a2.id}, {'id': self.a3.id}, {'id': self.a7.id}], + transform=identity) + self.assertQuerysetEqual(Article.objects.values('id', 'headline'), + [ + {'id': self.a5.id, 'headline': 'Article 5'}, + {'id': self.a6.id, 'headline': 'Article 6'}, + {'id': self.a4.id, 'headline': 'Article 4'}, + {'id': self.a2.id, 'headline': 'Article 2'}, + {'id': self.a3.id, 'headline': 'Article 3'}, + {'id': self.a7.id, 'headline': 'Article 7'}, + {'id': self.a1.id, 'headline': 'Article 1'}, + ], + transform=identity) + # You can use values() with iterator() for memory savings, + # because iterator() uses database-level iteration. + self.assertQuerysetEqual(Article.objects.values('id', 'headline').iterator(), + [ + {'headline': u'Article 5', 'id': self.a5.id}, + {'headline': u'Article 6', 'id': self.a6.id}, + {'headline': u'Article 4', 'id': self.a4.id}, + {'headline': u'Article 2', 'id': self.a2.id}, + {'headline': u'Article 3', 'id': self.a3.id}, + {'headline': u'Article 7', 'id': self.a7.id}, + {'headline': u'Article 1', 'id': self.a1.id}, + ], + transform=identity) + # The values() method works with "extra" fields specified in extra(select). + self.assertQuerysetEqual( + Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id', 'id_plus_one'), + [ + {'id': self.a5.id, 'id_plus_one': self.a5.id + 1}, + {'id': self.a6.id, 'id_plus_one': self.a6.id + 1}, + {'id': self.a4.id, 'id_plus_one': self.a4.id + 1}, + {'id': self.a2.id, 'id_plus_one': self.a2.id + 1}, + {'id': self.a3.id, 'id_plus_one': self.a3.id + 1}, + {'id': self.a7.id, 'id_plus_one': self.a7.id + 1}, + {'id': self.a1.id, 'id_plus_one': self.a1.id + 1}, + ], + transform=identity) + data = { + 'id_plus_one': 'id+1', + 'id_plus_two': 'id+2', + 'id_plus_three': 'id+3', + 'id_plus_four': 'id+4', + 'id_plus_five': 'id+5', + 'id_plus_six': 'id+6', + 'id_plus_seven': 'id+7', + 'id_plus_eight': 'id+8', + } + self.assertQuerysetEqual( + Article.objects.filter(id=self.a1.id).extra(select=data).values(*data.keys()), + [{ + 'id_plus_one': self.a1.id + 1, + 'id_plus_two': self.a1.id + 2, + 'id_plus_three': self.a1.id + 3, + 'id_plus_four': self.a1.id + 4, + 'id_plus_five': self.a1.id + 5, + 'id_plus_six': self.a1.id + 6, + 'id_plus_seven': self.a1.id + 7, + 'id_plus_eight': self.a1.id + 8, + }], transform=identity) + # However, an exception FieldDoesNotExist will be thrown if you specify + # a non-existent field name in values() (a field that is neither in the + # model nor in extra(select)). + self.assertRaises(FieldError, + Article.objects.extra(select={'id_plus_one': 'id + 1'}).values, + 'id', 'id_plus_two') + # If you don't specify field names to values(), all are returned. + self.assertQuerysetEqual(Article.objects.filter(id=self.a5.id).values(), + [{ + 'id': self.a5.id, + 'headline': 'Article 5', + 'pub_date': datetime(2005, 8, 1, 9, 0) + }], transform=identity) + + def test_values_list(self): + # values_list() is similar to values(), except that the results are + # returned as a list of tuples, rather than a list of dictionaries. + # Within each tuple, the order of the elemnts is the same as the order + # of fields in the values_list() call. + identity = lambda x:x + self.assertQuerysetEqual(Article.objects.values_list('headline'), + [ + (u'Article 5',), + (u'Article 6',), + (u'Article 4',), + (u'Article 2',), + (u'Article 3',), + (u'Article 7',), + (u'Article 1',), + ], transform=identity) + self.assertQuerysetEqual(Article.objects.values_list('id').order_by('id'), + [(self.a1.id,), (self.a2.id,), (self.a3.id,), (self.a4.id,), (self.a5.id,), (self.a6.id,), (self.a7.id,)], + transform=identity) + self.assertQuerysetEqual( + Article.objects.values_list('id', flat=True).order_by('id'), + [self.a1.id, self.a2.id, self.a3.id, self.a4.id, self.a5.id, self.a6.id, self.a7.id], + transform=identity) + self.assertQuerysetEqual( + Article.objects.extra(select={'id_plus_one': 'id+1'}) + .order_by('id').values_list('id'), + [(self.a1.id,), (self.a2.id,), (self.a3.id,), (self.a4.id,), (self.a5.id,), (self.a6.id,), (self.a7.id,)], + transform=identity) + self.assertQuerysetEqual( + Article.objects.extra(select={'id_plus_one': 'id+1'}) + .order_by('id').values_list('id_plus_one', 'id'), + [ + (self.a1.id+1, self.a1.id), + (self.a2.id+1, self.a2.id), + (self.a3.id+1, self.a3.id), + (self.a4.id+1, self.a4.id), + (self.a5.id+1, self.a5.id), + (self.a6.id+1, self.a6.id), + (self.a7.id+1, self.a7.id) + ], + transform=identity) + self.assertQuerysetEqual( + Article.objects.extra(select={'id_plus_one': 'id+1'}) + .order_by('id').values_list('id', 'id_plus_one'), + [ + (self.a1.id, self.a1.id+1), + (self.a2.id, self.a2.id+1), + (self.a3.id, self.a3.id+1), + (self.a4.id, self.a4.id+1), + (self.a5.id, self.a5.id+1), + (self.a6.id, self.a6.id+1), + (self.a7.id, self.a7.id+1) + ], + transform=identity) + self.assertRaises(TypeError, Article.objects.values_list, 'id', 'headline', flat=True) + + def test_get_next_previous_by(self): + # Every DateField and DateTimeField creates get_next_by_FOO() and + # get_previous_by_FOO() methods. In the case of identical date values, + # these methods will use the ID as a fallback check. This guarantees + # that no records are skipped or duplicated. + self.assertEqual(repr(self.a1.get_next_by_pub_date()), + '') + self.assertEqual(repr(self.a2.get_next_by_pub_date()), + '') + self.assertEqual(repr(self.a2.get_next_by_pub_date(headline__endswith='6')), + '') + self.assertEqual(repr(self.a3.get_next_by_pub_date()), + '') + self.assertEqual(repr(self.a4.get_next_by_pub_date()), + '') + self.assertRaises(Article.DoesNotExist, self.a5.get_next_by_pub_date) + self.assertEqual(repr(self.a6.get_next_by_pub_date()), + '') + self.assertEqual(repr(self.a7.get_next_by_pub_date()), + '') + + self.assertEqual(repr(self.a7.get_previous_by_pub_date()), + '') + self.assertEqual(repr(self.a6.get_previous_by_pub_date()), + '') + self.assertEqual(repr(self.a5.get_previous_by_pub_date()), + '') + self.assertEqual(repr(self.a4.get_previous_by_pub_date()), + '') + self.assertEqual(repr(self.a3.get_previous_by_pub_date()), + '') + self.assertEqual(repr(self.a2.get_previous_by_pub_date()), + '') + + def test_escaping(self): + # Underscores, percent signs and backslashes have special meaning in the + # underlying SQL code, but Django handles the quoting of them automatically. + a8 = Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20)) + a8.save() + self.assertQuerysetEqual(Article.objects.filter(headline__startswith='Article'), + [ + '', + '', + '', + '', + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(Article.objects.filter(headline__startswith='Article_'), + ['']) + a9 = Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21)) + a9.save() + self.assertQuerysetEqual(Article.objects.filter(headline__startswith='Article'), + [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(Article.objects.filter(headline__startswith='Article%'), + ['']) + a10 = Article(headline='Article with \\ backslash', pub_date=datetime(2005, 11, 22)) + a10.save() + self.assertQuerysetEqual(Article.objects.filter(headline__contains='\\'), + ['']) + + def test_exclude(self): + a8 = Article.objects.create(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20)) + a9 = Article.objects.create(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21)) + a10 = Article.objects.create(headline='Article with \\ backslash', pub_date=datetime(2005, 11, 22)) + + # exclude() is the opposite of filter() when doing lookups: + self.assertQuerysetEqual( + Article.objects.filter(headline__contains='Article').exclude(headline__contains='with'), + [ + '', + '', + '', + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(Article.objects.exclude(headline__startswith="Article_"), + [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(Article.objects.exclude(headline="Article 7"), + [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + ]) + + def test_none(self): + # none() returns an EmptyQuerySet that behaves like any other QuerySet object + self.assertQuerysetEqual(Article.objects.none(), []) + self.assertQuerysetEqual( + Article.objects.none().filter(headline__startswith='Article'), []) + self.assertQuerysetEqual( + Article.objects.filter(headline__startswith='Article').none(), []) + self.assertEqual(Article.objects.none().count(), 0) + self.assertEqual( + Article.objects.none().update(headline="This should not take effect"), 0) + self.assertQuerysetEqual( + [article for article in Article.objects.none().iterator()], + []) + + def test_in(self): + # using __in with an empty list should return an empty query set + self.assertQuerysetEqual(Article.objects.filter(id__in=[]), []) + self.assertQuerysetEqual(Article.objects.exclude(id__in=[]), + [ + '', + '', + '', + '', + '', + '', + '', + ]) + + def test_error_messages(self): + # Programming errors are pointed out with nice error messages + try: + Article.objects.filter(pub_date_year='2005').count() + self.fail('FieldError not raised') + except FieldError, ex: + self.assertEqual(str(ex), "Cannot resolve keyword 'pub_date_year' " + "into field. Choices are: headline, id, pub_date") + try: + Article.objects.filter(headline__starts='Article') + self.fail('FieldError not raised') + except FieldError, ex: + self.assertEqual(str(ex), "Join on field 'headline' not permitted. " + "Did you misspell 'starts' for the lookup type?") + + def test_regex(self): + # Create some articles with a bit more interesting headlines for testing field lookups: + for a in Article.objects.all(): + a.delete() + now = datetime.now() + a1 = Article(pub_date=now, headline='f') + a1.save() + a2 = Article(pub_date=now, headline='fo') + a2.save() + a3 = Article(pub_date=now, headline='foo') + a3.save() + a4 = Article(pub_date=now, headline='fooo') + a4.save() + a5 = Article(pub_date=now, headline='hey-Foo') + a5.save() + a6 = Article(pub_date=now, headline='bar') + a6.save() + a7 = Article(pub_date=now, headline='AbBa') + a7.save() + a8 = Article(pub_date=now, headline='baz') + a8.save() + a9 = Article(pub_date=now, headline='baxZ') + a9.save() + # zero-or-more + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'fo*'), + ['', '', '', '']) + self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'fo*'), + [ + '', + '', + '', + '', + '', + ]) + # one-or-more + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'fo+'), + ['', '', '']) + # wildcard + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'fooo?'), + ['', '']) + # leading anchor + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'^b'), + ['', '', '']) + self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'^a'), + ['']) + # trailing anchor + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'z$'), + ['']) + self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'z$'), + ['', '']) + # character sets + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'ba[rz]'), + ['', '']) + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'ba.[RxZ]'), + ['']) + self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'ba[RxZ]'), + ['', '', '']) + + # and more articles: + a10 = Article(pub_date=now, headline='foobar') + a10.save() + a11 = Article(pub_date=now, headline='foobaz') + a11.save() + a12 = Article(pub_date=now, headline='ooF') + a12.save() + a13 = Article(pub_date=now, headline='foobarbaz') + a13.save() + a14 = Article(pub_date=now, headline='zoocarfaz') + a14.save() + a15 = Article(pub_date=now, headline='barfoobaz') + a15.save() + a16 = Article(pub_date=now, headline='bazbaRFOO') + a16.save() + + # alternation + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'oo(f|b)'), + [ + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'oo(f|b)'), + [ + '', + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'^foo(f|b)'), + ['', '', '']) + + # greedy matching + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'b.*az'), + [ + '', + '', + '', + '', + '', + ]) + self.assertQuerysetEqual(Article.objects.filter(headline__iregex=r'b.*ar'), + [ + '', + '', + '', + '', + '', + ]) + + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql': + def test_regex_backreferencing(self): + # grouping and backreferences + now = datetime.now() + a10 = Article(pub_date=now, headline='foobar') + a10.save() + a11 = Article(pub_date=now, headline='foobaz') + a11.save() + a12 = Article(pub_date=now, headline='ooF') + a12.save() + a13 = Article(pub_date=now, headline='foobarbaz') + a13.save() + a14 = Article(pub_date=now, headline='zoocarfaz') + a14.save() + a15 = Article(pub_date=now, headline='barfoobaz') + a15.save() + a16 = Article(pub_date=now, headline='bazbaRFOO') + a16.save() + self.assertQuerysetEqual(Article.objects.filter(headline__regex=r'b(.).*b\1'), + ['', '', '']) From 628be3497a4c5a480f8564d782cbbd8f1f9879d7 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 2 Nov 2010 05:15:19 +0000 Subject: [PATCH 403/902] [1.2.X] Fixed #14470 -- Migrated modeladmin doctests. Thanks to Preston Timmons for the patch. Backport of r14425 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14426 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/modeladmin/models.py | 921 --------------- tests/regressiontests/modeladmin/tests.py | 1226 ++++++++++++++++++++ 2 files changed, 1226 insertions(+), 921 deletions(-) create mode 100644 tests/regressiontests/modeladmin/tests.py diff --git a/tests/regressiontests/modeladmin/models.py b/tests/regressiontests/modeladmin/models.py index 36ea416e7cbb..20dfe2ce98ea 100644 --- a/tests/regressiontests/modeladmin/models.py +++ b/tests/regressiontests/modeladmin/models.py @@ -34,924 +34,3 @@ class ValidationTestModel(models.Model): class ValidationTestInlineModel(models.Model): parent = models.ForeignKey(ValidationTestModel) - -__test__ = {'API_TESTS': """ - ->>> from django.contrib.admin.options import ModelAdmin, TabularInline, HORIZONTAL, VERTICAL ->>> from django.contrib.admin.sites import AdminSite - -None of the following tests really depend on the content of the request, so -we'll just pass in None. - ->>> request = None - -# the sign_date is not 100 percent accurate ;) ->>> band = Band(name='The Doors', bio='', sign_date=date(1965, 1, 1)) ->>> band.save() - -Under the covers, the admin system will initialize ModelAdmin with a Model -class and an AdminSite instance, so let's just go ahead and do that manually -for testing. - ->>> site = AdminSite() ->>> ma = ModelAdmin(Band, site) - ->>> ma.get_form(request).base_fields.keys() -['name', 'bio', 'sign_date'] - - -# form/fields/fieldsets interaction ########################################## - -fieldsets_add and fieldsets_change should return a special data structure that -is used in the templates. They should generate the "right thing" whether we -have specified a custom form, the fields arugment, or nothing at all. - -Here's the default case. There are no custom form_add/form_change methods, -no fields argument, and no fieldsets argument. - ->>> ma = ModelAdmin(Band, site) ->>> ma.get_fieldsets(request) -[(None, {'fields': ['name', 'bio', 'sign_date']})] ->>> ma.get_fieldsets(request, band) -[(None, {'fields': ['name', 'bio', 'sign_date']})] - - -If we specify the fields argument, fieldsets_add and fielsets_change should -just stick the fields into a formsets structure and return it. - ->>> class BandAdmin(ModelAdmin): -... fields = ['name'] - ->>> ma = BandAdmin(Band, site) ->>> ma.get_fieldsets(request) -[(None, {'fields': ['name']})] ->>> ma.get_fieldsets(request, band) -[(None, {'fields': ['name']})] - - - - -If we specify fields or fieldsets, it should exclude fields on the Form class -to the fields specified. This may cause errors to be raised in the db layer if -required model fields arent in fields/fieldsets, but that's preferable to -ghost errors where you have a field in your Form class that isn't being -displayed because you forgot to add it to fields/fielsets - ->>> class BandAdmin(ModelAdmin): -... fields = ['name'] - ->>> ma = BandAdmin(Band, site) ->>> ma.get_form(request).base_fields.keys() -['name'] ->>> ma.get_form(request, band).base_fields.keys() -['name'] - ->>> class BandAdmin(ModelAdmin): -... fieldsets = [(None, {'fields': ['name']})] - ->>> ma = BandAdmin(Band, site) ->>> ma.get_form(request).base_fields.keys() -['name'] ->>> ma.get_form(request, band).base_fields.keys() -['name'] - - -# Using `exclude`. - ->>> class BandAdmin(ModelAdmin): -... exclude = ['bio'] ->>> ma = BandAdmin(Band, site) ->>> ma.get_form(request).base_fields.keys() -['name', 'sign_date'] - -# You can also pass a tuple to `exclude`. - ->>> class BandAdmin(ModelAdmin): -... exclude = ('bio',) ->>> ma = BandAdmin(Band, site) ->>> ma.get_form(request).base_fields.keys() -['name', 'sign_date'] - -# Using `fields` and `exclude`. - ->>> class BandAdmin(ModelAdmin): -... fields = ['name', 'bio'] -... exclude = ['bio'] ->>> ma = BandAdmin(Band, site) ->>> ma.get_form(request).base_fields.keys() -['name'] - -If we specify a form, it should use it allowing custom validation to work -properly. This won't, however, break any of the admin widgets or media. - ->>> from django import forms ->>> class AdminBandForm(forms.ModelForm): -... delete = forms.BooleanField() -... -... class Meta: -... model = Band - ->>> class BandAdmin(ModelAdmin): -... form = AdminBandForm - ->>> ma = BandAdmin(Band, site) ->>> ma.get_form(request).base_fields.keys() -['name', 'bio', 'sign_date', 'delete'] ->>> type(ma.get_form(request).base_fields['sign_date'].widget) - - -If we need to override the queryset of a ModelChoiceField in our custom form -make sure that RelatedFieldWidgetWrapper doesn't mess that up. - ->>> band2 = Band(name='The Beetles', bio='', sign_date=date(1962, 1, 1)) ->>> band2.save() - ->>> class AdminConcertForm(forms.ModelForm): -... class Meta: -... model = Concert -... -... def __init__(self, *args, **kwargs): -... super(AdminConcertForm, self).__init__(*args, **kwargs) -... self.fields["main_band"].queryset = Band.objects.filter(name='The Doors') - ->>> class ConcertAdmin(ModelAdmin): -... form = AdminConcertForm - ->>> ma = ConcertAdmin(Concert, site) ->>> form = ma.get_form(request)() ->>> print form["main_band"] - - ->>> band2.delete() - -# radio_fields behavior ################################################ - -First, without any radio_fields specified, the widgets for ForeignKey -and fields with choices specified ought to be a basic Select widget. -ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so -they need to be handled properly when type checking. For Select fields, all of -the choices lists have a first entry of dashes. - ->>> cma = ModelAdmin(Concert, site) ->>> cmafa = cma.get_form(request) - ->>> type(cmafa.base_fields['main_band'].widget.widget) - ->>> list(cmafa.base_fields['main_band'].widget.choices) -[(u'', u'---------'), (1, u'The Doors')] - ->>> type(cmafa.base_fields['opening_band'].widget.widget) - ->>> list(cmafa.base_fields['opening_band'].widget.choices) -[(u'', u'---------'), (1, u'The Doors')] - ->>> type(cmafa.base_fields['day'].widget) - ->>> list(cmafa.base_fields['day'].widget.choices) -[('', '---------'), (1, 'Fri'), (2, 'Sat')] - ->>> type(cmafa.base_fields['transport'].widget) - ->>> list(cmafa.base_fields['transport'].widget.choices) -[('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')] - -Now specify all the fields as radio_fields. Widgets should now be -RadioSelect, and the choices list should have a first entry of 'None' if -blank=True for the model field. Finally, the widget should have the -'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL. - ->>> class ConcertAdmin(ModelAdmin): -... radio_fields = { -... 'main_band': HORIZONTAL, -... 'opening_band': VERTICAL, -... 'day': VERTICAL, -... 'transport': HORIZONTAL, -... } - ->>> cma = ConcertAdmin(Concert, site) ->>> cmafa = cma.get_form(request) - ->>> type(cmafa.base_fields['main_band'].widget.widget) - ->>> cmafa.base_fields['main_band'].widget.attrs -{'class': 'radiolist inline'} ->>> list(cmafa.base_fields['main_band'].widget.choices) -[(1, u'The Doors')] - ->>> type(cmafa.base_fields['opening_band'].widget.widget) - ->>> cmafa.base_fields['opening_band'].widget.attrs -{'class': 'radiolist'} ->>> list(cmafa.base_fields['opening_band'].widget.choices) -[(u'', u'None'), (1, u'The Doors')] - ->>> type(cmafa.base_fields['day'].widget) - ->>> cmafa.base_fields['day'].widget.attrs -{'class': 'radiolist'} ->>> list(cmafa.base_fields['day'].widget.choices) -[(1, 'Fri'), (2, 'Sat')] - ->>> type(cmafa.base_fields['transport'].widget) - ->>> cmafa.base_fields['transport'].widget.attrs -{'class': 'radiolist inline'} ->>> list(cmafa.base_fields['transport'].widget.choices) -[('', u'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')] - ->>> class AdminConcertForm(forms.ModelForm): -... class Meta: -... model = Concert -... exclude = ('transport',) - ->>> class ConcertAdmin(ModelAdmin): -... form = AdminConcertForm - ->>> ma = ConcertAdmin(Concert, site) ->>> ma.get_form(request).base_fields.keys() -['main_band', 'opening_band', 'day'] - ->>> class AdminConcertForm(forms.ModelForm): -... extra = forms.CharField() -... class Meta: -... model = Concert -... fields = ['extra', 'transport'] - ->>> class ConcertAdmin(ModelAdmin): -... form = AdminConcertForm - ->>> ma = ConcertAdmin(Concert, site) ->>> ma.get_form(request).base_fields.keys() -['extra', 'transport'] - ->>> class ConcertInline(TabularInline): -... form = AdminConcertForm -... model = Concert -... fk_name = 'main_band' -... can_delete = True - ->>> class BandAdmin(ModelAdmin): -... inlines = [ -... ConcertInline -... ] - ->>> ma = BandAdmin(Band, site) ->>> list(ma.get_formsets(request))[0]().forms[0].fields.keys() -['extra', 'transport', 'id', 'DELETE', 'main_band'] - - ->>> band.delete() - -# ModelAdmin Option Validation ################################################ - ->>> from django.contrib.admin.validation import validate ->>> from django.conf import settings - -# Ensure validation only runs when DEBUG = True - ->>> settings.DEBUG = True - ->>> class ValidationTestModelAdmin(ModelAdmin): -... raw_id_fields = 10 ->>> site = AdminSite() ->>> site.register(ValidationTestModel, ValidationTestModelAdmin) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple. - ->>> settings.DEBUG = False - ->>> class ValidationTestModelAdmin(ModelAdmin): -... raw_id_fields = 10 ->>> site = AdminSite() ->>> site.register(ValidationTestModel, ValidationTestModelAdmin) - -# raw_id_fields - ->>> class ValidationTestModelAdmin(ModelAdmin): -... raw_id_fields = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... raw_id_fields = ('non_existent_field',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.raw_id_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... raw_id_fields = ('name',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.raw_id_fields[0]', 'name' must be either a ForeignKey or ManyToManyField. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... raw_id_fields = ('users',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# fieldsets - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fieldsets = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.fieldsets' must be a list or tuple. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fieldsets = ({},) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.fieldsets[0]' must be a list or tuple. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fieldsets = ((),) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.fieldsets[0]' does not have exactly two elements. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fieldsets = (("General", ()),) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.fieldsets[0][1]' must be a dictionary. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fieldsets = (("General", {}),) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'fields' key is required in ValidationTestModelAdmin.fieldsets[0][1] field options dict. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fieldsets = (("General", {"fields": ("non_existent_field",)}),) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.fieldsets[0][1]['fields']' refers to field 'non_existent_field' that is missing from the form. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fieldsets = (("General", {"fields": ("name",)}),) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fieldsets = (("General", {"fields": ("name",)}),) -... fields = ["name",] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: Both fieldsets and fields are specified in ValidationTestModelAdmin. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fieldsets = [(None, {'fields': ['name', 'name']})] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: There are duplicate field(s) in ValidationTestModelAdmin.fieldsets - ->>> class ValidationTestModelAdmin(ModelAdmin): -... fields = ["name", "name"] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: There are duplicate field(s) in ValidationTestModelAdmin.fields - -# form - ->>> class FakeForm(object): -... pass ->>> class ValidationTestModelAdmin(ModelAdmin): -... form = FakeForm ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: ValidationTestModelAdmin.form does not inherit from BaseModelForm. - -# fielsets with custom form - ->>> class BandAdmin(ModelAdmin): -... fieldsets = ( -... ('Band', { -... 'fields': ('non_existent_field',) -... }), -... ) ->>> validate(BandAdmin, Band) -Traceback (most recent call last): -... -ImproperlyConfigured: 'BandAdmin.fieldsets[0][1]['fields']' refers to field 'non_existent_field' that is missing from the form. - ->>> class BandAdmin(ModelAdmin): -... fieldsets = ( -... ('Band', { -... 'fields': ('name',) -... }), -... ) ->>> validate(BandAdmin, Band) - ->>> class AdminBandForm(forms.ModelForm): -... class Meta: -... model = Band ->>> class BandAdmin(ModelAdmin): -... form = AdminBandForm -... -... fieldsets = ( -... ('Band', { -... 'fields': ('non_existent_field',) -... }), -... ) ->>> validate(BandAdmin, Band) -Traceback (most recent call last): -... -ImproperlyConfigured: 'BandAdmin.fieldsets[0][1]['fields']' refers to field 'non_existent_field' that is missing from the form. - ->>> class AdminBandForm(forms.ModelForm): -... delete = forms.BooleanField() -... -... class Meta: -... model = Band ->>> class BandAdmin(ModelAdmin): -... form = AdminBandForm -... -... fieldsets = ( -... ('Band', { -... 'fields': ('name', 'bio', 'sign_date', 'delete') -... }), -... ) ->>> validate(BandAdmin, Band) - -# filter_vertical - ->>> class ValidationTestModelAdmin(ModelAdmin): -... filter_vertical = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.filter_vertical' must be a list or tuple. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... filter_vertical = ("non_existent_field",) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.filter_vertical' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... filter_vertical = ("name",) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.filter_vertical[0]' must be a ManyToManyField. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... filter_vertical = ("users",) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# filter_horizontal - ->>> class ValidationTestModelAdmin(ModelAdmin): -... filter_horizontal = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.filter_horizontal' must be a list or tuple. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... filter_horizontal = ("non_existent_field",) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.filter_horizontal' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... filter_horizontal = ("name",) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.filter_horizontal[0]' must be a ManyToManyField. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... filter_horizontal = ("users",) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# radio_fields - ->>> class ValidationTestModelAdmin(ModelAdmin): -... radio_fields = () ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.radio_fields' must be a dictionary. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... radio_fields = {"non_existent_field": None} ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.radio_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... radio_fields = {"name": None} ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.radio_fields['name']' is neither an instance of ForeignKey nor does have choices set. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... radio_fields = {"state": None} ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.radio_fields['state']' is neither admin.HORIZONTAL nor admin.VERTICAL. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... radio_fields = {"state": VERTICAL} ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# prepopulated_fields - ->>> class ValidationTestModelAdmin(ModelAdmin): -... prepopulated_fields = () ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.prepopulated_fields' must be a dictionary. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... prepopulated_fields = {"non_existent_field": None} ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.prepopulated_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... prepopulated_fields = {"slug": ("non_existent_field",)} ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.prepopulated_fields['slug'][0]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... prepopulated_fields = {"users": ("name",)} ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.prepopulated_fields['users']' is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... prepopulated_fields = {"slug": ("name",)} ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# list_display - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_display = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.list_display' must be a list or tuple. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_display = ('non_existent_field',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: ValidationTestModelAdmin.list_display[0], 'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'. - - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_display = ('users',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.list_display[0]', 'users' is a ManyToManyField which is not supported. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_display = ('name',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# list_display_links - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_display_links = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.list_display_links' must be a list or tuple. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_display_links = ('non_existent_field',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.list_display_links[0]' refers to 'non_existent_field' that is neither a field, method or property of model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_display_links = ('name',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.list_display_links[0]'refers to 'name' which is not defined in 'list_display'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_display = ('name',) -... list_display_links = ('name',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# list_filter - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_filter = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.list_filter' must be a list or tuple. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_filter = ('non_existent_field',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.list_filter[0]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_filter = ('is_active',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# list_per_page - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_per_page = 'hello' ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.list_per_page' should be a integer. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_per_page = 100 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# search_fields - ->>> class ValidationTestModelAdmin(ModelAdmin): -... search_fields = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.search_fields' must be a list or tuple. - -# date_hierarchy - ->>> class ValidationTestModelAdmin(ModelAdmin): -... date_hierarchy = 'non_existent_field' ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.date_hierarchy' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... date_hierarchy = 'name' ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... date_hierarchy = 'pub_date' ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# ordering - ->>> class ValidationTestModelAdmin(ModelAdmin): -... ordering = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.ordering' must be a list or tuple. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... ordering = ('non_existent_field',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.ordering[0]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... ordering = ('?', 'name') ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.ordering' has the random ordering marker '?', but contains other fields as well. Please either remove '?' or the other fields. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... ordering = ('?',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - ->>> class ValidationTestModelAdmin(ModelAdmin): -... ordering = ('band__name',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - ->>> class ValidationTestModelAdmin(ModelAdmin): -... ordering = ('name',) ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# list_select_related - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_select_related = 1 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.list_select_related' should be a boolean. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... list_select_related = False ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# save_as - ->>> class ValidationTestModelAdmin(ModelAdmin): -... save_as = 1 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.save_as' should be a boolean. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... save_as = True ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# save_on_top - ->>> class ValidationTestModelAdmin(ModelAdmin): -... save_on_top = 1 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.save_on_top' should be a boolean. - ->>> class ValidationTestModelAdmin(ModelAdmin): -... save_on_top = True ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# inlines - ->>> from django.contrib.admin.options import TabularInline - ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = 10 ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.inlines' must be a list or tuple. - ->>> class ValidationTestInline(object): -... pass ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.inlines[0]' does not inherit from BaseModelAdmin. - ->>> class ValidationTestInline(TabularInline): -... pass ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'model' is a required attribute of 'ValidationTestModelAdmin.inlines[0]'. - ->>> class SomethingBad(object): -... pass ->>> class ValidationTestInline(TabularInline): -... model = SomethingBad ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestModelAdmin.inlines[0].model' does not inherit from models.Model. - ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# fields - ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... fields = 10 ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestInline.fields' must be a list or tuple. - ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... fields = ("non_existent_field",) ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestInline.fields' refers to field 'non_existent_field' that is missing from the form. - -# fk_name - ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... fk_name = "non_existent_field" ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestInline.fk_name' refers to field 'non_existent_field' that is missing from model 'ValidationTestInlineModel'. - ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... fk_name = "parent" ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# extra - ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... extra = "hello" ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestInline.extra' should be a integer. - ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... extra = 2 ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# max_num - ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... max_num = "hello" ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestInline.max_num' should be an integer or None (default). - ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... max_num = 2 ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -# formset - ->>> from django.forms.models import BaseModelFormSet - ->>> class FakeFormSet(object): -... pass ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... formset = FakeFormSet ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) -Traceback (most recent call last): -... -ImproperlyConfigured: 'ValidationTestInline.formset' does not inherit from BaseModelFormSet. - ->>> class RealModelFormSet(BaseModelFormSet): -... pass ->>> class ValidationTestInline(TabularInline): -... model = ValidationTestInlineModel -... formset = RealModelFormSet ->>> class ValidationTestModelAdmin(ModelAdmin): -... inlines = [ValidationTestInline] ->>> validate(ValidationTestModelAdmin, ValidationTestModel) - -""" -} diff --git a/tests/regressiontests/modeladmin/tests.py b/tests/regressiontests/modeladmin/tests.py new file mode 100644 index 000000000000..b13a0ec952ad --- /dev/null +++ b/tests/regressiontests/modeladmin/tests.py @@ -0,0 +1,1226 @@ +from datetime import date +import unittest + +from django import forms +from django.conf import settings +from django.contrib.admin.options import ModelAdmin, TabularInline, \ + HORIZONTAL, VERTICAL +from django.contrib.admin.sites import AdminSite +from django.contrib.admin.validation import validate +from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect +from django.core.exceptions import ImproperlyConfigured +from django.forms.models import BaseModelFormSet +from django.forms.widgets import Select +from django.test import TestCase + +from models import Band, Concert, ValidationTestModel, \ + ValidationTestInlineModel + + +# None of the following tests really depend on the content of the request, +# so we'll just pass in None. +request = None + + +class ModelAdminTests(TestCase): + + def setUp(self): + self.band = Band.objects.create( + name='The Doors', + bio='', + sign_date=date(1965, 1, 1), + ) + self.site = AdminSite() + + # form/fields/fieldsets interaction ############################## + + def test_default_fields(self): + ma = ModelAdmin(Band, self.site) + + self.assertEquals(ma.get_form(request).base_fields.keys(), + ['name', 'bio', 'sign_date']) + + def test_default_fieldsets(self): + # fieldsets_add and fieldsets_change should return a special data structure that + # is used in the templates. They should generate the "right thing" whether we + # have specified a custom form, the fields argument, or nothing at all. + # + # Here's the default case. There are no custom form_add/form_change methods, + # no fields argument, and no fieldsets argument. + ma = ModelAdmin(Band, self.site) + self.assertEqual(ma.get_fieldsets(request), + [(None, {'fields': ['name', 'bio', 'sign_date']})]) + + self.assertEqual(ma.get_fieldsets(request, self.band), + [(None, {'fields': ['name', 'bio', 'sign_date']})]) + + def test_field_arguments(self): + # If we specify the fields argument, fieldsets_add and fielsets_change should + # just stick the fields into a formsets structure and return it. + class BandAdmin(ModelAdmin): + fields = ['name'] + + ma = BandAdmin(Band, self.site) + + self.assertEqual( ma.get_fieldsets(request), + [(None, {'fields': ['name']})]) + + self.assertEqual(ma.get_fieldsets(request, self.band), + [(None, {'fields': ['name']})]) + + def test_field_arguments_restricted_on_form(self): + # If we specify fields or fieldsets, it should exclude fields on the Form class + # to the fields specified. This may cause errors to be raised in the db layer if + # required model fields arent in fields/fieldsets, but that's preferable to + # ghost errors where you have a field in your Form class that isn't being + # displayed because you forgot to add it to fields/fieldsets + + # Using `fields`. + class BandAdmin(ModelAdmin): + fields = ['name'] + + ma = BandAdmin(Band, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), ['name']) + self.assertEqual(ma.get_form(request, self.band).base_fields.keys(), + ['name']) + + # Using `fieldsets`. + class BandAdmin(ModelAdmin): + fieldsets = [(None, {'fields': ['name']})] + + ma = BandAdmin(Band, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), ['name']) + self.assertEqual(ma.get_form(request, self.band).base_fields.keys(), + ['name']) + + # Using `exclude`. + class BandAdmin(ModelAdmin): + exclude = ['bio'] + + ma = BandAdmin(Band, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), + ['name', 'sign_date']) + + # You can also pass a tuple to `exclude`. + class BandAdmin(ModelAdmin): + exclude = ('bio',) + + ma = BandAdmin(Band, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), + ['name', 'sign_date']) + + # Using `fields` and `exclude`. + class BandAdmin(ModelAdmin): + fields = ['name', 'bio'] + exclude = ['bio'] + + ma = BandAdmin(Band, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), + ['name']) + + def test_custom_form_validation(self): + # If we specify a form, it should use it allowing custom validation to work + # properly. This won't, however, break any of the admin widgets or media. + + class AdminBandForm(forms.ModelForm): + delete = forms.BooleanField() + + class Meta: + model = Band + + class BandAdmin(ModelAdmin): + form = AdminBandForm + + ma = BandAdmin(Band, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), + ['name', 'bio', 'sign_date', 'delete']) + + self.assertEqual( + type(ma.get_form(request).base_fields['sign_date'].widget), + AdminDateWidget) + + def test_queryset_override(self): + # If we need to override the queryset of a ModelChoiceField in our custom form + # make sure that RelatedFieldWidgetWrapper doesn't mess that up. + + band2 = Band(name='The Beatles', bio='', sign_date=date(1962, 1, 1)) + band2.save() + + class ConcertAdmin(ModelAdmin): + pass + ma = ConcertAdmin(Concert, self.site) + form = ma.get_form(request)() + + self.assertEqual(str(form["main_band"]), + '' % (self.band.id, band2.id)) + + class AdminConcertForm(forms.ModelForm): + class Meta: + model = Concert + + def __init__(self, *args, **kwargs): + super(AdminConcertForm, self).__init__(*args, **kwargs) + self.fields["main_band"].queryset = Band.objects.filter(name='The Doors') + + class ConcertAdmin(ModelAdmin): + form = AdminConcertForm + + ma = ConcertAdmin(Concert, self.site) + form = ma.get_form(request)() + + self.assertEqual(str(form["main_band"]), + '' % self.band.id) + + # radio_fields behavior ########################################### + + def test_default_foreign_key_widget(self): + # First, without any radio_fields specified, the widgets for ForeignKey + # and fields with choices specified ought to be a basic Select widget. + # ForeignKey widgets in the admin are wrapped with RelatedFieldWidgetWrapper so + # they need to be handled properly when type checking. For Select fields, all of + # the choices lists have a first entry of dashes. + + cma = ModelAdmin(Concert, self.site) + cmafa = cma.get_form(request) + + self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget), + Select) + self.assertEqual( + list(cmafa.base_fields['main_band'].widget.choices), + [(u'', u'---------'), (self.band.id, u'The Doors')]) + + self.assertEqual( + type(cmafa.base_fields['opening_band'].widget.widget), Select) + self.assertEqual( + list(cmafa.base_fields['opening_band'].widget.choices), + [(u'', u'---------'), (self.band.id, u'The Doors')]) + + self.assertEqual(type(cmafa.base_fields['day'].widget), Select) + self.assertEqual(list(cmafa.base_fields['day'].widget.choices), + [('', '---------'), (1, 'Fri'), (2, 'Sat')]) + + self.assertEqual(type(cmafa.base_fields['transport'].widget), + Select) + self.assertEqual( + list(cmafa.base_fields['transport'].widget.choices), + [('', '---------'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]) + + def test_foreign_key_as_radio_field(self): + # Now specify all the fields as radio_fields. Widgets should now be + # RadioSelect, and the choices list should have a first entry of 'None' if + # blank=True for the model field. Finally, the widget should have the + # 'radiolist' attr, and 'inline' as well if the field is specified HORIZONTAL. + + class ConcertAdmin(ModelAdmin): + radio_fields = { + 'main_band': HORIZONTAL, + 'opening_band': VERTICAL, + 'day': VERTICAL, + 'transport': HORIZONTAL, + } + + cma = ConcertAdmin(Concert, self.site) + cmafa = cma.get_form(request) + + self.assertEqual(type(cmafa.base_fields['main_band'].widget.widget), + AdminRadioSelect) + self.assertEqual(cmafa.base_fields['main_band'].widget.attrs, + {'class': 'radiolist inline'}) + self.assertEqual(list(cmafa.base_fields['main_band'].widget.choices), + [(self.band.id, u'The Doors')]) + + self.assertEqual( + type(cmafa.base_fields['opening_band'].widget.widget), + AdminRadioSelect) + self.assertEqual(cmafa.base_fields['opening_band'].widget.attrs, + {'class': 'radiolist'}) + self.assertEqual( + list(cmafa.base_fields['opening_band'].widget.choices), + [(u'', u'None'), (self.band.id, u'The Doors')]) + + self.assertEqual(type(cmafa.base_fields['day'].widget), + AdminRadioSelect) + self.assertEqual(cmafa.base_fields['day'].widget.attrs, + {'class': 'radiolist'}) + self.assertEqual(list(cmafa.base_fields['day'].widget.choices), + [(1, 'Fri'), (2, 'Sat')]) + + self.assertEqual(type(cmafa.base_fields['transport'].widget), + AdminRadioSelect) + self.assertEqual(cmafa.base_fields['transport'].widget.attrs, + {'class': 'radiolist inline'}) + self.assertEqual(list(cmafa.base_fields['transport'].widget.choices), + [('', u'None'), (1, 'Plane'), (2, 'Train'), (3, 'Bus')]) + + class AdminConcertForm(forms.ModelForm): + class Meta: + model = Concert + exclude = ('transport',) + + class ConcertAdmin(ModelAdmin): + form = AdminConcertForm + + ma = ConcertAdmin(Concert, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), + ['main_band', 'opening_band', 'day']) + + class AdminConcertForm(forms.ModelForm): + extra = forms.CharField() + + class Meta: + model = Concert + fields = ['extra', 'transport'] + + class ConcertAdmin(ModelAdmin): + form = AdminConcertForm + + ma = ConcertAdmin(Concert, self.site) + self.assertEqual(ma.get_form(request).base_fields.keys(), + ['extra', 'transport']) + + class ConcertInline(TabularInline): + form = AdminConcertForm + model = Concert + fk_name = 'main_band' + can_delete = True + + class BandAdmin(ModelAdmin): + inlines = [ + ConcertInline + ] + + ma = BandAdmin(Band, self.site) + self.assertEqual( + list(ma.get_formsets(request))[0]().forms[0].fields.keys(), + ['extra', 'transport', 'id', 'DELETE', 'main_band']) + + +class ValidationTests(unittest.TestCase): + def assertRaisesErrorWithMessage(self, error, message, callable, *args, **kwargs): + self.assertRaises(error, callable, *args, **kwargs) + try: + callable(*args, **kwargs) + except error, e: + self.assertEqual(message, str(e)) + + def test_validation_only_runs_in_debug(self): + # Ensure validation only runs when DEBUG = True + try: + settings.DEBUG = True + + class ValidationTestModelAdmin(ModelAdmin): + raw_id_fields = 10 + + site = AdminSite() + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.", + site.register, + ValidationTestModel, + ValidationTestModelAdmin, + ) + finally: + settings.DEBUG = False + + site = AdminSite() + site.register(ValidationTestModel, ValidationTestModelAdmin) + + def test_raw_id_fields_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + raw_id_fields = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + raw_id_fields = ('non_existent_field',) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.raw_id_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + raw_id_fields = ('name',) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.raw_id_fields[0]', 'name' must be either a ForeignKey or ManyToManyField.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + raw_id_fields = ('users',) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_fieldsets_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + fieldsets = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.fieldsets' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + fieldsets = ({},) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.fieldsets[0]' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + fieldsets = ((),) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.fieldsets[0]' does not have exactly two elements.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + fieldsets = (("General", ()),) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.fieldsets[0][1]' must be a dictionary.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + fieldsets = (("General", {}),) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'fields' key is required in ValidationTestModelAdmin.fieldsets[0][1] field options dict.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + fieldsets = (("General", {"fields": ("non_existent_field",)}),) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.fieldsets[0][1]['fields']' refers to field 'non_existent_field' that is missing from the form.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + fieldsets = (("General", {"fields": ("name",)}),) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + class ValidationTestModelAdmin(ModelAdmin): + fieldsets = (("General", {"fields": ("name",)}),) + fields = ["name",] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "Both fieldsets and fields are specified in ValidationTestModelAdmin.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + fieldsets = [(None, {'fields': ['name', 'name']})] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "There are duplicate field(s) in ValidationTestModelAdmin.fieldsets", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + fields = ["name", "name"] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "There are duplicate field(s) in ValidationTestModelAdmin.fields", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + def test_form_validation(self): + + class FakeForm(object): + pass + + class ValidationTestModelAdmin(ModelAdmin): + form = FakeForm + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "ValidationTestModelAdmin.form does not inherit from BaseModelForm.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + def test_fieldsets_with_custom_form_validation(self): + + class BandAdmin(ModelAdmin): + + fieldsets = ( + ('Band', { + 'fields': ('non_existent_field',) + }), + ) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'BandAdmin.fieldsets[0][1]['fields']' refers to field 'non_existent_field' that is missing from the form.", + validate, + BandAdmin, + Band, + ) + + class BandAdmin(ModelAdmin): + fieldsets = ( + ('Band', { + 'fields': ('name',) + }), + ) + + validate(BandAdmin, Band) + + class AdminBandForm(forms.ModelForm): + class Meta: + model = Band + + class BandAdmin(ModelAdmin): + form = AdminBandForm + + fieldsets = ( + ('Band', { + 'fields': ('non_existent_field',) + }), + ) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'BandAdmin.fieldsets[0][1]['fields']' refers to field 'non_existent_field' that is missing from the form.", + validate, + BandAdmin, + Band, + ) + + class AdminBandForm(forms.ModelForm): + delete = forms.BooleanField() + + class Meta: + model = Band + + class BandAdmin(ModelAdmin): + form = AdminBandForm + + fieldsets = ( + ('Band', { + 'fields': ('name', 'bio', 'sign_date', 'delete') + }), + ) + + validate(BandAdmin, Band) + + def test_filter_vertical_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + filter_vertical = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.filter_vertical' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + filter_vertical = ("non_existent_field",) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.filter_vertical' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + filter_vertical = ("name",) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.filter_vertical[0]' must be a ManyToManyField.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + filter_vertical = ("users",) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_filter_horizontal_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + filter_horizontal = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.filter_horizontal' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + filter_horizontal = ("non_existent_field",) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.filter_horizontal' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + filter_horizontal = ("name",) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.filter_horizontal[0]' must be a ManyToManyField.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + filter_horizontal = ("users",) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_radio_fields_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + radio_fields = () + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.radio_fields' must be a dictionary.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + radio_fields = {"non_existent_field": None} + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.radio_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + radio_fields = {"name": None} + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.radio_fields['name']' is neither an instance of ForeignKey nor does have choices set.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + radio_fields = {"state": None} + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.radio_fields['state']' is neither admin.HORIZONTAL nor admin.VERTICAL.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + radio_fields = {"state": VERTICAL} + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_prepopulated_fields_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + prepopulated_fields = () + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.prepopulated_fields' must be a dictionary.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + prepopulated_fields = {"non_existent_field": None} + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.prepopulated_fields' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + prepopulated_fields = {"slug": ("non_existent_field",)} + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.prepopulated_fields['slug'][0]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + prepopulated_fields = {"users": ("name",)} + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.prepopulated_fields['users']' is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + prepopulated_fields = {"slug": ("name",)} + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_list_display_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + list_display = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.list_display' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_display = ('non_existent_field',) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "ValidationTestModelAdmin.list_display[0], 'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_display = ('users',) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.list_display[0]', 'users' is a ManyToManyField which is not supported.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_display = ('name',) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_list_display_links_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + list_display_links = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.list_display_links' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_display_links = ('non_existent_field',) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.list_display_links[0]' refers to 'non_existent_field' that is neither a field, method or property of model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_display_links = ('name',) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.list_display_links[0]'refers to 'name' which is not defined in 'list_display'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_display = ('name',) + list_display_links = ('name',) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_list_filter_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + list_filter = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.list_filter' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_filter = ('non_existent_field',) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.list_filter[0]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_filter = ('is_active',) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_list_per_page_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + list_per_page = 'hello' + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.list_per_page' should be a integer.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_per_page = 100 + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_search_fields_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + search_fields = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.search_fields' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + def test_date_hierarchy_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + date_hierarchy = 'non_existent_field' + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.date_hierarchy' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + date_hierarchy = 'name' + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + date_hierarchy = 'pub_date' + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_ordering_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + ordering = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.ordering' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + ordering = ('non_existent_field',) + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.ordering[0]' refers to field 'non_existent_field' that is missing from model 'ValidationTestModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + ordering = ('?', 'name') + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.ordering' has the random ordering marker '?', but contains other fields as well. Please either remove '?' or the other fields.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + ordering = ('?',) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + class ValidationTestModelAdmin(ModelAdmin): + ordering = ('band__name',) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + class ValidationTestModelAdmin(ModelAdmin): + ordering = ('name',) + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_list_select_related_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + list_select_related = 1 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.list_select_related' should be a boolean.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + list_select_related = False + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_save_as_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + save_as = 1 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.save_as' should be a boolean.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + save_as = True + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_save_on_top_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + save_on_top = 1 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.save_on_top' should be a boolean.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestModelAdmin(ModelAdmin): + save_on_top = True + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_inlines_validation(self): + + class ValidationTestModelAdmin(ModelAdmin): + inlines = 10 + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.inlines' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestInline(object): + pass + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.inlines[0]' does not inherit from BaseModelAdmin.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestInline(TabularInline): + pass + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'model' is a required attribute of 'ValidationTestModelAdmin.inlines[0]'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class SomethingBad(object): + pass + + class ValidationTestInline(TabularInline): + model = SomethingBad + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestModelAdmin.inlines[0].model' does not inherit from models.Model.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_fields_validation(self): + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + fields = 10 + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestInline.fields' must be a list or tuple.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + fields = ("non_existent_field",) + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestInline.fields' refers to field 'non_existent_field' that is missing from the form.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + def test_fk_name_validation(self): + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + fk_name = "non_existent_field" + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestInline.fk_name' refers to field 'non_existent_field' that is missing from model 'ValidationTestInlineModel'.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + fk_name = "parent" + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_extra_validation(self): + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + extra = "hello" + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestInline.extra' should be a integer.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + extra = 2 + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_max_num_validation(self): + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + max_num = "hello" + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestInline.max_num' should be an integer or None (default).", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + max_num = 2 + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + validate(ValidationTestModelAdmin, ValidationTestModel) + + def test_formset_validation(self): + + class FakeFormSet(object): + pass + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + formset = FakeFormSet + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + self.assertRaisesErrorWithMessage( + ImproperlyConfigured, + "'ValidationTestInline.formset' does not inherit from BaseModelFormSet.", + validate, + ValidationTestModelAdmin, + ValidationTestModel, + ) + + class RealModelFormSet(BaseModelFormSet): + pass + + class ValidationTestInline(TabularInline): + model = ValidationTestInlineModel + formset = RealModelFormSet + + class ValidationTestModelAdmin(ModelAdmin): + inlines = [ValidationTestInline] + + validate(ValidationTestModelAdmin, ValidationTestModel) From 3cfde8364be4f12e59e77dcd656e394236681ea8 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 2 Nov 2010 05:23:44 +0000 Subject: [PATCH 404/902] [1.2.X] Migrated app_loading doctests. Backport of r14427 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14428 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/app_loading/tests.py | 29 ++++++++++------------ 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/tests/regressiontests/app_loading/tests.py b/tests/regressiontests/app_loading/tests.py index 111ed4692558..d43cfafaaca3 100644 --- a/tests/regressiontests/app_loading/tests.py +++ b/tests/regressiontests/app_loading/tests.py @@ -7,26 +7,23 @@ from django.conf import Settings from django.db.models.loading import cache, load_app -__test__ = {"API_TESTS": """ -Test the globbing of INSTALLED_APPS. ->>> old_sys_path = sys.path ->>> sys.path.append(os.path.dirname(os.path.abspath(__file__))) - ->>> old_tz = os.environ.get("TZ") ->>> settings = Settings('test_settings') - ->>> settings.INSTALLED_APPS -['parent.app', 'parent.app1', 'parent.app_2'] +class InstalledAppsGlobbingTest(TestCase): + def setUp(self): + self.OLD_SYS_PATH = sys.path + sys.path.append(os.path.dirname(os.path.abspath(__file__))) + self.OLD_TZ = os.environ.get("TZ") ->>> sys.path = old_sys_path + def test_globbing(self): + settings = Settings('test_settings') + self.assertEquals(settings.INSTALLED_APPS, ['parent.app', 'parent.app1', 'parent.app_2']) -# Undo a side-effect of installing a new settings object. ->>> if hasattr(time, "tzset") and old_tz: -... os.environ["TZ"] = old_tz -... time.tzset() + def tearDown(self): + sys.path = self.OLD_SYS_PATH + if hasattr(time, "tzset") and self.OLD_TZ: + os.environ["TZ"] = self.OLD_TZ + time.tzset() -"""} class EggLoadingTest(TestCase): From a24ccfc79f94ce2e42bffd945539c320cfd058c8 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 2 Nov 2010 05:28:12 +0000 Subject: [PATCH 405/902] [1.2.X] Properly handle the fact that lists are mutable when trying to maintain state in a test. Backport of [14429]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14430 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/app_loading/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/regressiontests/app_loading/tests.py b/tests/regressiontests/app_loading/tests.py index d43cfafaaca3..17fe08b29934 100644 --- a/tests/regressiontests/app_loading/tests.py +++ b/tests/regressiontests/app_loading/tests.py @@ -10,7 +10,7 @@ class InstalledAppsGlobbingTest(TestCase): def setUp(self): - self.OLD_SYS_PATH = sys.path + self.OLD_SYS_PATH = sys.path[:] sys.path.append(os.path.dirname(os.path.abspath(__file__))) self.OLD_TZ = os.environ.get("TZ") @@ -28,7 +28,7 @@ def tearDown(self): class EggLoadingTest(TestCase): def setUp(self): - self.old_path = sys.path + self.old_path = sys.path[:] self.egg_dir = '%s/eggs' % os.path.dirname(__file__) # This test adds dummy applications to the app cache. These From 5b78abeba795364e0f90448d9f51c255d0708394 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 2 Nov 2010 05:31:53 +0000 Subject: [PATCH 406/902] [1.2.X] Fixed a few more cases of the tests not properly restoring sys.path (follow up on [14430]). Backport of [14431]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14432 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/proxy_model_inheritance/tests.py | 2 +- tests/regressiontests/templates/tests.py | 2 +- tests/regressiontests/utils/module_loading.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/modeltests/proxy_model_inheritance/tests.py b/tests/modeltests/proxy_model_inheritance/tests.py index d10d6a4ac15d..b6828515ab9a 100644 --- a/tests/modeltests/proxy_model_inheritance/tests.py +++ b/tests/modeltests/proxy_model_inheritance/tests.py @@ -17,7 +17,7 @@ class ProxyModelInheritanceTests(TransactionTestCase): def setUp(self): - self.old_sys_path = sys.path + self.old_sys_path = sys.path[:] sys.path.append(os.path.dirname(os.path.abspath(__file__))) self.old_installed_apps = settings.INSTALLED_APPS settings.INSTALLED_APPS = ('app1', 'app2') diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index cea42224dd92..1dd54779b742 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -1356,7 +1356,7 @@ def get_template_tests(self): class TemplateTagLoading(unittest.TestCase): def setUp(self): - self.old_path = sys.path + self.old_path = sys.path[:] self.old_apps = settings.INSTALLED_APPS self.egg_dir = '%s/eggs' % os.path.dirname(__file__) self.old_tag_modules = template.templatetags_modules diff --git a/tests/regressiontests/utils/module_loading.py b/tests/regressiontests/utils/module_loading.py index 4422f8f5d85d..8cbefbb011d5 100644 --- a/tests/regressiontests/utils/module_loading.py +++ b/tests/regressiontests/utils/module_loading.py @@ -26,7 +26,7 @@ def test_loader(self): class EggLoader(unittest.TestCase): def setUp(self): - self.old_path = sys.path + self.old_path = sys.path[:] self.egg_dir = '%s/eggs' % os.path.dirname(__file__) def tearDown(self): From 72e3bc6e6f6035e3657069790391ce5058d95e20 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Tue, 2 Nov 2010 11:53:53 +0000 Subject: [PATCH 407/902] [1.2.X] Fixed #14584 -- Documented settings.PASSWORD_RESET_TIMEOUT_DAYS. Also fixed some cross-refs in the neighborhood. Thanks to hop for the report and Adam Mckerlie for the draft patch. Backport of [14437] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14438 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/settings.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 80a2d5b900c6..b2a9b550efad 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1176,10 +1176,20 @@ Default: ``0`` Number of digits grouped together on the integer part of a number. Common use is to display a thousand separator. If this setting is ``0``, then, no grouping will be applied to the number. If this setting is greater than ``0`` then the -setting ``THOUSAND_SEPARATOR`` will be used as the separator between those +setting :setting:`THOUSAND_SEPARATOR` will be used as the separator between those groups. -See also ``THOUSAND_SEPARATOR`` +See also :setting:`THOUSAND_SEPARATOR`. + +.. setting:: PASSWORD_RESET_TIMEOUT_DAYS + +PASSWORD_RESET_TIMEOUT_DAYS +--------------------------- + +Default: ``3`` + +The number of days a password reset link is valid for. Used by the +:mod:`django.contrib.auth` password reset mechanism. .. setting:: PREPEND_WWW From a25718c157938e0bdbba7c9e9ee021ea654b1577 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 2 Nov 2010 18:30:31 +0000 Subject: [PATCH 408/902] [1.2.X] Fixed #14559 -- corrected some typos and misleading docstrings. Thanks to Gabriel Hurley for the patch. Backport of [14441]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14442 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/dispatch/dispatcher.py | 10 +++++----- django/middleware/common.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index de093346375b..30245a79c5ff 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -41,7 +41,7 @@ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): A function or an instance method which is to receive signals. Receivers must be hashable objects. - if weak is True, then receiver must be weak-referencable (more + If weak is True, then receiver must be weak-referencable (more precisely saferef.safeRef() must be able to create a reference to the receiver). @@ -52,11 +52,11 @@ def connect(self, receiver, sender=None, weak=True, dispatch_uid=None): dispatch_uid. sender - The sender to which the receiver should respond Must either be + The sender to which the receiver should respond. Must either be of type Signal, or None to receive events from any sender. weak - Whether to use weak references to the receiver By default, the + Whether to use weak references to the receiver. By default, the module will attempt to use weak references to the receiver objects. If this parameter is false, then strong references will be used. @@ -170,7 +170,7 @@ def send_robust(self, sender, **named): Arguments: sender - The sender of the signal Can be any python object (normally one + The sender of the signal. Can be any python object (normally one registered with a connect if you actually want something to occur). @@ -182,7 +182,7 @@ def send_robust(self, sender, **named): Return a list of tuple pairs [(receiver, response), ... ]. May raise DispatcherKeyError. - if any receiver raises an error (specifically any subclass of + If any receiver raises an error (specifically any subclass of Exception), the error instance is returned as the result for that receiver. """ diff --git a/django/middleware/common.py b/django/middleware/common.py index 309058870a2a..0be89d438245 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -80,7 +80,7 @@ def process_request(self, request): return http.HttpResponsePermanentRedirect(newurl) def process_response(self, request, response): - "Check for a flat page (for 404s) and calculate the Etag, if needed." + "Send broken link emails and calculate the Etag, if needed." if response.status_code == 404: if settings.SEND_BROKEN_LINK_EMAILS: # If the referrer was from an internal link or a non-search-engine site, From 6f30440b7291deac7e7de0c0e422f2bcc89a3c6d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 2 Nov 2010 18:41:23 +0000 Subject: [PATCH 409/902] [1.2.X] Fixed #10728 -- corrected subclassing of Fields who use the SubfieldBase metaclass. Thanks to G2P and George Sakkis for their work on the patch. Backport of [14443]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14444 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/subclassing.py | 7 ++++--- tests/modeltests/field_subclassing/fields.py | 3 +++ tests/modeltests/field_subclassing/models.py | 5 ++++- tests/modeltests/field_subclassing/tests.py | 8 +++++++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/django/db/models/fields/subclassing.py b/django/db/models/fields/subclassing.py index bd11675ad3a0..e7cf48349fd5 100644 --- a/django/db/models/fields/subclassing.py +++ b/django/db/models/fields/subclassing.py @@ -79,7 +79,8 @@ class SubfieldBase(LegacyConnection): def __new__(cls, base, name, attrs): new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs) new_class.contribute_to_class = make_contrib( - attrs.get('contribute_to_class')) + new_class, attrs.get('contribute_to_class') + ) return new_class class Creator(object): @@ -97,7 +98,7 @@ def __get__(self, obj, type=None): def __set__(self, obj, value): obj.__dict__[self.field.name] = self.field.to_python(value) -def make_contrib(func=None): +def make_contrib(superclass, func=None): """ Returns a suitable contribute_to_class() method for the Field subclass. @@ -110,7 +111,7 @@ def contribute_to_class(self, cls, name): if func: func(self, cls, name) else: - super(self.__class__, self).contribute_to_class(cls, name) + super(superclass, self).contribute_to_class(cls, name) setattr(cls, self.name, Creator(self)) return contribute_to_class diff --git a/tests/modeltests/field_subclassing/fields.py b/tests/modeltests/field_subclassing/fields.py index 1f9bdf5e0a08..8675b31be04a 100644 --- a/tests/modeltests/field_subclassing/fields.py +++ b/tests/modeltests/field_subclassing/fields.py @@ -50,6 +50,9 @@ def get_prep_lookup(self, lookup_type, value): return [] raise TypeError('Invalid lookup type: %r' % lookup_type) +class SmallerField(SmallField): + pass + class JSONField(models.TextField): __metaclass__ = models.SubfieldBase diff --git a/tests/modeltests/field_subclassing/models.py b/tests/modeltests/field_subclassing/models.py index 4a55b72961d0..b0d83365c56c 100644 --- a/tests/modeltests/field_subclassing/models.py +++ b/tests/modeltests/field_subclassing/models.py @@ -5,7 +5,7 @@ from django.db import models from django.utils.encoding import force_unicode -from fields import Small, SmallField, JSONField +from fields import Small, SmallField, SmallerField, JSONField class MyModel(models.Model): @@ -15,5 +15,8 @@ class MyModel(models.Model): def __unicode__(self): return force_unicode(self.name) +class OtherModel(models.Model): + data = SmallerField() + class DataModel(models.Model): data = JSONField() diff --git a/tests/modeltests/field_subclassing/tests.py b/tests/modeltests/field_subclassing/tests.py index ba7148a6547a..25f51600b932 100644 --- a/tests/modeltests/field_subclassing/tests.py +++ b/tests/modeltests/field_subclassing/tests.py @@ -2,7 +2,7 @@ from django.test import TestCase from fields import Small -from models import DataModel, MyModel +from models import DataModel, MyModel, OtherModel class CustomField(TestCase): @@ -73,3 +73,9 @@ def test_custom_field(self): ], lambda m: str(m.data) ) + + def test_field_subclassing(self): + o = OtherModel.objects.create(data=Small("a", "b")) + o = OtherModel.objects.get() + self.assertEqual(o.data.first, "a") + self.assertEqual(o.data.second, "b") From d633a58108970613854e71eab6d8aeb8eacdac78 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 4 Nov 2010 04:47:54 +0000 Subject: [PATCH 410/902] [1.2.X] Converted templates doctests into unittests. We have always been at war with doctests. Backport of [14448]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14449 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/templates/context.py | 32 ++-- tests/regressiontests/templates/custom.py | 16 +- tests/regressiontests/templates/parser.py | 198 +++++++++------------ tests/regressiontests/templates/tests.py | 18 +- tests/regressiontests/templates/unicode.py | 56 +++--- 5 files changed, 129 insertions(+), 191 deletions(-) diff --git a/tests/regressiontests/templates/context.py b/tests/regressiontests/templates/context.py index 7886c8328bd0..05c1dd57b92f 100644 --- a/tests/regressiontests/templates/context.py +++ b/tests/regressiontests/templates/context.py @@ -1,22 +1,16 @@ # coding: utf-8 +from django.template import Context +from django.utils.unittest import TestCase -context_tests = r""" ->>> from django.template import Context ->>> c = Context({'a': 1, 'b': 'xyzzy'}) ->>> c['a'] -1 ->>> c.push() -{} ->>> c['a'] = 2 ->>> c['a'] -2 ->>> c.get('a') -2 ->>> c.pop() -{'a': 2} ->>> c['a'] -1 ->>> c.get('foo', 42) -42 -""" +class ContextTests(TestCase): + def test_context(self): + c = Context({"a": 1, "b": "xyzzy"}) + self.assertEqual(c["a"], 1) + self.assertEqual(c.push(), {}) + c["a"] = 2 + self.assertEqual(c["a"], 2) + self.assertEqual(c.get("a"), 2) + self.assertEqual(c.pop(), {"a": 2}) + self.assertEqual(c["a"], 1) + self.assertEqual(c.get("foo", 42), 42) diff --git a/tests/regressiontests/templates/custom.py b/tests/regressiontests/templates/custom.py index e83b7dc73683..bd1e441e7f24 100644 --- a/tests/regressiontests/templates/custom.py +++ b/tests/regressiontests/templates/custom.py @@ -1,11 +1,11 @@ -from django import test from django import template +from django.utils.unittest import TestCase -custom_filters = """ ->>> t = template.Template("{% load custom %}{{ string|trim:5 }}") ->>> ctxt = template.Context({"string": "abcdefghijklmnopqrstuvwxyz"}) ->>> t.render(ctxt) -u"abcde" -""" - +class CustomTests(TestCase): + def test_filter(self): + t = template.Template("{% load custom %}{{ string|trim:5 }}") + self.assertEqual( + t.render(template.Context({"string": "abcdefghijklmnopqrstuvwxyz"})), + u"abcde" + ) diff --git a/tests/regressiontests/templates/parser.py b/tests/regressiontests/templates/parser.py index 4db54556ed6c..1609c67e2232 100644 --- a/tests/regressiontests/templates/parser.py +++ b/tests/regressiontests/templates/parser.py @@ -1,121 +1,83 @@ """ Testing some internals of the template processing. These are *not* examples to be copied in user code. """ - -token_parsing=r""" -Tests for TokenParser behavior in the face of quoted strings with spaces. - ->>> from django.template import TokenParser - - -Test case 1: {% tag thevar|filter sometag %} - ->>> p = TokenParser("tag thevar|filter sometag") ->>> p.tagname -'tag' ->>> p.value() -'thevar|filter' ->>> p.more() -True ->>> p.tag() -'sometag' ->>> p.more() -False - -Test case 2: {% tag "a value"|filter sometag %} - ->>> p = TokenParser('tag "a value"|filter sometag') ->>> p.tagname -'tag' ->>> p.value() -'"a value"|filter' ->>> p.more() -True ->>> p.tag() -'sometag' ->>> p.more() -False - -Test case 3: {% tag 'a value'|filter sometag %} - ->>> p = TokenParser("tag 'a value'|filter sometag") ->>> p.tagname -'tag' ->>> p.value() -"'a value'|filter" ->>> p.more() -True ->>> p.tag() -'sometag' ->>> p.more() -False -""" - -filter_parsing = r""" ->>> from django.template import FilterExpression, Parser - ->>> c = {'article': {'section': u'News'}} ->>> p = Parser("") ->>> def fe_test(s): return FilterExpression(s, p).resolve(c) - ->>> fe_test('article.section') -u'News' ->>> fe_test('article.section|upper') -u'NEWS' ->>> fe_test(u'"News"') -u'News' ->>> fe_test(u"'News'") -u'News' ->>> fe_test(ur'"Some \"Good\" News"') -u'Some "Good" News' ->>> fe_test(ur"'Some \'Bad\' News'") -u"Some 'Bad' News" - ->>> fe = FilterExpression(ur'"Some \"Good\" News"', p) ->>> fe.filters -[] ->>> fe.var -u'Some "Good" News' - -Filtered variables should reject access of attributes beginning with underscores. - ->>> FilterExpression('article._hidden|upper', p) -Traceback (most recent call last): -... -TemplateSyntaxError: Variables and attributes may not begin with underscores: 'article._hidden' -""" - -variable_parsing = r""" ->>> from django.template import Variable - ->>> c = {'article': {'section': u'News'}} ->>> Variable('article.section').resolve(c) -u'News' ->>> Variable(u'"News"').resolve(c) -u'News' ->>> Variable(u"'News'").resolve(c) -u'News' - -Translated strings are handled correctly. - ->>> Variable('_(article.section)').resolve(c) -u'News' ->>> Variable('_("Good News")').resolve(c) -u'Good News' ->>> Variable("_('Better News')").resolve(c) -u'Better News' - -Escaped quotes work correctly as well. - ->>> Variable(ur'"Some \"Good\" News"').resolve(c) -u'Some "Good" News' ->>> Variable(ur"'Some \'Better\' News'").resolve(c) -u"Some 'Better' News" - -Variables should reject access of attributes beginning with underscores. - ->>> Variable('article._hidden') -Traceback (most recent call last): -... -TemplateSyntaxError: Variables and attributes may not begin with underscores: 'article._hidden' -""" +from django.template import (TokenParser, FilterExpression, Parser, Variable, + TemplateSyntaxError) +from django.utils.unittest import TestCase + + +class ParserTests(TestCase): + def test_token_parsing(self): + # Tests for TokenParser behavior in the face of quoted strings with + # spaces. + + p = TokenParser("tag thevar|filter sometag") + self.assertEqual(p.tagname, "tag") + self.assertEqual(p.value(), "thevar|filter") + self.assertTrue(p.more()) + self.assertEqual(p.tag(), "sometag") + self.assertFalse(p.more()) + + p = TokenParser('tag "a value"|filter sometag') + self.assertEqual(p.tagname, "tag") + self.assertEqual(p.value(), '"a value"|filter') + self.assertTrue(p.more()) + self.assertEqual(p.tag(), "sometag") + self.assertFalse(p.more()) + + p = TokenParser("tag 'a value'|filter sometag") + self.assertEqual(p.tagname, "tag") + self.assertEqual(p.value(), "'a value'|filter") + self.assertTrue(p.more()) + self.assertEqual(p.tag(), "sometag") + self.assertFalse(p.more()) + + def test_filter_parsing(self): + c = {"article": {"section": u"News"}} + p = Parser("") + + def fe_test(s, val): + self.assertEqual(FilterExpression(s, p).resolve(c), val) + + fe_test("article.section", u"News") + fe_test("article.section|upper", u"NEWS") + fe_test(u'"News"', u"News") + fe_test(u"'News'", u"News") + fe_test(ur'"Some \"Good\" News"', u'Some "Good" News') + fe_test(ur'"Some \"Good\" News"', u'Some "Good" News') + fe_test(ur"'Some \'Bad\' News'", u"Some 'Bad' News") + + fe = FilterExpression(ur'"Some \"Good\" News"', p) + self.assertEqual(fe.filters, []) + self.assertEqual(fe.var, u'Some "Good" News') + + # Filtered variables should reject access of attributes beginning with + # underscores. + self.assertRaises(TemplateSyntaxError, + FilterExpression, "article._hidden|upper", p + ) + + def test_variable_parsing(self): + c = {"article": {"section": u"News"}} + self.assertEqual(Variable("article.section").resolve(c), "News") + self.assertEqual(Variable(u'"News"').resolve(c), "News") + self.assertEqual(Variable(u"'News'").resolve(c), "News") + + # Translated strings are handled correctly. + self.assertEqual(Variable("_(article.section)").resolve(c), "News") + self.assertEqual(Variable('_("Good News")').resolve(c), "Good News") + self.assertEqual(Variable("_('Better News')").resolve(c), "Better News") + + # Escaped quotes work correctly as well. + self.assertEqual( + Variable(ur'"Some \"Good\" News"').resolve(c), 'Some "Good" News' + ) + self.assertEqual( + Variable(ur"'Some \'Better\' News'").resolve(c), "Some 'Better' News" + ) + + # Variables should reject access of attributes beginning with + # underscores. + self.assertRaises(TemplateSyntaxError, + Variable, "article._hidden" + ) diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 1dd54779b742..62b2237a83eb 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -21,10 +21,10 @@ from django.utils.safestring import mark_safe from django.utils.tzinfo import LocalTimezone -from context import context_tests -from custom import custom_filters -from parser import token_parsing, filter_parsing, variable_parsing -from unicode import unicode_tests +from context import ContextTests +from custom import CustomTests +from parser import ParserTests +from unicode import UnicodeTests from nodelist import NodelistTest from smartif import * @@ -35,16 +35,6 @@ import filters -# Some other tests we would like to run -__test__ = { - 'unicode': unicode_tests, - 'context': context_tests, - 'token_parsing': token_parsing, - 'filter_parsing': filter_parsing, - 'variable_parsing': variable_parsing, - 'custom_filters': custom_filters, -} - ################################# # Custom template tag for tests # ################################# diff --git a/tests/regressiontests/templates/unicode.py b/tests/regressiontests/templates/unicode.py index e5f308d20215..c8d7309ad787 100644 --- a/tests/regressiontests/templates/unicode.py +++ b/tests/regressiontests/templates/unicode.py @@ -1,37 +1,29 @@ # -*- coding: utf-8 -*- +from django.template import Template, TemplateEncodingError, Context +from django.utils.safestring import SafeData +from django.utils.unittest import TestCase -unicode_tests = ur""" -Templates can be created from unicode strings. ->>> from django.template import * ->>> from django.utils.safestring import SafeData ->>> t1 = Template(u'ŠĐĆŽćžšđ {{ var }}') -Templates can also be created from bytestrings. These are assumed by encoded -using UTF-8. +class UnicodeTests(TestCase): + def test_template(self): + # Templates can be created from unicode strings. + t1 = Template(u'ŠĐĆŽćžšđ {{ var }}') + # Templates can also be created from bytestrings. These are assumed to + # be encoded using UTF-8. + s = '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91 {{ var }}' + t2 = Template(s) + s = '\x80\xc5\xc0' + self.assertRaises(TemplateEncodingError, Template, s) ->>> s = '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91 {{ var }}' ->>> t2 = Template(s) ->>> s = '\x80\xc5\xc0' ->>> Template(s) -Traceback (most recent call last): - ... -TemplateEncodingError: Templates can only be constructed from unicode or UTF-8 strings. + # Contexts can be constructed from unicode or UTF-8 bytestrings. + c1 = Context({"var": "foo"}) + c2 = Context({u"var": "foo"}) + c3 = Context({"var": u"Đđ"}) + c4 = Context({u"var": "\xc4\x90\xc4\x91"}) -Contexts can be constructed from unicode or UTF-8 bytestrings. - ->>> c1 = Context({'var': 'foo'}) ->>> c2 = Context({u'var': 'foo'}) ->>> c3 = Context({'var': u'Đđ'}) ->>> c4 = Context({u'var': '\xc4\x90\xc4\x91'}) - -Since both templates and all four contexts represent the same thing, they all -render the same (and are returned as unicode objects and "safe" objects as -well, for auto-escaping purposes). - ->>> t1.render(c3) == t2.render(c3) -True ->>> isinstance(t1.render(c3), unicode) -True ->>> isinstance(t1.render(c3), SafeData) -True -""" + # Since both templates and all four contexts represent the same thing, + # they all render the same (and are returned as unicode objects and + # "safe" objects as well, for auto-escaping purposes). + self.assertEqual(t1.render(c3), t2.render(c3)) + self.assertIsInstance(t1.render(c3), unicode) + self.assertIsInstance(t1.render(c3), SafeData) From b66df2f49e55886058351bc0aa9cded5500de2f5 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 4 Nov 2010 12:03:01 +0000 Subject: [PATCH 411/902] [1.2.X] Fixed typo in i18n docs. Backport from trunk (r14451). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14452 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/internationalization.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 5fc347c89d04..6c5a7b8405aa 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -179,7 +179,7 @@ cardinality of the elements at play. count = Report.objects.count() d = { 'count': count, - 'name': Report._meta.verbose_name + 'name': Report._meta.verbose_name, 'plural_name': Report._meta.verbose_name_plural } text = ungettext( From 74b566e81ccb0e12372bcd5828e9724f4aee8f1b Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 4 Nov 2010 12:39:11 +0000 Subject: [PATCH 412/902] [1.2.X] Fixed a test setup and isolation bug that was causing PasswordResetTest to fail when run individually Backport of [14455] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14457 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/tests/urls.py | 2 +- django/contrib/auth/tests/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django/contrib/auth/tests/urls.py b/django/contrib/auth/tests/urls.py index f94b8daa7f4b..2003d5e805d0 100644 --- a/django/contrib/auth/tests/urls.py +++ b/django/contrib/auth/tests/urls.py @@ -10,7 +10,7 @@ def remote_user_auth_view(request): return HttpResponse(t.render(c)) # special urls for auth test cases -urlpatterns += patterns('', +urlpatterns = urlpatterns + patterns('', (r'^logout/custom_query/$', 'django.contrib.auth.views.logout', dict(redirect_field_name='follow')), (r'^logout/next_page/$', 'django.contrib.auth.views.logout', dict(next_page='/somewhere/')), (r'^remote_user/$', remote_user_auth_view), diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index 4b32fec4e1cf..808de1052e1c 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -16,7 +16,7 @@ class AuthViewsTestCase(TestCase): Helper base class for all the follow test cases. """ fixtures = ['authtestdata.json'] - urls = 'django.contrib.auth.urls' + urls = 'django.contrib.auth.tests.urls' def setUp(self): self.old_LANGUAGES = settings.LANGUAGES From fca56e845065da91e13b6511cc8cf7b70ad1272e Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 4 Nov 2010 12:39:27 +0000 Subject: [PATCH 413/902] [1.2.X] Fixed #14612 - Password reset page leaks valid user ids publicly. Thanks to PaulM for the report. Backport of [14456] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14458 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/auth/tests/views.py | 6 ++++++ django/contrib/auth/views.py | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index 808de1052e1c..22fdbd6eaf9d 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -82,6 +82,12 @@ def test_confirm_invalid(self): self.assertEquals(response.status_code, 200) self.assert_("The password reset link was invalid" in response.content) + def test_confirm_invalid_user(self): + # Ensure that we get a 200 response for a non-existant user, not a 404 + response = self.client.get('/reset/123456-1-1/') + self.assertEquals(response.status_code, 200) + self.assert_("The password reset link was invalid" in response.content) + def test_confirm_invalid_post(self): # Same as test_confirm_invalid, but trying # to do a POST instead. diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 197799eb9e3b..6f75873fe0cd 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -142,13 +142,13 @@ def password_reset_confirm(request, uidb36=None, token=None, template_name='regi post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete') try: uid_int = base36_to_int(uidb36) - except ValueError: - raise Http404 + user = User.objects.get(id=uid_int) + except (ValueError, User.DoesNotExist): + user = None - user = get_object_or_404(User, id=uid_int) context_instance = RequestContext(request) - if token_generator.check_token(user, token): + if user is not None and token_generator.check_token(user, token): context_instance['validlink'] = True if request.method == 'POST': form = set_password_form(user, request.POST) From 85f4dd6353b9254ccf1dc5b4393b028f2eb3c4ab Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Thu, 4 Nov 2010 14:15:16 +0000 Subject: [PATCH 414/902] [1.2.X] Fixed #11966 -- Made it possible to use a percent sign in a translation message id. Thanks for the patch, Claude Paroz. Backport from trunk (r14459). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14460 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/utils/translation/trans_real.py | 5 +++-- tests/regressiontests/makemessages/extraction.py | 11 +++++++++++ .../regressiontests/makemessages/templates/test.html | 4 +++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index b528f8e586dc..feb2f41ac149 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -439,10 +439,11 @@ def templatize(src): else: singular.append('%%(%s)s' % t.contents) elif t.token_type == TOKEN_TEXT: + contents = t.contents.replace('%', '%%') if inplural: - plural.append(t.contents) + plural.append(contents) else: - singular.append(t.contents) + singular.append(contents) else: if t.token_type == TOKEN_BLOCK: imatch = inline_re.match(t.contents) diff --git a/tests/regressiontests/makemessages/extraction.py b/tests/regressiontests/makemessages/extraction.py index 4b4fa1b07e95..6df6e90211df 100644 --- a/tests/regressiontests/makemessages/extraction.py +++ b/tests/regressiontests/makemessages/extraction.py @@ -34,6 +34,17 @@ def assertNotMsgId(self, msgid, s): return self.assert_(not re.search('^msgid "%s"' % msgid, s, re.MULTILINE)) +class TemplateExtractorTests(ExtractorTests): + + def test_templatize(self): + os.chdir(self.test_dir) + management.call_command('makemessages', locale=LOCALE, verbosity=0) + self.assert_(os.path.exists(self.PO_FILE)) + po_contents = open(self.PO_FILE, 'r').read() + self.assertMsgId('I think that 100%% is more that 50%% of anything.', po_contents) + self.assertMsgId('I think that 100%% is more that 50%% of %\(obj\)s.', po_contents) + + class JavascriptExtractorTests(ExtractorTests): PO_FILE='locale/%s/LC_MESSAGES/djangojs.po' % LOCALE diff --git a/tests/regressiontests/makemessages/templates/test.html b/tests/regressiontests/makemessages/templates/test.html index 2c38b3e4e6d8..96438e1b63ab 100644 --- a/tests/regressiontests/makemessages/templates/test.html +++ b/tests/regressiontests/makemessages/templates/test.html @@ -1,2 +1,4 @@ {% load i18n %} -{% trans "This literal should be included." %} \ No newline at end of file +{% trans "This literal should be included." %} +{% blocktrans %}I think that 100% is more that 50% of anything.{% endblocktrans %} +{% blocktrans with 'txt' as obj %}I think that 100% is more that 50% of {{ obj }}.{% endblocktrans %} \ No newline at end of file From 25452e3aa5c67452e67ceb06f81124e538f9463c Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 4 Nov 2010 21:10:22 +0000 Subject: [PATCH 415/902] [1.2.X] Fixed #14619 -- corrected a typo in the email docs. Backport of [14463]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14464 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/email.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 33564b6f1961..15f44903ff46 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -470,7 +470,7 @@ In-memory backend The ``'locmem'`` backend stores messages in a special attribute of the ``django.core.mail`` module. The ``outbox`` attribute is created when the -first message is send. It's a list with an +first message is sent. It's a list with an :class:`~django.core.mail.EmailMessage` instance for each message that would be send. From 80d6495f6e346917347dd1c8ca277cf0fb40165c Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 6 Nov 2010 03:26:46 +0000 Subject: [PATCH 416/902] [1.2.X] Fixed #14629 -- corrected a misspelling and poor wording in the docs. Thanks to OldTroll for the patch. Backport of [14466]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14467 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/models.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index a5370b6c590d..4467f2259b5d 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -1213,11 +1213,11 @@ cannot create another model field called ``author`` in any class that inherits from that base class. Overriding fields in a parent model leads to difficulties in areas such as -initialising new instances (specifying which field is being intialised in +initialising new instances (specifying which field is being initialized in ``Model.__init__``) and serialization. These are features which normal Python class inheritance doesn't have to deal with in quite the same way, so the difference between Django model inheritance and Python class inheritance isn't -merely arbitrary. +arbitrary. This restriction only applies to attributes which are :class:`~django.db.models.fields.Field` instances. Normal Python attributes @@ -1229,4 +1229,3 @@ different database tables). Django will raise a ``FieldError`` exception if you override any model field in any ancestor model. - From c8c781a9d510def19720aca09b4a6d3cf75853f1 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sat, 6 Nov 2010 04:32:23 +0000 Subject: [PATCH 417/902] [1.2.X] Fixed #14630 -- Increased maximum size of the Oracle tablespace datafile used for tests from 100MB to 200MB. This allows the execution of the full Django test suite without running out of allocated space. Backport of [14468] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14469 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/oracle/creation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index e6e242b9f72e..d06ea223caf5 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -156,7 +156,7 @@ def _execute_test_db_creation(self, cursor, parameters, verbosity): statements = [ """CREATE TABLESPACE %(tblspace)s DATAFILE '%(tblspace)s.dbf' SIZE 20M - REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 100M + REUSE AUTOEXTEND ON NEXT 10M MAXSIZE 200M """, """CREATE TEMPORARY TABLESPACE %(tblspace_temp)s TEMPFILE '%(tblspace_temp)s.dbf' SIZE 20M From 1423593e8fe21072de841183cd76a667b6523ed1 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 6 Nov 2010 09:21:00 +0000 Subject: [PATCH 418/902] [1.2.X] Fixed #14624 -- Updated a few outdated references to CacheMiddleware in the transactions topic guide. Thanks to quinode for the report. Backport of [14470] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14471 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/transactions.txt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 2d99c17a323c..be9d9a864f0b 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -32,13 +32,14 @@ If the view function produces an exception, Django rolls back any pending transactions. To activate this feature, just add the ``TransactionMiddleware`` middleware to -your ``MIDDLEWARE_CLASSES`` setting:: +your :setting:`MIDDLEWARE_CLASSES` setting:: MIDDLEWARE_CLASSES = ( + 'django.middleware.cache.UpdateCacheMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', - 'django.middleware.cache.CacheMiddleware', 'django.middleware.transaction.TransactionMiddleware', + 'django.middleware.cache.FetchFromCacheMiddleware', ) The order is quite important. The transaction middleware applies not only to @@ -46,9 +47,12 @@ view functions, but also for all middleware modules that come after it. So if you use the session middleware after the transaction middleware, session creation will be part of the transaction. -An exception is ``CacheMiddleware``, which is never affected. The cache -middleware uses its own database cursor (which is mapped to its own database -connection internally). +The various cache middlewares are an exception: +:class:`~django.middleware.cache.CacheMiddleware`, +:class:`~django.middleware.cache.UpdateCacheMiddleware`, and +:class:`~django.middleware.cache.FetchFromCacheMiddleware` are never affected. +Even when using database caching, Django's cache backend uses its own +database cursor (which is mapped to its own database connection internally). Controlling transaction management in views =========================================== From 24ce5033c835b76556893001758ef7e12f305373 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sat, 6 Nov 2010 10:09:35 +0000 Subject: [PATCH 419/902] [1.2.X] Fixed #14627 -- Made Tutorial 3 more explicit regarding the transformations the URLconf undergoes in the final two sections, and gave an example of concatenating two patterns() in the process. Thanks to filmer for the report. Backport of [14472] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14473 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial03.txt | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 5b2bf0ccec32..8f6061a14e77 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -454,6 +454,27 @@ first argument to :func:`~django.conf.urls.defaults.patterns`, like so:: This is functionally identical to the previous formatting. It's just a bit tidier. +Since you generally don't want the prefix for one app to be applied to every +callback in your URLconf, you can concatenate multiple +:func:`~django.conf.urls.defaults.patterns`. Your full ``mysite/urls.py`` might +now look like this:: + + from django.conf.urls.defaults import * + + from django.contrib import admin + admin.autodiscover() + + urlpatterns = patterns('polls.views', + (r'^polls/$', 'index'), + (r'^polls/(?P\d+)/$', 'detail'), + (r'^polls/(?P\d+)/results/$', 'results'), + (r'^polls/(?P\d+)/vote/$', 'vote'), + ) + + urlpatterns += patterns('', + (r'^admin/', include(admin.site.urls)), + ) + Decoupling the URLconfs ======================= @@ -472,18 +493,20 @@ URLs within the app directory. Copy the file ``mysite/urls.py`` to ``polls/urls.py``. Then, change ``mysite/urls.py`` to remove the poll-specific URLs and insert an -:func:`~django.conf.urls.defaults.include`:: +:func:`~django.conf.urls.defaults.include`, leaving you with:: # This also imports the include function from django.conf.urls.defaults import * - - # ... + + from django.contrib import admin + admin.autodiscover() + urlpatterns = patterns('', (r'^polls/', include('polls.urls')), - # ... + (r'^admin/', include(admin.site.urls)), ) -:func:`~django.conf.urls.defaults.include`, simply, references another URLconf. +:func:`~django.conf.urls.defaults.include` simply references another URLconf. Note that the regular expression doesn't have a ``$`` (end-of-string match character) but has the trailing slash. Whenever Django encounters :func:`~django.conf.urls.defaults.include`, it chops off whatever part of the From d8e0052d454f581513cd8d3b683093a1f33f12e8 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sat, 6 Nov 2010 16:32:08 +0000 Subject: [PATCH 420/902] [1.2.X] Fix for running GEOS/GDAL tests on Python 2.6 and below. Backport of r14474 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14475 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/geometry/test_data.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/django/contrib/gis/geometry/test_data.py b/django/contrib/gis/geometry/test_data.py index 553db471f4a6..4e073487a5d7 100644 --- a/django/contrib/gis/geometry/test_data.py +++ b/django/contrib/gis/geometry/test_data.py @@ -23,6 +23,11 @@ def tuplize(seq): return seq +def strconvert(d): + "Converts all keys in dictionary to str type." + return dict([(str(k), v) for k, v in d.iteritems()]) + + def get_ds_file(name, ext): return os.path.join(TEST_DATA, name, @@ -81,7 +86,7 @@ class TestGeomSet(object): """ def __init__(self, **kwargs): for key, value in kwargs.items(): - setattr(self, key, [TestGeom(**kwargs) for kwargs in value]) + setattr(self, key, [TestGeom(**strconvert(kw)) for kw in value]) class TestDataMixin(object): @@ -96,5 +101,5 @@ def geometries(self): # Load up the test geometry data from fixture into global. gzf = gzip.GzipFile(os.path.join(TEST_DATA, 'geometries.json.gz')) geometries = simplejson.loads(gzf.read()) - GEOMETRIES = TestGeomSet(**geometries) + GEOMETRIES = TestGeomSet(**strconvert(geometries)) return GEOMETRIES From 2946a657cd339345dd2a6ba56045ba0a37b9995c Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 7 Nov 2010 01:00:05 +0000 Subject: [PATCH 421/902] [1.2.X] Fixed #8325 -- Reorganization and expansion of the login_required decorator docs to make it clearer how the redirect_field_name parameter works and improve the overall flow of the text. Thanks to Robert Reeves for the report. Backport of [14480] from trunk (sans 1.3-specific changes). git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14481 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/auth.txt | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 6c144d9d4f0b..332d61419a90 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -693,7 +693,7 @@ login page:: The login_required decorator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. function:: decorators.login_required() +.. function:: decorators.login_required([redirect_field_name=REDIRECT_FIELD_NAME]) As a shortcut, you can use the convenient :func:`~django.contrib.auth.decorators.login_required` decorator:: @@ -703,35 +703,38 @@ The login_required decorator @login_required def my_view(request): ... + + :func:`~django.contrib.auth.decorators.login_required` does the following: + + * If the user isn't logged in, redirect to + :setting:`settings.LOGIN_URL `, passing the current absolute + path in the query string. Example: ``/accounts/login/?next=/polls/3/``. - :func:`~django.contrib.auth.decorators.login_required` also takes an - optional ``redirect_field_name`` parameter. Example:: + * If the user is logged in, execute the view normally. The view code is + free to assume the user is logged in. + By default, the path that the user should be redirected to upon + successful authentication is stored in a query string parameter called + ``"next"``. If you would prefer to use a different name for this parameter, + :func:`~django.contrib.auth.decorators.login_required` takes an + optional ``redirect_field_name`` parameter:: from django.contrib.auth.decorators import login_required - @login_required(redirect_field_name='redirect_to') + @login_required(redirect_field_name='my_redirect_field') def my_view(request): ... - :func:`~django.contrib.auth.decorators.login_required` does the following: - - * If the user isn't logged in, redirect to - :setting:`settings.LOGIN_URL ` (``/accounts/login/`` by - default), passing the current absolute URL in the query string. The - name of the GET argument is determined by the ``redirect_field_name`` - argument provided to the decorator. The default argument name is - ``next``. For example: - ``/accounts/login/?next=/polls/3/``. - - * If the user is logged in, execute the view normally. The view code is - free to assume the user is logged in. + If you provide a value to ``redirect_field_name``, you will most + likely need to customize your login template as well, since the template + context variable which stores the redirect path will use the value of + ``redirect_field_name`` as it's key rather than ``"next"`` (the default). -Note that you'll need to map the appropriate Django view to -:setting:`settings.LOGIN_URL `. For example, using the defaults, add -the following line to your URLconf:: + Note that you'll need to map the appropriate Django view to + :setting:`settings.LOGIN_URL `. For example, using the defaults, + add the following line to your URLconf:: - (r'^accounts/login/$', 'django.contrib.auth.views.login'), + (r'^accounts/login/$', 'django.contrib.auth.views.login'), .. function:: views.login(request, [template_name, redirect_field_name, authentication_form]) From 969ac914ff83f8f01051c98d909c7a590c945530 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 7 Nov 2010 01:44:32 +0000 Subject: [PATCH 422/902] [1.2.X] Fixed #10904 -- Corrected inappropriate usage of the term "absolute URL" throughout the docs. Replaced with the (RFC 2396-compliant) terms "absolute path reference" or "absolute path" as appropriate for the context. Thanks to sharan666 for the report, and Malcolm, Chris, and dwillis for their work in supplying a solution and patch. Backport of [14482] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14483 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/faq/usage.txt | 2 +- docs/intro/tutorial03.txt | 4 ++-- docs/ref/contrib/sitemaps.txt | 19 ++++++++++--------- docs/ref/contrib/syndication.txt | 2 +- docs/ref/models/fields.txt | 2 +- docs/ref/models/instances.txt | 2 +- docs/ref/request-response.txt | 6 +++--- docs/ref/templates/builtins.txt | 7 ++++--- 8 files changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/faq/usage.txt b/docs/faq/usage.txt index 856b97c35c51..c11514c4cdaf 100644 --- a/docs/faq/usage.txt +++ b/docs/faq/usage.txt @@ -63,7 +63,7 @@ Using a :class:`~django.db.models.FileField` or an (relative to :setting:`MEDIA_ROOT`). You'll most likely want to use the convenience :attr:`~django.core.files.File.url` attribute provided by Django. For example, if your :class:`~django.db.models.ImageField` is - called ``mug_shot``, you can get the absolute URL to your image in a + called ``mug_shot``, you can get the absolute path to your image in a template with ``{{ object.mug_shot.url }}``. How do I make a variable available to all my templates? diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 8f6061a14e77..0843d9e48ff9 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -538,9 +538,9 @@ this:: The idea behind :func:`~django.conf.urls.defaults.include` and URLconf decoupling is to make it easy to plug-and-play URLs. Now that polls are in their own URLconf, they can be placed under "/polls/", or under "/fun_polls/", or -under "/content/polls/", or any other URL root, and the app will still work. +under "/content/polls/", or any other path root, and the app will still work. -All the poll app cares about is its relative URLs, not its absolute URLs. +All the poll app cares about is its relative path, not its absolute path. When you're comfortable with writing views, read :doc:`part 4 of this tutorial ` to learn about simple form processing and generic views. diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 50bc10c4b645..b58b55cab4d3 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -143,21 +143,22 @@ Sitemap class reference **Optional.** Either a method or attribute. - If it's a method, it should return the absolute URL for a given object as - returned by :attr:`~Sitemap.items()`. + If it's a method, it should return the absolute path for a given object + as returned by :attr:`~Sitemap.items()`. - If it's an attribute, its value should be a string representing an absolute URL - to use for *every* object returned by :attr:`~Sitemap.items()`. + If it's an attribute, its value should be a string representing an + absolute path to use for *every* object returned by + :attr:`~Sitemap.items()`. - In both cases, "absolute URL" means a URL that doesn't include the protocol or - domain. Examples: + In both cases, "absolute path" means a URL that doesn't include the + protocol or domain. Examples: * Good: :file:`'/foo/bar/'` * Bad: :file:`'example.com/foo/bar/'` * Bad: :file:`'http://example.com/foo/bar/'` - If :attr:`~Sitemap.location` isn't provided, the framework will call the - ``get_absolute_url()`` method on each object as returned by + If :attr:`~Sitemap.location` isn't provided, the framework will call + the ``get_absolute_url()`` method on each object as returned by :attr:`~Sitemap.items()`. .. attribute:: Sitemap.lastmod @@ -300,7 +301,7 @@ that: :func:`django.contrib.sitemaps.ping_google()`. .. function:: ping_google :func:`ping_google` takes an optional argument, :data:`sitemap_url`, - which should be the absolute URL of your site's sitemap (e.g., + which should be the absolute path to your site's sitemap (e.g., :file:`'/sitemap.xml'`). If this argument isn't provided, :func:`ping_google` will attempt to figure out your sitemap by performing a reverse looking in your URLconf. diff --git a/docs/ref/contrib/syndication.txt b/docs/ref/contrib/syndication.txt index a12d646a0816..04f14b553178 100644 --- a/docs/ref/contrib/syndication.txt +++ b/docs/ref/contrib/syndication.txt @@ -274,7 +274,7 @@ comes directly from your :setting:`LANGUAGE_CODE` setting. URLs ---- -The :attr:`link` method/attribute can return either an absolute URL (e.g. +The :attr:`link` method/attribute can return either an absolute path (e.g. :file:`"/blog/"`) or a URL with the fully-qualified domain and protocol (e.g. ``"http://www.example.com/blog/"``). If :attr:`link` doesn't return the domain, the syndication framework will insert the domain of the current site, according diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 02ed60833ff4..0d60e0543f71 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -539,7 +539,7 @@ takes a few steps: (relative to :setting:`MEDIA_ROOT`). You'll most likely want to use the convenience :attr:`~django.core.files.File.url` function provided by Django. For example, if your :class:`ImageField` is called ``mug_shot``, - you can get the absolute URL to your image in a template with + you can get the absolute path to your image in a template with ``{{ object.mug_shot.url }}``. For example, say your :setting:`MEDIA_ROOT` is set to ``'/home/media'``, and diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index fb951ce6a7bb..1730ec69c01b 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -491,7 +491,7 @@ Similarly, if you had a URLconf entry that looked like:: Notice that we specify an empty sequence for the second parameter in this case, because we only want to pass keyword parameters, not positional ones. -In this way, you're tying the model's absolute URL to the view that is used +In this way, you're tying the model's absolute path to the view that is used to display it, without repeating the URL information anywhere. You can still use the ``get_absolute_url`` method in templates, as before. diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 0eaa4b6e22aa..943d26d67833 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -554,9 +554,9 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in .. class:: HttpResponseRedirect The constructor takes a single argument -- the path to redirect to. This - can be a fully qualified URL (e.g. ``'http://www.yahoo.com/search/'``) or an - absolute URL with no domain (e.g. ``'/search/'``). Note that this returns - an HTTP status code 302. + can be a fully qualified URL (e.g. ``'http://www.yahoo.com/search/'``) or + an absolute path with no domain (e.g. ``'/search/'``). Note that this + returns an HTTP status code 302. .. class:: HttpResponsePermanentRedirect diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index f65e94b825ed..4a80a1593a51 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -848,9 +848,10 @@ The argument tells which template bit to output: url ~~~ -Returns an absolute URL (i.e., a URL without the domain name) matching a given -view function and optional parameters. This is a way to output links without -violating the DRY principle by having to hard-code URLs in your templates:: +Returns an absolute path reference (a URL without the domain name) matching a +given view function and optional parameters. This is a way to output links +without violating the DRY principle by having to hard-code URLs in your +templates:: {% url path.to.some_view v1 v2 %} From 7509bdf86b1bd805f9866fbc94697ea44ada65c0 Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 7 Nov 2010 09:23:12 +0000 Subject: [PATCH 423/902] [1.2.X] Fixed #12975 -- Moved the docs for contrib.admindocs out of the template docs and into their own reference section, and significantly improved the documentation of what admindocs can do. Thanks to jabapyth for the report, and whiteinge for the patch. Backport of [14484] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14485 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/custom-model-fields.txt | 9 +- docs/index.txt | 2 +- docs/ref/contrib/admin/admindocs.txt | 161 +++++++++++++++++++++++++++ docs/ref/contrib/admin/index.txt | 1 + docs/ref/middleware.txt | 2 +- docs/ref/templates/builtins.txt | 4 +- docs/topics/templates.txt | 58 ++-------- 7 files changed, 183 insertions(+), 54 deletions(-) create mode 100644 docs/ref/contrib/admin/admindocs.txt diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index fa4c07fed2f3..1840c5b6f260 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -292,10 +292,11 @@ Documenting your Custom Field As always, you should document your field type, so users will know what it is. In addition to providing a docstring for it, which is useful for developers, you can also allow users of the admin app to see a short description of the -field type via the ``django.contrib.admindocs`` application. To do this simply -provide descriptive text in a ``description`` class attribute of your custom field. -In the above example, the type description displayed by the ``admindocs`` application -for a ``HandField`` will be 'A hand of cards (bridge style)'. +field type via the :doc:`django.contrib.admindocs +` application. To do this simply provide +descriptive text in a ``description`` class attribute of your custom field. In +the above example, the type description displayed by the ``admindocs`` +application for a ``HandField`` will be 'A hand of cards (bridge style)'. Useful methods -------------- diff --git a/docs/index.txt b/docs/index.txt index c031b03f5411..d6c2d4f19e9b 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -161,7 +161,7 @@ The development process Other batteries included ======================== - * :doc:`Admin site ` | :doc:`Admin actions ` + * :doc:`Admin site ` | :doc:`Admin actions ` | :doc:`Admin documentation generator` * :doc:`Authentication ` * :doc:`Cache system ` * :doc:`Conditional content processing ` diff --git a/docs/ref/contrib/admin/admindocs.txt b/docs/ref/contrib/admin/admindocs.txt new file mode 100644 index 000000000000..882163e9eba5 --- /dev/null +++ b/docs/ref/contrib/admin/admindocs.txt @@ -0,0 +1,161 @@ +======================================== +The Django admin documentation generator +======================================== + +.. module:: django.contrib.admindocs + :synopsis: Django's admin documentation generator. + +.. currentmodule:: django.contrib.admindocs + +Django's :mod:`~django.contrib.admindocs` app pulls documentation from the +docstrings of models, views, template tags, and template filters for any app in +:setting:`INSTALLED_APPS` and makes that documentation available from the +:mod:`Django admin `. + +In addition to providing offline documentation for all template tags and +template filters that ship with Django, you may utilize admindocs to quickly +document your own code. + +Overview +======== + +To activate the :mod:`~django.contrib.admindocs`, you will need to do +the following: + + * Add :mod:`django.contrib.admindocs` to your :setting:`INSTALLED_APPS`. + * Add ``(r'^admin/doc/', include('django.contrib.admindocs.urls'))`` to + your :data:`urlpatterns`. Make sure it's included *before* the + ``r'^admin/'`` entry, so that requests to ``/admin/doc/`` don't get + handled by the latter entry. + * Install the docutils Python module (http://docutils.sf.net/). + * **Optional:** Linking to templates requires the :setting:`ADMIN_FOR` + setting to be configured. + * **Optional:** Using the admindocs bookmarklets requires the + :mod:`XViewMiddleware` to be installed. + +Once those steps are complete, you can start browsing the documentation by +going to your admin interface and clicking the "Documentation" link in the +upper right of the page. + +Documentation helpers +===================== + +The following special markup can be used in your docstrings to easily create +hyperlinks to other components: + +================= ======================= +Django Component reStructuredText roles +================= ======================= +Models ``:model:`appname.ModelName``` +Views ``:view:`appname.view_name``` +Template tags ``:tag:`tagname``` +Template filters ``:filter:`filtername``` +Templates ``:template:`path/to/template.html``` +================= ======================= + +Model reference +=============== + +The **models** section of the ``admindocs`` page describes each model in the +system along with all the fields and methods available on it. Relationships to +other models appear as hyperlinks. Descriptions are pulled from ``help_text`` +attributes on fields or from docstrings on model methods. + +A model with useful documentation might look like this:: + + class BlogEntry(models.Model): + """ + Stores a single blog entry, related to :model:`blog.Blog` and + :model:`auth.User`. + + """ + slug = models.SlugField(help_text="A short label, generally used in URLs.") + author = models.ForeignKey(User) + blog = models.ForeignKey(Blog) + ... + + def publish(self): + """Makes the blog entry live on the site.""" + ... + +View reference +============== + +Each URL in your site has a separate entry in the ``admindocs`` page, and +clicking on a given URL will show you the corresponding view. Helpful things +you can document in your view function docstrings include: + + * A short description of what the view does. + * The **context**, or a list of variables available in the view's template. + * The name of the template or templates that are used for that view. + +For example:: + + from myapp.models import MyModel + + def my_view(request, slug): + """ + Display an individual :model:`myapp.MyModel`. + + **Context** + + ``RequestContext`` + + ``mymodel`` + An instance of :model:`myapp.MyModel`. + + **Template:** + + :template:`myapp/my_template.html` + + """ + return render_to_response('myapp/my_template.html', { + 'mymodel': MyModel.objects.get(slug=slug) + }, context_instance=RequestContext(request)) + + +Template tags and filters reference +=================================== + +The **tags** and **filters** ``admindocs`` sections describe all the tags and +filters that come with Django (in fact, the :ref:`built-in tag reference +` and :ref:`built-in filter reference +` documentation come directly from those +pages). Any tags or filters that you create or are added by a third-party app +will show up in these sections as well. + + +Template reference +================== + +While ``admindocs`` does not include a place to document templates by +themselves, if you use the ``:template:`path/to/template.html``` syntax in a +docstring the resulting page will verify the path of that template with +Djangos :ref:`template loaders `. This can be a handy way to +check if the specified template exists and to show where on the filesystem that +template is stored. + + +Included Bookmarklets +===================== + +Several useful bookmarklets are available from the ``admindocs`` page: + + Documentation for this page + Jumps you from any page to the documentation for the view that generates + that page. + + Show object ID + Shows the content-type and unique ID for pages that represent a single + object. + + Edit this object + Jumps to the admin page for pages that represent a single object. + +Using these bookmarklets requires that you are either logged into the +:mod:`Django admin ` as a +:class:`~django.contrib.auth.models.User` with +:attr:`~django.contrib.auth.models.User.is_staff` set to `True`, or +that the :mod:`django.middleware.doc` middleware and +:mod:`XViewMiddleware ` are installed and you +are accessing the site from an IP address listed in :setting:`INTERNAL_IPS`. diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 055057677c5f..ac517e868b20 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -49,6 +49,7 @@ Other topics :maxdepth: 1 actions + admindocs .. seealso:: diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index fa275d92d709..eb746e448e6f 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -84,7 +84,7 @@ View metadata middleware Sends custom ``X-View`` HTTP headers to HEAD requests that come from IP addresses defined in the :setting:`INTERNAL_IPS` setting. This is used by -Django's automatic documentation system. +Django's :doc:`automatic documentation system `. GZIP middleware --------------- diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 4a80a1593a51..9313e2ffe797 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -3,8 +3,8 @@ Built-in template tags and filters ================================== This document describes Django's built-in template tags and filters. It is -recommended that you use the :ref:`automatic documentation -`, if available, as this will also include +recommended that you use the :doc:`automatic documentation +`, if available, as this will also include documentation for any custom tags or filters installed. .. _ref-templates-builtins-tags: diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index 5586ed8c1246..d249bd3ccd7f 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -100,9 +100,6 @@ If you use a variable that doesn't exist, the template system will insert the value of the ``TEMPLATE_STRING_IF_INVALID`` setting, which is set to ``''`` (the empty string) by default. -See `Using the built-in reference`_, below, for help on finding what variables -are available in a given template. - Filters ======= @@ -161,6 +158,12 @@ Again, these are just a few examples; see the :ref:`built-in filter reference You can also create your own custom template filters; see :doc:`/howto/custom-template-tags`. +.. seealso:: + + Django's admin interface can include a complete reference of all template + tags and filters available for a given site. See + :doc:`/ref/contrib/admin/admindocs`. + Tags ==== @@ -217,6 +220,12 @@ tag reference ` for the complete list. You can also create your own custom template tags; see :doc:`/howto/custom-template-tags`. +.. seealso:: + + Django's admin interface can include a complete reference of all template + tags and filters available for a given site. See + :doc:`/ref/contrib/admin/admindocs`. + Comments ======== @@ -569,49 +578,6 @@ This doesn't affect what happens to data coming from the variable itself. The variable's contents are still automatically escaped, if necessary, because they're beyond the control of the template author. -.. _template-built-in-reference: - -Using the built-in reference -============================ - -Django's admin interface includes a complete reference of all template tags and -filters available for a given site. To activate it, follow these steps: - - * Add :mod:`django.contrib.admindocs` to your :setting:`INSTALLED_APPS`. - * Add ``(r'^admin/doc/', include('django.contrib.admindocs.urls'))`` to your - :data:`urlpatterns`. Make sure it's included *before* the ``r'^admin/'`` - entry, so that requests to ``/admin/doc/`` don't get handled by the - latter entry. - * Install the docutils module (http://docutils.sf.net/). - -After you've followed those steps, you can start browsing the documentation by -going to your admin interface and clicking the "Documentation" link in the -upper right of the page. - -The reference is divided into 4 sections: tags, filters, models, and views. - -The **tags** and **filters** sections describe all the built-in tags (in fact, -the tag and filter references below come directly from those pages) as well as -any custom tag or filter libraries available. - -The **views** page is the most valuable. Each URL in your site has a separate -entry here, and clicking on a URL will show you: - - * The name of the view function that generates that view. - * A short description of what the view does. - * The **context**, or a list of variables available in the view's template. - * The name of the template or templates that are used for that view. - -Each view documentation page also has a bookmarklet that you can use to jump -from any page to the documentation page for that view. - -Because Django-powered sites usually use database objects, the **models** -section of the documentation page describes each type of object in the system -along with all the fields available on that object. - -Taken together, the documentation pages should tell you every tag, filter, -variable and object available to you in a given template. - .. _loading-custom-template-libraries: Custom tag and filter libraries From 1431bcc70ec46de34080ad0f44c64740e1520704 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 7 Nov 2010 21:02:49 +0000 Subject: [PATCH 424/902] [1.2.X] Fixed some Oracle backend test DB creation code bugs. Made saving of modified settings compatible with multi-db, removed dead and superfluous code. Backport of [14489] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14490 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/oracle/creation.py | 42 ++++----------------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index d06ea223caf5..940af15d0657 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -1,5 +1,4 @@ import sys, time -from django.core import management from django.db.backends.creation import BaseDatabaseCreation TEST_DATABASE_PREFIX = 'test_' @@ -39,7 +38,9 @@ class DatabaseCreation(BaseDatabaseCreation): 'URLField': 'VARCHAR2(%(max_length)s)', } - remember = {} + def __init__(self, connection): + self.remember = {} + super(DatabaseCreation, self).__init__(connection) def _create_test_db(self, verbosity=1, autoclobber=False): TEST_NAME = self._test_database_name() @@ -135,9 +136,6 @@ def _destroy_test_db(self, test_database_name, verbosity=1): 'tblspace_temp': TEST_TBLSPACE_TMP, } - self.remember['user'] = self.connection.settings_dict['USER'] - self.remember['passwd'] = self.connection.settings_dict['PASSWORD'] - cursor = self.connection.cursor() time.sleep(1) # To avoid "database is being accessed by other users" errors. if self._test_user_create(): @@ -214,35 +212,13 @@ def _test_database_name(self): name = self.connection.settings_dict['TEST_NAME'] except AttributeError: pass - except: - raise return name def _test_database_create(self): - name = True - try: - if self.connection.settings_dict['TEST_CREATE']: - name = True - else: - name = False - except KeyError: - pass - except: - raise - return name + return self.connection.settings_dict.get('TEST_CREATE', True) def _test_user_create(self): - name = True - try: - if self.connection.settings_dict['TEST_USER_CREATE']: - name = True - else: - name = False - except KeyError: - pass - except: - raise - return name + return self.connection.settings_dict.get('TEST_USER_CREATE', True) def _test_database_user(self): name = TEST_DATABASE_PREFIX + self.connection.settings_dict['USER'] @@ -251,8 +227,6 @@ def _test_database_user(self): name = self.connection.settings_dict['TEST_USER'] except KeyError: pass - except: - raise return name def _test_database_passwd(self): @@ -262,8 +236,6 @@ def _test_database_passwd(self): name = self.connection.settings_dict['TEST_PASSWD'] except KeyError: pass - except: - raise return name def _test_database_tblspace(self): @@ -273,8 +245,6 @@ def _test_database_tblspace(self): name = self.connection.settings_dict['TEST_TBLSPACE'] except KeyError: pass - except: - raise return name def _test_database_tblspace_tmp(self): @@ -284,6 +254,4 @@ def _test_database_tblspace_tmp(self): name = self.connection.settings_dict['TEST_TBLSPACE_TMP'] except KeyError: pass - except: - raise return name From 0164ee0082614c188d26ba59ee994700bcf31ab2 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 7 Nov 2010 21:46:03 +0000 Subject: [PATCH 425/902] [1.2.X] Completed and enhanced links to database-specific notes from the install docs. Backport of [14491] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14492 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/install.txt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/topics/install.txt b/docs/topics/install.txt index ad99b33890bd..87e99b3aa63d 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -84,22 +84,24 @@ database bindings are installed. * If you're using PostgreSQL, you'll need the psycopg_ package. Django supports both version 1 and 2. (When you configure Django's database layer, specify either ``postgresql`` [for version 1] or ``postgresql_psycopg2`` [for version 2].) + You might want to refer to our :ref:`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 MySQLdb_, version 1.2.1p2 or higher. You - will also want to read the database-specific notes for the :doc:`MySQL - backend `. + will also want to read the database-specific :ref:`notes for the MySQL + backend `. * If you're using SQLite and Python 2.4, you'll need pysqlite_. Use version 2.0.3 or higher. Python 2.5 ships with an SQLite wrapper in the standard library, so you don't need to install anything extra in that case. Please - read the SQLite backend :ref:`notes`. + read the :ref:`SQLite backend notes `. * If you're using Oracle, you'll need a copy of cx_Oracle_, but please - read the database-specific notes for the - :ref:`Oracle backend ` for important information - regarding supported versions of both Oracle and ``cx_Oracle``. + read the database-specific :ref:`notes for the Oracle backend ` + for important information regarding supported versions of both Oracle and + ``cx_Oracle``. * If you're using an unofficial 3rd party backend, please consult the documentation provided for any additional requirements. From 6e201bcae22adcdf6894e56ad7426cc624bf85fd Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 7 Nov 2010 23:03:47 +0000 Subject: [PATCH 426/902] [1.2.X] Fixed #11877 -- Documented that HttpRequest.get_host() fails behind multiple reverse proxies, and added an example middleware solution. Thanks to Tom Evans for the report, and arnav for the patch. Backport of [14493] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14494 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/request-response.txt | 39 ++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 943d26d67833..07d1c425f55b 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -189,16 +189,39 @@ Methods .. method:: HttpRequest.get_host() - .. versionadded:: 1.0 - - Returns the originating host of the request using information from the - ``HTTP_X_FORWARDED_HOST`` and ``HTTP_HOST`` headers (in that order). If - they don't provide a value, the method uses a combination of - ``SERVER_NAME`` and ``SERVER_PORT`` as detailed in `PEP 333`_. + .. versionadded:: 1.0 - .. _PEP 333: http://www.python.org/dev/peps/pep-0333/ + Returns the originating host of the request using information from the + ``HTTP_X_FORWARDED_HOST`` and ``HTTP_HOST`` headers (in that order). If + they don't provide a value, the method uses a combination of + ``SERVER_NAME`` and ``SERVER_PORT`` as detailed in `PEP 333`_. + + .. _PEP 333: http://www.python.org/dev/peps/pep-0333/ + + Example: ``"127.0.0.1:8000"`` + + .. note:: The :meth:`~HttpRequest.get_host()` method fails when the host is + behind multiple proxies. One solution is to use middleware to rewrite + the proxy headers, as in the following example:: + + class MultipleProxyMiddleware(object): + FORWARDED_FOR_FIELDS = [ + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_FORWARDED_HOST', + 'HTTP_X_FORWARDED_SERVER', + ] + + def process_request(self, request): + """ + Rewrites the proxy headers so that only the most + recent proxy is used. + """ + for field in self.FORWARDED_FOR_FIELDS: + if field in request.META: + if ',' in request.META[field]: + parts = request.META[field].split(',') + request.META[field] = parts[-1].strip() - Example: ``"127.0.0.1:8000"`` .. method:: HttpRequest.get_full_path() From 488c9b3f115b9deb5e4b0f4e62109da6aad02cfa Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Sun, 7 Nov 2010 23:10:35 +0000 Subject: [PATCH 427/902] [1.2.X] Backporting myself to the 1.2.X docs/internals/committers.txt... and fixing the typo in Jeremy's bio (again). :) Backport of [14056] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14495 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/internals/committers.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index bc31009afc7e..cd623988c6a0 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -292,7 +292,7 @@ Matt Boersma Matt is also responsible for Django's Oracle support. Jeremy Dunck - Jeremy the lead developer of Pegasus News, a personalized local site based + Jeremy is the lead developer of Pegasus News, a personalized local site based in Dallas, Texas. An early contributor to Greasemonkey and Django, he sees technology as a tool for communication and access to knowledge. @@ -311,6 +311,18 @@ Jeremy Dunck .. _simon meers: http://simonmeers.com/ +`Gabriel Hurley`_ + Gabriel has been working with Django since 2008, shortly after the 1.0 + release. Convinced by his business partner that Python and Django were the + right direction for the company, he couldn't have been more happy with the + decision. His contributions range across many areas in Django, but years of + copy-editing and an eye for detail lead him to be particularly at home + while working on Django's documentation. + + Gabriel works as a web developer in Berkeley, CA, USA. + +.. _gabriel hurley: http://strikeawe.com/ + Developers Emeritus =================== From cbe27e3cc4478431bb90dd22ecf81d0948dc7b7a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 8 Nov 2010 04:42:13 +0000 Subject: [PATCH 428/902] [1.2.X] Corrected some backporting errors in the template tests. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14496 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/templates/context.py | 3 ++- tests/regressiontests/templates/custom.py | 3 ++- tests/regressiontests/templates/parser.py | 3 ++- tests/regressiontests/templates/unicode.py | 7 ++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/regressiontests/templates/context.py b/tests/regressiontests/templates/context.py index 05c1dd57b92f..394de942c713 100644 --- a/tests/regressiontests/templates/context.py +++ b/tests/regressiontests/templates/context.py @@ -1,6 +1,7 @@ # coding: utf-8 +from unittest import TestCase + from django.template import Context -from django.utils.unittest import TestCase class ContextTests(TestCase): diff --git a/tests/regressiontests/templates/custom.py b/tests/regressiontests/templates/custom.py index bd1e441e7f24..b34619887475 100644 --- a/tests/regressiontests/templates/custom.py +++ b/tests/regressiontests/templates/custom.py @@ -1,5 +1,6 @@ +from unittest import TestCase + from django import template -from django.utils.unittest import TestCase class CustomTests(TestCase): diff --git a/tests/regressiontests/templates/parser.py b/tests/regressiontests/templates/parser.py index 1609c67e2232..93e8118494b5 100644 --- a/tests/regressiontests/templates/parser.py +++ b/tests/regressiontests/templates/parser.py @@ -1,9 +1,10 @@ """ Testing some internals of the template processing. These are *not* examples to be copied in user code. """ +from unittest import TestCase + from django.template import (TokenParser, FilterExpression, Parser, Variable, TemplateSyntaxError) -from django.utils.unittest import TestCase class ParserTests(TestCase): diff --git a/tests/regressiontests/templates/unicode.py b/tests/regressiontests/templates/unicode.py index c8d7309ad787..05b0e22c2ede 100644 --- a/tests/regressiontests/templates/unicode.py +++ b/tests/regressiontests/templates/unicode.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- +from unittest import TestCase + from django.template import Template, TemplateEncodingError, Context from django.utils.safestring import SafeData -from django.utils.unittest import TestCase class UnicodeTests(TestCase): @@ -25,5 +26,5 @@ def test_template(self): # they all render the same (and are returned as unicode objects and # "safe" objects as well, for auto-escaping purposes). self.assertEqual(t1.render(c3), t2.render(c3)) - self.assertIsInstance(t1.render(c3), unicode) - self.assertIsInstance(t1.render(c3), SafeData) + self.assertTrue(isinstance(t1.render(c3), unicode)) + self.assertTrue(isinstance(t1.render(c3), SafeData)) From 6e5eb99c55ab5549b46bdf1d7b9963321d60eaff Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Mon, 8 Nov 2010 20:43:30 +0000 Subject: [PATCH 429/902] [1.2.X] Fixed #14641 - a handful of grammer/typo fixes. Thanks, programmerq. Backport of [14497] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14498 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/index.txt | 2 +- docs/internals/committers.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.txt b/docs/index.txt index d6c2d4f19e9b..f163fd901a6f 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -21,7 +21,7 @@ Having trouble? We'd like to help! `post a question`_. * Ask a question in the `#django IRC channel`_, or search the `IRC logs`_ to see - if its been asked before. + if it's been asked before. * Report bugs with Django in our `ticket tracker`_. diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index cd623988c6a0..12e8d3ad8d60 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -101,7 +101,7 @@ and have free rein to hack on all parts of Django. Joseph Kocherhans Joseph is currently a developer at EveryBlock_, and previously worked for - the Lawrence Journal-World where he built most of the backend for the their + the Lawrence Journal-World where he built most of the backend for their Marketplace site. He often disappears for several days into the woods, attempts to teach himself computational linguistics, and annoys his neighbors with his Charango_ playing. From 33c0a14180a000c58a771f65fb463499238c3cc9 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Mon, 8 Nov 2010 23:50:17 +0000 Subject: [PATCH 430/902] [1.2.X] Small rewording of tutorial01.txt changes from [14066] Backport of [14499] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14500 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/intro/tutorial01.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 785218ba04fe..9f36b3fff819 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -274,7 +274,7 @@ so you can focus on writing code rather than creating directories. configuration and apps for a particular Web site. A project can contain multiple apps. An app can be in multiple projects. -Your apps can live anywhere on your `Python path`_. In this tutorial we will +Your apps can live anywhere on your `Python path`_. In this tutorial, we'll create our poll app in the :file:`mysite` directory for simplicity. To create your app, make sure you're in the :file:`mysite` directory and type From caf51bfcd40bd718503dff418cd1953da07b1baf Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Tue, 9 Nov 2010 05:07:42 +0000 Subject: [PATCH 431/902] [1.2.X] Fixed typo in raw SQL docs example. Backport of [14501] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14502 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/db/sql.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index 9f2be7a2efb8..cac9a7253031 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -81,7 +81,7 @@ had ``Person`` data in it, you could easily map it into ``Person`` instances:: ... last AS last_name, ... bd AS birth_date, ... pk as id, - ... FROM some_other_table) + ... FROM some_other_table''') As long as the names match, the model instances will be created correctly. From 66c72b1f80ed5bfc4001e42e9062e5a2b2db3830 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 9 Nov 2010 05:19:17 +0000 Subject: [PATCH 432/902] [1.2.X] Fixed #14650 -- noted that underscores are also valid in slug fields. Thanks to stringify for the report and patch. Backport of [14503]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14504 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/forms/fields.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index cd32e10eed8f..91f245a0acea 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -682,8 +682,8 @@ and the error message as the value. * Default widget: ``TextInput`` * Empty value: ``''`` (an empty string) * Normalizes to: A Unicode object. - * Validates that the given value contains only letters, numbers and - hyphens. + * Validates that the given value contains only letters, numbers, + underscores, and hyphens. * Error messages: ``required``, ``invalid`` This field is intended for use in representing a model @@ -910,11 +910,11 @@ example:: .. class:: ModelMultipleChoiceField(**kwargs) * Default widget: ``SelectMultiple`` - * Empty value: ``[]`` (an empty list) + * Empty value: ``[]`` (an empty list) * Normalizes to: A list of model instances. * Validates that every id in the given list of values exists in the queryset. - * Error message keys: ``required``, ``list``, ``invalid_choice``, + * Error message keys: ``required``, ``list``, ``invalid_choice``, ``invalid_pk_value`` Allows the selection of one or more model objects, suitable for From a8114d64d55aaea4d0c4f995affbf883109fa9ef Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Tue, 9 Nov 2010 16:35:39 +0000 Subject: [PATCH 433/902] Fixed #14599 -- Added documentation for QuerySet.delete() in the QuerySet API reference. Thanks to abeld for the report. Backport of r14505 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14506 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/querysets.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 18cc1c679e72..c9a640f560cb 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1246,6 +1246,37 @@ The ``update()`` method does a bulk update and does not call any ``save()`` methods on your models, nor does it emit the ``pre_save`` or ``post_save`` signals (which are a consequence of calling ``save()``). +``delete()`` +~~~~~~~~~~~~~~~~~~~~ + +.. method:: delete() + +Performs an SQL delete query on all rows in the :class:`QuerySet`. The +``delete()`` is applied instantly. You cannot call ``delete()`` on a +:class:`QuerySet` that has had a slice taken or can otherwise no longer be +filtered. + +For example, to delete all the entries in a particular blog:: + + >>> b = Blog.objects.get(pk=1) + + # Delete all the entries belonging to this Blog. + >>> Entry.objects.filter(blog=b).delete() + +Django emulates the SQL constraint ``ON DELETE CASCADE`` -- in other words, any +objects with foreign keys pointing at the objects to be deleted will be deleted +along with them. For example:: + + blogs = Blog.objects.all() + # This will delete all Blogs and all of their Entry objects. + blogs.delete() + +The ``delete()`` method does a bulk delete and does not call any ``delete()`` +methods on your models. It does, however, emit the +:data:`~django.db.models.signals.pre_delete` and +:data:`~django.db.models.signals.post_delete` signals for all deleted objects +(including cascaded deletions). + .. _field-lookups: Field lookups From 7335388be2d2dbccf82c15f86121698487032fd6 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Tue, 9 Nov 2010 17:42:38 +0000 Subject: [PATCH 434/902] [1.2.X] Fixed #14653 -- Removed vestigal `getstatusoutput` from GeoDjango utils. Backport of r14508 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14509 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/db/backends/util.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/django/contrib/gis/db/backends/util.py b/django/contrib/gis/db/backends/util.py index ed35ce724a5f..b50c8e222e8d 100644 --- a/django/contrib/gis/db/backends/util.py +++ b/django/contrib/gis/db/backends/util.py @@ -3,20 +3,6 @@ backends. """ -def getstatusoutput(cmd): - """ - Executes a shell command on the platform using subprocess.Popen and - return a tuple of the status and stdout output. - """ - from subprocess import Popen, PIPE - # Set stdout and stderr to PIPE because we want to capture stdout and - # prevent stderr from displaying. - p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) - # We use p.communicate() instead of p.wait() to avoid deadlocks if the - # output buffers exceed POSIX buffer size. - stdout, stderr = p.communicate() - return p.returncode, stdout.strip() - def gqn(val): """ The geographic quote name function; used for quoting tables and From 0f065d346dc4814d42374c9069ef0e232f6d9b7b Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Thu, 11 Nov 2010 04:37:37 +0000 Subject: [PATCH 435/902] [1.2.X] Implemented changes made in r14320 in the Oracle backend. Thanks Russell for reviewing the proposed fix. Backport of [14510] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14511 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/oracle/base.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index a175c3933a5e..919c86262175 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -396,6 +396,28 @@ def _cursor(self): def _savepoint_commit(self, sid): pass + def _commit(self): + if self.connection is not None: + try: + return self.connection.commit() + except Database.IntegrityError, e: + # In case cx_Oracle implements (now or in a future version) + # raising this specific exception + raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + except Database.DatabaseError, e: + # cx_Oracle 5.0.4 raises a cx_Oracle.DatabaseError exception + # with the following attributes and values: + # code = 2091 + # message = 'ORA-02091: transaction rolled back + # 'ORA-02291: integrity constraint (TEST_DJANGOTEST.SYS + # _C00102056) violated - parent key not found' + # We convert that particular case to our IntegrityError exception + x = e.args[0] + if hasattr(x, 'code') and hasattr(x, 'message') \ + and x.code == 2091 and 'ORA-02291' in x.message: + raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2] + raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2] + class OracleParam(object): """ From 2778f146604f33c4eab316ce6c8566a83b198ea8 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Thu, 11 Nov 2010 05:12:05 +0000 Subject: [PATCH 436/902] [1.2.X] Fixed small multi-db compatibility issue in the Oracle backend. Also, converted a couple of constructs to a more Python idiomatic form. Backport of [14512] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14513 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/oracle/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 919c86262175..f4c9050a8d1c 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -329,11 +329,11 @@ class DatabaseWrapper(BaseDatabaseWrapper): 'istartswith': "LIKE UPPER(TRANSLATE(%s USING NCHAR_CS)) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", 'iendswith': "LIKE UPPER(TRANSLATE(%s USING NCHAR_CS)) ESCAPE TRANSLATE('\\' USING NCHAR_CS)", } - oracle_version = None def __init__(self, *args, **kwargs): super(DatabaseWrapper, self).__init__(*args, **kwargs) + self.oracle_version = None self.features = DatabaseFeatures() self.ops = DatabaseOperations() self.client = DatabaseClient(self) @@ -346,9 +346,9 @@ def _valid_connection(self): def _connect_string(self): settings_dict = self.settings_dict - if len(settings_dict['HOST'].strip()) == 0: + if settings_dict['HOST'].strip(): settings_dict['HOST'] = 'localhost' - if len(settings_dict['PORT'].strip()) != 0: + if settings_dict['PORT'].strip(): dsn = Database.makedsn(settings_dict['HOST'], int(settings_dict['PORT']), settings_dict['NAME']) From 79ab8cad03847146029f9f686aedfbdc8168807d Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Thu, 11 Nov 2010 05:18:27 +0000 Subject: [PATCH 437/902] [1.2.X] Fixed error introduced in r14512. Backport of [14514] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14515 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/oracle/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index f4c9050a8d1c..cf8e9f5c1a4f 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -346,7 +346,7 @@ def _valid_connection(self): def _connect_string(self): settings_dict = self.settings_dict - if settings_dict['HOST'].strip(): + if not settings_dict['HOST'].strip(): settings_dict['HOST'] = 'localhost' if settings_dict['PORT'].strip(): dsn = Database.makedsn(settings_dict['HOST'], From d7b8d07cf21ca058251a5e21a7d0dd356aefe00c Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 11 Nov 2010 15:30:13 +0000 Subject: [PATCH 438/902] [1.2.X] Fixed #14508 - test suite silences warnings. Utility functions get_warnings_state and save_warnings_state have been added to django.test.utils, and methods to django.test.TestCase for convenience. The implementation is based on the catch_warnings context manager from Python 2.6. Backport of [14526] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14527 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/testcases.py | 14 ++++++++++++++ django/test/utils.py | 22 ++++++++++++++++++++++ tests/regressiontests/cache/tests.py | 21 +++++++++------------ tests/regressiontests/templates/loaders.py | 4 +++- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/django/test/testcases.py b/django/test/testcases.py index 276d1f3c4111..fc49d0d79b6d 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -11,6 +11,7 @@ from django.http import QueryDict from django.test import _doctest as doctest from django.test.client import Client +from django.test.utils import get_warnings_state, restore_warnings_state from django.utils import simplejson from django.utils.encoding import smart_str @@ -286,6 +287,19 @@ def _urlconf_teardown(self): settings.ROOT_URLCONF = self._old_root_urlconf clear_url_caches() + def save_warnings_state(self): + """ + Saves the state of the warnings module + """ + self._warnings_state = get_warnings_state() + + def restore_warnings_state(self): + """ + Restores the sate of the warnings module to the state + saved by save_warnings_state() + """ + restore_warnings_state(self._warnings_state) + def assertRedirects(self, response, expected_url, status_code=302, target_status_code=200, host=None, msg_prefix=''): """Asserts that a response redirected to a specific URL, and that the diff --git a/django/test/utils.py b/django/test/utils.py index 8ecb5a0e606a..517d06ff0b3f 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -1,6 +1,7 @@ import sys import time import os +import warnings from django.conf import settings from django.core import mail from django.core.mail.backends import locmem @@ -43,6 +44,7 @@ def __contains__(self, key): return False return True + def instrumented_test_render(self, context): """ An instrumented Template render method, providing a signal @@ -72,6 +74,7 @@ def setup_test_environment(): deactivate() + def teardown_test_environment(): """Perform any global post-test teardown. This involves: @@ -90,6 +93,25 @@ def teardown_test_environment(): del mail.outbox + +def get_warnings_state(): + """ + Returns an object containing the state of the warnings module + """ + # There is no public interface for doing this, but this implementation of + # get_warnings_state and restore_warnings_state appears to work on Python + # 2.4 to 2.7. + return warnings.filters[:] + + +def restore_warnings_state(state): + """ + Restores the state of the warnings module when passed an object that was + returned by get_warnings_state() + """ + warnings.filters = state[:] + + def get_runner(settings): test_path = settings.TEST_RUNNER.split('.') # Allow for Python 2.5 relative paths diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 1e0a4046bb57..7167e2fbf82b 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -16,6 +16,7 @@ from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning from django.http import HttpResponse, HttpRequest from django.middleware.cache import FetchFromCacheMiddleware, UpdateCacheMiddleware +from django.test.utils import get_warnings_state, restore_warnings_state from django.utils import translation from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key from django.utils.hashcompat import md5_constructor @@ -379,20 +380,16 @@ def test_invalid_keys(self): # manager to test this warning nicely. Since we can't do that # yet, the cleanest option is to temporarily ask for # CacheKeyWarning to be raised as an exception. + _warnings_state = get_warnings_state() warnings.simplefilter("error", CacheKeyWarning) - # memcached does not allow whitespace or control characters in keys - self.assertRaises(CacheKeyWarning, self.cache.set, 'key with spaces', 'value') - # memcached limits key length to 250 - self.assertRaises(CacheKeyWarning, self.cache.set, 'a' * 251, 'value') - - # The warnings module has no public API for getting the - # current list of warning filters, so we can't save that off - # and reset to the previous value, we have to globally reset - # it. The effect will be the same, as long as the Django test - # runner doesn't add any global warning filters (it currently - # does not). - warnings.resetwarnings() + try: + # memcached does not allow whitespace or control characters in keys + self.assertRaises(CacheKeyWarning, self.cache.set, 'key with spaces', 'value') + # memcached limits key length to 250 + self.assertRaises(CacheKeyWarning, self.cache.set, 'a' * 251, 'value') + finally: + restore_warnings_state(_warnings_state) class DBCacheTests(unittest.TestCase, BaseCacheTests): def setUp(self): diff --git a/tests/regressiontests/templates/loaders.py b/tests/regressiontests/templates/loaders.py index caa3faa6bcd2..47cd18ae21bb 100644 --- a/tests/regressiontests/templates/loaders.py +++ b/tests/regressiontests/templates/loaders.py @@ -21,6 +21,7 @@ from django.template.loaders.eggs import load_template_source as lts_egg from django.template.loaders.eggs import Loader as EggLoader from django.template import loader +from django.test.utils import get_warnings_state, restore_warnings_state # Mock classes and objects for pkg_resources functions. class MockProvider(pkg_resources.NullProvider): @@ -67,11 +68,12 @@ def setUp(self): }) self._old_installed_apps = settings.INSTALLED_APPS settings.INSTALLED_APPS = [] + self._warnings_state = get_warnings_state() warnings.simplefilter("ignore", PendingDeprecationWarning) def tearDown(self): settings.INSTALLED_APPS = self._old_installed_apps - warnings.resetwarnings() + restore_warnings_state(self._warnings_state) def test_existing(self): "A template can be loaded from an egg" From 659ac65cc39a6105a5e908f648bbc209112b1a37 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 11 Nov 2010 17:51:45 +0000 Subject: [PATCH 439/902] [1.2.X] Remove the executable flag from uploadhandler.py, it wasn't. Thanks to Florian for the report. Backport of [14528]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14529 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/files/uploadhandler.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 django/core/files/uploadhandler.py diff --git a/django/core/files/uploadhandler.py b/django/core/files/uploadhandler.py old mode 100755 new mode 100644 From 4e4418d34d37ed87661bbcb30f975cd2d6d9d7a7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 11 Nov 2010 17:53:59 +0000 Subject: [PATCH 440/902] [1.2.X] Removed the executable bit from a test file, it wasn't. Thanks to Florian for the report. Backport of [14530]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14531 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/model_forms/mforms.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 tests/modeltests/model_forms/mforms.py diff --git a/tests/modeltests/model_forms/mforms.py b/tests/modeltests/model_forms/mforms.py old mode 100755 new mode 100644 From 0046f37393d0e8e74d8d8c6b0fc46e9bf211b638 Mon Sep 17 00:00:00 2001 From: Ian Kelly Date: Fri, 12 Nov 2010 01:54:07 +0000 Subject: [PATCH 441/902] [1.2.X] Backport of r14537 from trunk. Fixed #11101: Rewrote the sequence reset SQL for Oracle to prevent it from performing an implicit commit that caused all fixtures to be automatically committed, causing a large number of test failures. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14538 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/backends/oracle/base.py | 20 ++++++------- .../fixtures_regress/fixtures/thingy.json | 9 ++++++ .../fixtures_regress/models.py | 4 +++ .../regressiontests/fixtures_regress/tests.py | 28 +++++++++++++++++-- 4 files changed, 46 insertions(+), 15 deletions(-) create mode 100644 tests/regressiontests/fixtures_regress/fixtures/thingy.json diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index cf8e9f5c1a4f..5ccaded47466 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -666,19 +666,15 @@ def _get_sequence_reset_sql(): # TODO: colorize this SQL code with style.SQL_KEYWORD(), etc. return """ DECLARE - startvalue integer; - cval integer; + table_value integer; + seq_value integer; BEGIN - LOCK TABLE %(table)s IN SHARE MODE; - SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s; - SELECT "%(sequence)s".nextval INTO cval FROM dual; - cval := startvalue - cval; - IF cval != 0 THEN - EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" MINVALUE 0 INCREMENT BY '||cval; - SELECT "%(sequence)s".nextval INTO cval FROM dual; - EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" INCREMENT BY 1'; - END IF; - COMMIT; + SELECT NVL(MAX(%(column)s), 0) INTO table_value FROM %(table)s; + SELECT NVL(last_number - cache_size, 0) INTO seq_value FROM user_sequences + WHERE sequence_name = '%(sequence)s'; + WHILE table_value > seq_value LOOP + SELECT "%(sequence)s".nextval INTO seq_value FROM dual; + END LOOP; END; /""" diff --git a/tests/regressiontests/fixtures_regress/fixtures/thingy.json b/tests/regressiontests/fixtures_regress/fixtures/thingy.json new file mode 100644 index 000000000000..1693177b981c --- /dev/null +++ b/tests/regressiontests/fixtures_regress/fixtures/thingy.json @@ -0,0 +1,9 @@ +[ + { + "pk": "1", + "model": "fixtures_regress.thingy", + "fields": { + "name": "Whatchamacallit" + } + } +] diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py index 9e09a1688bce..2c435625f0a4 100644 --- a/tests/regressiontests/fixtures_regress/models.py +++ b/tests/regressiontests/fixtures_regress/models.py @@ -225,3 +225,7 @@ def natural_key(self): return self.name natural_key.dependencies = ['fixtures_regress.book'] + +# Model for regression test of #11101 +class Thingy(models.Model): + name = models.CharField(max_length=255) diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py index 3fe026d5a733..404181e52b97 100644 --- a/tests/regressiontests/fixtures_regress/tests.py +++ b/tests/regressiontests/fixtures_regress/tests.py @@ -11,7 +11,8 @@ from django.core.management.commands.dumpdata import sort_dependencies from django.core.management.base import CommandError from django.db.models import signals -from django.test import TestCase +from django.db import transaction +from django.test import TestCase, TransactionTestCase from models import Animal, Stuff from models import Absolute, Parent, Child @@ -20,6 +21,7 @@ from models import NKChild, RefToNKChild from models import Circle1, Circle2, Circle3 from models import ExternalDependency +from models import Thingy pre_save_checks = [] @@ -56,7 +58,7 @@ def test_duplicate_pk(self): weight=2.2 ) animal.save() - self.assertEqual(animal.id, 2) + self.assertGreater(animal.id, 1) def test_pretty_print_xml(self): """ @@ -309,7 +311,8 @@ def test_dumpdata_uses_default_manager(self): data = stdout.getvalue() lion_json = '{"pk": 1, "model": "fixtures_regress.animal", "fields": {"count": 3, "weight": 1.2, "name": "Lion", "latin_name": "Panthera leo"}}' emu_json = '{"pk": 10, "model": "fixtures_regress.animal", "fields": {"count": 42, "weight": 1.2, "name": "Emu", "latin_name": "Dromaius novaehollandiae"}}' - platypus_json = '{"pk": 11, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.2000000000000002, "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}' + platypus_json = '{"pk": %d, "model": "fixtures_regress.animal", "fields": {"count": 2, "weight": 2.2000000000000002, "name": "Platypus", "latin_name": "Ornithorhynchus anatinus"}}' + platypus_json = platypus_json % animal.pk self.assertEqual(len(data), len('[%s]' % ', '.join([lion_json, emu_json, platypus_json]))) self.assertTrue(lion_json in data) @@ -569,3 +572,22 @@ def test_normal_pk(self): books.__repr__(), """[, , ]""" ) + + +class TestTicket11101(TransactionTestCase): + + def ticket_11101(self): + management.call_command( + 'loaddata', + 'thingy.json', + verbosity=0, + commit=False + ) + self.assertEqual(Thingy.objects.count(), 1) + transaction.rollback() + self.assertEqual(Thingy.objects.count(), 0) + + def test_ticket_11101(self): + """Test that fixtures can be rolled back (ticket #11101).""" + ticket_11101 = transaction.commit_manually(self.ticket_11101) + ticket_11101() From e074d2b9e9200e211a71283b382f03906a127aca Mon Sep 17 00:00:00 2001 From: Gabriel Hurley Date: Fri, 12 Nov 2010 02:49:21 +0000 Subject: [PATCH 442/902] [1.2.X] Fixed #14669 -- corrected an ungrammatical sentence in the internationalization docs. Thanks to steveire for the report. Backport of [14539] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14540 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/topics/i18n/internationalization.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 6c5a7b8405aa..04ecc73c253a 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -87,13 +87,13 @@ The strings you pass to ``_()`` or ``ugettext()`` can take placeholders, specified with Python's standard named-string interpolation syntax. Example:: def my_view(request, m, d): - output = _('Today is %(month)s, %(day)s.') % {'month': m, 'day': d} + output = _('Today is %(month)s %(day)s.') % {'month': m, 'day': d} return HttpResponse(output) This technique lets language-specific translations reorder the placeholder -text. For example, an English translation may be ``"Today is November, 26."``, +text. For example, an English translation may be ``"Today is November 26."``, while a Spanish translation may be ``"Hoy es 26 de Noviembre."`` -- with the -placeholders (the month and the day) with their positions swapped. +the month and the day placeholders swapped. For this reason, you should use named-string interpolation (e.g., ``%(day)s``) instead of positional interpolation (e.g., ``%s`` or ``%d``) whenever you From 80946dc0fa0ea8ca6245e7fec815edf8295ed396 Mon Sep 17 00:00:00 2001 From: Ian Kelly Date: Fri, 12 Nov 2010 18:51:54 +0000 Subject: [PATCH 443/902] [1.2.X] Backport of r14545 from trunk. Fixed a multiple_database test case that was failing in Oracle due to forced table name capitalization. The test case now matches the style used by the raw_query tests. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14546 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/multiple_database/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py index 484cac6adab6..784181aae2ca 100644 --- a/tests/regressiontests/multiple_database/tests.py +++ b/tests/regressiontests/multiple_database/tests.py @@ -852,10 +852,10 @@ def test_raw(self): "test the raw() method across databases" dive = Book.objects.using('other').create(title="Dive into Python", published=datetime.date(2009, 5, 4)) - val = Book.objects.db_manager("other").raw('SELECT id FROM "multiple_database_book"') + val = Book.objects.db_manager("other").raw('SELECT id FROM multiple_database_book') self.assertEqual(map(lambda o: o.pk, val), [dive.pk]) - val = Book.objects.raw('SELECT id FROM "multiple_database_book"').using('other') + val = Book.objects.raw('SELECT id FROM multiple_database_book').using('other') self.assertEqual(map(lambda o: o.pk, val), [dive.pk]) def test_select_related(self): From 8c7df2198acc6b81522bf0b89d348dbffd7dacce Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 12 Nov 2010 19:40:04 +0000 Subject: [PATCH 444/902] =?UTF-8?q?[1.2.X]Converted=20m2m=5Fsignals=20from?= =?UTF-8?q?=20doctests=20to=20unittests.=20=20Thanks=20to=20Gregor=20M?= =?UTF-8?q?=C3=BCllegger=20for=20the=20patch.=20=20We=20have=20always=20be?= =?UTF-8?q?en=20at=20war=20with=20doctests.=20=20Backport=20of=20[14548].?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14549 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/modeltests/m2m_signals/models.py | 336 +------------------ tests/modeltests/m2m_signals/tests.py | 427 +++++++++++++++++++++++++ 2 files changed, 428 insertions(+), 335 deletions(-) create mode 100644 tests/modeltests/m2m_signals/tests.py diff --git a/tests/modeltests/m2m_signals/models.py b/tests/modeltests/m2m_signals/models.py index c76cde46fc35..526c4a782ecd 100644 --- a/tests/modeltests/m2m_signals/models.py +++ b/tests/modeltests/m2m_signals/models.py @@ -1,9 +1,6 @@ -""" -Testing signals emitted on changing m2m relations. -""" - from django.db import models + class Part(models.Model): name = models.CharField(max_length=20) @@ -37,334 +34,3 @@ class Meta: def __unicode__(self): return self.name - -def m2m_changed_test(signal, sender, **kwargs): - print 'm2m_changed signal' - print 'instance:', kwargs['instance'] - print 'action:', kwargs['action'] - print 'reverse:', kwargs['reverse'] - print 'model:', kwargs['model'] - if kwargs['pk_set']: - print 'objects:',kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) - - -__test__ = {'API_TESTS':""" -# Install a listener on one of the two m2m relations. ->>> models.signals.m2m_changed.connect(m2m_changed_test, Car.optional_parts.through) - -# Test the add, remove and clear methods on both sides of the -# many-to-many relation - ->>> c1 = Car.objects.create(name='VW') ->>> c2 = Car.objects.create(name='BMW') ->>> c3 = Car.objects.create(name='Toyota') ->>> p1 = Part.objects.create(name='Wheelset') ->>> p2 = Part.objects.create(name='Doors') ->>> p3 = Part.objects.create(name='Engine') ->>> p4 = Part.objects.create(name='Airbag') ->>> p5 = Part.objects.create(name='Sunroof') - -# adding a default part to our car - no signal listener installed ->>> c1.default_parts.add(p5) - -# Now install a listener ->>> models.signals.m2m_changed.connect(m2m_changed_test, Car.default_parts.through) - ->>> c1.default_parts.add(p1, p2, p3) -m2m_changed signal -instance: VW -action: pre_add -reverse: False -model: -objects: [, , ] -m2m_changed signal -instance: VW -action: post_add -reverse: False -model: -objects: [, , ] - -# give the BMW and Toyata some doors as well ->>> p2.car_set.add(c2, c3) -m2m_changed signal -instance: Doors -action: pre_add -reverse: True -model: -objects: [, ] -m2m_changed signal -instance: Doors -action: post_add -reverse: True -model: -objects: [, ] - -# remove the engine from the VW and the airbag (which is not set but is returned) ->>> c1.default_parts.remove(p3, p4) -m2m_changed signal -instance: VW -action: pre_remove -reverse: False -model: -objects: [, ] -m2m_changed signal -instance: VW -action: post_remove -reverse: False -model: -objects: [, ] - -# give the VW some optional parts (second relation to same model) ->>> c1.optional_parts.add(p4,p5) -m2m_changed signal -instance: VW -action: pre_add -reverse: False -model: -objects: [, ] -m2m_changed signal -instance: VW -action: post_add -reverse: False -model: -objects: [, ] - -# add airbag to all the cars (even though the VW already has one) ->>> p4.cars_optional.add(c1, c2, c3) -m2m_changed signal -instance: Airbag -action: pre_add -reverse: True -model: -objects: [, ] -m2m_changed signal -instance: Airbag -action: post_add -reverse: True -model: -objects: [, ] - -# remove airbag from the VW (reverse relation with custom related_name) ->>> p4.cars_optional.remove(c1) -m2m_changed signal -instance: Airbag -action: pre_remove -reverse: True -model: -objects: [] -m2m_changed signal -instance: Airbag -action: post_remove -reverse: True -model: -objects: [] - -# clear all parts of the VW ->>> c1.default_parts.clear() -m2m_changed signal -instance: VW -action: pre_clear -reverse: False -model: -m2m_changed signal -instance: VW -action: post_clear -reverse: False -model: - -# take all the doors off of cars ->>> p2.car_set.clear() -m2m_changed signal -instance: Doors -action: pre_clear -reverse: True -model: -m2m_changed signal -instance: Doors -action: post_clear -reverse: True -model: - -# take all the airbags off of cars (clear reverse relation with custom related_name) ->>> p4.cars_optional.clear() -m2m_changed signal -instance: Airbag -action: pre_clear -reverse: True -model: -m2m_changed signal -instance: Airbag -action: post_clear -reverse: True -model: - -# alternative ways of setting relation: - ->>> c1.default_parts.create(name='Windows') -m2m_changed signal -instance: VW -action: pre_add -reverse: False -model: -objects: [] -m2m_changed signal -instance: VW -action: post_add -reverse: False -model: -objects: [] - - -# direct assignment clears the set first, then adds ->>> c1.default_parts = [p1,p2,p3] -m2m_changed signal -instance: VW -action: pre_clear -reverse: False -model: -m2m_changed signal -instance: VW -action: post_clear -reverse: False -model: -m2m_changed signal -instance: VW -action: pre_add -reverse: False -model: -objects: [, , ] -m2m_changed signal -instance: VW -action: post_add -reverse: False -model: -objects: [, , ] - -# Check that signals still work when model inheritance is involved ->>> c4 = SportsCar.objects.create(name='Bugatti', price='1000000') ->>> c4.default_parts = [p2] -m2m_changed signal -instance: Bugatti -action: pre_clear -reverse: False -model: -m2m_changed signal -instance: Bugatti -action: post_clear -reverse: False -model: -m2m_changed signal -instance: Bugatti -action: pre_add -reverse: False -model: -objects: [] -m2m_changed signal -instance: Bugatti -action: post_add -reverse: False -model: -objects: [] - ->>> p3.car_set.add(c4) -m2m_changed signal -instance: Engine -action: pre_add -reverse: True -model: -objects: [] -m2m_changed signal -instance: Engine -action: post_add -reverse: True -model: -objects: [] - -# Now test m2m relations with self ->>> p1 = Person.objects.create(name='Alice') ->>> p2 = Person.objects.create(name='Bob') ->>> p3 = Person.objects.create(name='Chuck') ->>> p4 = Person.objects.create(name='Daisy') - ->>> models.signals.m2m_changed.connect(m2m_changed_test, Person.fans.through) ->>> models.signals.m2m_changed.connect(m2m_changed_test, Person.friends.through) - ->>> p1.friends = [p2, p3] -m2m_changed signal -instance: Alice -action: pre_clear -reverse: False -model: -m2m_changed signal -instance: Alice -action: post_clear -reverse: False -model: -m2m_changed signal -instance: Alice -action: pre_add -reverse: False -model: -objects: [, ] -m2m_changed signal -instance: Alice -action: post_add -reverse: False -model: -objects: [, ] - ->>> p1.fans = [p4] -m2m_changed signal -instance: Alice -action: pre_clear -reverse: False -model: -m2m_changed signal -instance: Alice -action: post_clear -reverse: False -model: -m2m_changed signal -instance: Alice -action: pre_add -reverse: False -model: -objects: [] -m2m_changed signal -instance: Alice -action: post_add -reverse: False -model: -objects: [] - ->>> p3.idols = [p1,p2] -m2m_changed signal -instance: Chuck -action: pre_clear -reverse: True -model: -m2m_changed signal -instance: Chuck -action: post_clear -reverse: True -model: -m2m_changed signal -instance: Chuck -action: pre_add -reverse: True -model: -objects: [, ] -m2m_changed signal -instance: Chuck -action: post_add -reverse: True -model: -objects: [, ] - -# Cleanup - disconnect all signal handlers ->>> models.signals.m2m_changed.disconnect(m2m_changed_test, Car.default_parts.through) ->>> models.signals.m2m_changed.disconnect(m2m_changed_test, Car.optional_parts.through) ->>> models.signals.m2m_changed.disconnect(m2m_changed_test, Person.fans.through) ->>> models.signals.m2m_changed.disconnect(m2m_changed_test, Person.friends.through) - -"""} diff --git a/tests/modeltests/m2m_signals/tests.py b/tests/modeltests/m2m_signals/tests.py new file mode 100644 index 000000000000..9e9158f5710b --- /dev/null +++ b/tests/modeltests/m2m_signals/tests.py @@ -0,0 +1,427 @@ +""" +Testing signals emitted on changing m2m relations. +""" + +from django.db import models +from django.test import TestCase + +from models import Part, Car, SportsCar, Person + + +class ManyToManySignalsTest(TestCase): + def m2m_changed_signal_receiver(self, signal, sender, **kwargs): + message = { + 'instance': kwargs['instance'], + 'action': kwargs['action'], + 'reverse': kwargs['reverse'], + 'model': kwargs['model'], + } + if kwargs['pk_set']: + message['objects'] = list( + kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) + ) + self.m2m_changed_messages.append(message) + + def setUp(self): + self.m2m_changed_messages = [] + + self.vw = Car.objects.create(name='VW') + self.bmw = Car.objects.create(name='BMW') + self.toyota = Car.objects.create(name='Toyota') + self.wheelset = Part.objects.create(name='Wheelset') + self.doors = Part.objects.create(name='Doors') + self.engine = Part.objects.create(name='Engine') + self.airbag = Part.objects.create(name='Airbag') + self.sunroof = Part.objects.create(name='Sunroof') + + self.alice = Person.objects.create(name='Alice') + self.bob = Person.objects.create(name='Bob') + self.chuck = Person.objects.create(name='Chuck') + self.daisy = Person.objects.create(name='Daisy') + + def tearDown(self): + # disconnect all signal handlers + models.signals.m2m_changed.disconnect( + self.m2m_changed_signal_receiver, Car.default_parts.through + ) + models.signals.m2m_changed.disconnect( + self.m2m_changed_signal_receiver, Car.optional_parts.through + ) + models.signals.m2m_changed.disconnect( + self.m2m_changed_signal_receiver, Person.fans.through + ) + models.signals.m2m_changed.disconnect( + self.m2m_changed_signal_receiver, Person.friends.through + ) + + def test_m2m_relations_add_remove_clear(self): + expected_messages = [] + + # Install a listener on one of the two m2m relations. + models.signals.m2m_changed.connect( + self.m2m_changed_signal_receiver, Car.optional_parts.through + ) + + # Test the add, remove and clear methods on both sides of the + # many-to-many relation + + # adding a default part to our car - no signal listener installed + self.vw.default_parts.add(self.sunroof) + + # Now install a listener + models.signals.m2m_changed.connect( + self.m2m_changed_signal_receiver, Car.default_parts.through + ) + + self.vw.default_parts.add(self.wheelset, self.doors, self.engine) + expected_messages.append({ + 'instance': self.vw, + 'action': 'pre_add', + 'reverse': False, + 'model': Part, + 'objects': [self.doors, self.engine, self.wheelset], + }) + expected_messages.append({ + 'instance': self.vw, + 'action': 'post_add', + 'reverse': False, + 'model': Part, + 'objects': [self.doors, self.engine, self.wheelset], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # give the BMW and Toyata some doors as well + self.doors.car_set.add(self.bmw, self.toyota) + expected_messages.append({ + 'instance': self.doors, + 'action': 'pre_add', + 'reverse': True, + 'model': Car, + 'objects': [self.bmw, self.toyota], + }) + expected_messages.append({ + 'instance': self.doors, + 'action': 'post_add', + 'reverse': True, + 'model': Car, + 'objects': [self.bmw, self.toyota], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # remove the engine from the self.vw and the airbag (which is not set + # but is returned) + self.vw.default_parts.remove(self.engine, self.airbag) + expected_messages.append({ + 'instance': self.vw, + 'action': 'pre_remove', + 'reverse': False, + 'model': Part, + 'objects': [self.airbag, self.engine], + }) + expected_messages.append({ + 'instance': self.vw, + 'action': 'post_remove', + 'reverse': False, + 'model': Part, + 'objects': [self.airbag, self.engine], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # give the self.vw some optional parts (second relation to same model) + self.vw.optional_parts.add(self.airbag, self.sunroof) + expected_messages.append({ + 'instance': self.vw, + 'action': 'pre_add', + 'reverse': False, + 'model': Part, + 'objects': [self.airbag, self.sunroof], + }) + expected_messages.append({ + 'instance': self.vw, + 'action': 'post_add', + 'reverse': False, + 'model': Part, + 'objects': [self.airbag, self.sunroof], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # add airbag to all the cars (even though the self.vw already has one) + self.airbag.cars_optional.add(self.vw, self.bmw, self.toyota) + expected_messages.append({ + 'instance': self.airbag, + 'action': 'pre_add', + 'reverse': True, + 'model': Car, + 'objects': [self.bmw, self.toyota], + }) + expected_messages.append({ + 'instance': self.airbag, + 'action': 'post_add', + 'reverse': True, + 'model': Car, + 'objects': [self.bmw, self.toyota], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # remove airbag from the self.vw (reverse relation with custom + # related_name) + self.airbag.cars_optional.remove(self.vw) + expected_messages.append({ + 'instance': self.airbag, + 'action': 'pre_remove', + 'reverse': True, + 'model': Car, + 'objects': [self.vw], + }) + expected_messages.append({ + 'instance': self.airbag, + 'action': 'post_remove', + 'reverse': True, + 'model': Car, + 'objects': [self.vw], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # clear all parts of the self.vw + self.vw.default_parts.clear() + expected_messages.append({ + 'instance': self.vw, + 'action': 'pre_clear', + 'reverse': False, + 'model': Part, + }) + expected_messages.append({ + 'instance': self.vw, + 'action': 'post_clear', + 'reverse': False, + 'model': Part, + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # take all the doors off of cars + self.doors.car_set.clear() + expected_messages.append({ + 'instance': self.doors, + 'action': 'pre_clear', + 'reverse': True, + 'model': Car, + }) + expected_messages.append({ + 'instance': self.doors, + 'action': 'post_clear', + 'reverse': True, + 'model': Car, + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # take all the airbags off of cars (clear reverse relation with custom + # related_name) + self.airbag.cars_optional.clear() + expected_messages.append({ + 'instance': self.airbag, + 'action': 'pre_clear', + 'reverse': True, + 'model': Car, + }) + expected_messages.append({ + 'instance': self.airbag, + 'action': 'post_clear', + 'reverse': True, + 'model': Car, + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # alternative ways of setting relation: + self.vw.default_parts.create(name='Windows') + p6 = Part.objects.get(name='Windows') + expected_messages.append({ + 'instance': self.vw, + 'action': 'pre_add', + 'reverse': False, + 'model': Part, + 'objects': [p6], + }) + expected_messages.append({ + 'instance': self.vw, + 'action': 'post_add', + 'reverse': False, + 'model': Part, + 'objects': [p6], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # direct assignment clears the set first, then adds + self.vw.default_parts = [self.wheelset,self.doors,self.engine] + expected_messages.append({ + 'instance': self.vw, + 'action': 'pre_clear', + 'reverse': False, + 'model': Part, + }) + expected_messages.append({ + 'instance': self.vw, + 'action': 'post_clear', + 'reverse': False, + 'model': Part, + }) + expected_messages.append({ + 'instance': self.vw, + 'action': 'pre_add', + 'reverse': False, + 'model': Part, + 'objects': [self.doors, self.engine, self.wheelset], + }) + expected_messages.append({ + 'instance': self.vw, + 'action': 'post_add', + 'reverse': False, + 'model': Part, + 'objects': [self.doors, self.engine, self.wheelset], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + # Check that signals still work when model inheritance is involved + c4 = SportsCar.objects.create(name='Bugatti', price='1000000') + c4b = Car.objects.get(name='Bugatti') + c4.default_parts = [self.doors] + expected_messages.append({ + 'instance': c4, + 'action': 'pre_clear', + 'reverse': False, + 'model': Part, + }) + expected_messages.append({ + 'instance': c4, + 'action': 'post_clear', + 'reverse': False, + 'model': Part, + }) + expected_messages.append({ + 'instance': c4, + 'action': 'pre_add', + 'reverse': False, + 'model': Part, + 'objects': [self.doors], + }) + expected_messages.append({ + 'instance': c4, + 'action': 'post_add', + 'reverse': False, + 'model': Part, + 'objects': [self.doors], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + self.engine.car_set.add(c4) + expected_messages.append({ + 'instance': self.engine, + 'action': 'pre_add', + 'reverse': True, + 'model': Car, + 'objects': [c4b], + }) + expected_messages.append({ + 'instance': self.engine, + 'action': 'post_add', + 'reverse': True, + 'model': Car, + 'objects': [c4b], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + def test_m2m_relations_with_self(self): + expected_messages = [] + + models.signals.m2m_changed.connect( + self.m2m_changed_signal_receiver, Person.fans.through + ) + models.signals.m2m_changed.connect( + self.m2m_changed_signal_receiver, Person.friends.through + ) + + self.alice.friends = [self.bob, self.chuck] + expected_messages.append({ + 'instance': self.alice, + 'action': 'pre_clear', + 'reverse': False, + 'model': Person, + }) + expected_messages.append({ + 'instance': self.alice, + 'action': 'post_clear', + 'reverse': False, + 'model': Person, + }) + expected_messages.append({ + 'instance': self.alice, + 'action': 'pre_add', + 'reverse': False, + 'model': Person, + 'objects': [self.bob, self.chuck], + }) + expected_messages.append({ + 'instance': self.alice, + 'action': 'post_add', + 'reverse': False, + 'model': Person, + 'objects': [self.bob, self.chuck], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + self.alice.fans = [self.daisy] + expected_messages.append({ + 'instance': self.alice, + 'action': 'pre_clear', + 'reverse': False, + 'model': Person, + }) + expected_messages.append({ + 'instance': self.alice, + 'action': 'post_clear', + 'reverse': False, + 'model': Person, + }) + expected_messages.append({ + 'instance': self.alice, + 'action': 'pre_add', + 'reverse': False, + 'model': Person, + 'objects': [self.daisy], + }) + expected_messages.append({ + 'instance': self.alice, + 'action': 'post_add', + 'reverse': False, + 'model': Person, + 'objects': [self.daisy], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) + + self.chuck.idols = [self.alice,self.bob] + expected_messages.append({ + 'instance': self.chuck, + 'action': 'pre_clear', + 'reverse': True, + 'model': Person, + }) + expected_messages.append({ + 'instance': self.chuck, + 'action': 'post_clear', + 'reverse': True, + 'model': Person, + }) + expected_messages.append({ + 'instance': self.chuck, + 'action': 'pre_add', + 'reverse': True, + 'model': Person, + 'objects': [self.alice, self.bob], + }) + expected_messages.append({ + 'instance': self.chuck, + 'action': 'post_add', + 'reverse': True, + 'model': Person, + 'objects': [self.alice, self.bob], + }) + self.assertEqual(self.m2m_changed_messages, expected_messages) From 17bd39ae5a0337f8bcd535a5fb03d27aaf0ab0c4 Mon Sep 17 00:00:00 2001 From: Ian Kelly Date: Fri, 12 Nov 2010 19:47:41 +0000 Subject: [PATCH 445/902] [1.2.X] Backport of r14547 from trunk. Fixed a test case that was failing in Oracle due to conflation of null and empty strings. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14550 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- .../fixtures_regress/models.py | 8 +-- .../regressiontests/fixtures_regress/tests.py | 52 +++++++++++++------ 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py index 2c435625f0a4..7465fd242dc3 100644 --- a/tests/regressiontests/fixtures_regress/models.py +++ b/tests/regressiontests/fixtures_regress/models.py @@ -28,13 +28,7 @@ class Stuff(models.Model): owner = models.ForeignKey(User, null=True) def __unicode__(self): - # Oracle doesn't distinguish between None and the empty string. - # This hack makes the test case pass using Oracle. - name = self.name - if (settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle' - and name == u''): - name = None - return unicode(name) + u' is owned by ' + unicode(self.owner) + return unicode(self.name) + u' is owned by ' + unicode(self.owner) class Absolute(models.Model): diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py index 404181e52b97..57ee7c045143 100644 --- a/tests/regressiontests/fixtures_regress/tests.py +++ b/tests/regressiontests/fixtures_regress/tests.py @@ -7,11 +7,12 @@ except ImportError: from StringIO import StringIO +from django.conf import settings from django.core import management from django.core.management.commands.dumpdata import sort_dependencies from django.core.management.base import CommandError from django.db.models import signals -from django.db import transaction +from django.db import DEFAULT_DB_ALIAS, transaction from django.test import TestCase, TransactionTestCase from models import Animal, Stuff @@ -58,22 +59,39 @@ def test_duplicate_pk(self): weight=2.2 ) animal.save() - self.assertGreater(animal.id, 1) - - def test_pretty_print_xml(self): - """ - Regression test for ticket #4558 -- pretty printing of XML fixtures - doesn't affect parsing of None values. - """ - # Load a pretty-printed XML fixture with Nulls. - management.call_command( - 'loaddata', - 'pretty.xml', - verbosity=0, - commit=False - ) - self.assertEqual(Stuff.objects.all()[0].name, None) - self.assertEqual(Stuff.objects.all()[0].owner, None) + self.assertTrue(animal.id > 1) + + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.oracle': + def test_pretty_print_xml(self): + """ + Regression test for ticket #4558 -- pretty printing of XML fixtures + doesn't affect parsing of None values. + """ + # Load a pretty-printed XML fixture with Nulls. + management.call_command( + 'loaddata', + 'pretty.xml', + verbosity=0, + commit=False + ) + self.assertEqual(Stuff.objects.all()[0].name, None) + self.assertEqual(Stuff.objects.all()[0].owner, None) + + if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle': + def test_pretty_print_xml_empty_strings(self): + """ + Regression test for ticket #4558 -- pretty printing of XML fixtures + doesn't affect parsing of None values. + """ + # Load a pretty-printed XML fixture with Nulls. + management.call_command( + 'loaddata', + 'pretty.xml', + verbosity=0, + commit=False + ) + self.assertEqual(Stuff.objects.all()[0].name, u'') + self.assertEqual(Stuff.objects.all()[0].owner, None) def test_absolute_path(self): """ From a052bcb0119f0e7885ff9fa9bb5e7d39b838b6aa Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 14 Nov 2010 14:09:52 +0000 Subject: [PATCH 446/902] Fixed #14536 -- Corrected DB connection OPTIONS examples in documentation. Thanks denilsonsa for reporting the error. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14558 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/databases.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index b71cc359be9a..bff3ec6840c3 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -55,8 +55,8 @@ autocommit behavior is enabled by setting the ``autocommit`` key in the :setting:`OPTIONS` part of your database configuration in :setting:`DATABASES`:: - OPTIONS = { - "autocommit": True, + 'OPTIONS': { + 'autocommit': True, } In this configuration, Django still ensures that :ref:`delete() @@ -306,8 +306,8 @@ storage engine, you have a couple of options. * Another option is to use the ``init_command`` option for MySQLdb prior to creating your tables:: - OPTIONS = { - "init_command": "SET storage_engine=INNODB", + 'OPTIONS': { + 'init_command': 'SET storage_engine=INNODB', } This sets the default storage engine upon connecting to the database. @@ -470,9 +470,9 @@ If you're getting this error, you can solve it by: * Increase the default timeout value by setting the ``timeout`` database option option:: - OPTIONS = { + 'OPTIONS': { # ... - "timeout": 20, + 'timeout': 20, # ... } From dc3b524f6c0c513425f21434b44f3d9ef2d923c1 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 14 Nov 2010 15:50:01 +0000 Subject: [PATCH 447/902] [1.2.X] Fixed #10650 -- Clarified description of MEDIA_ROOT in setting files. Thanks jjconti, tvon, vak, Muhammad Alkarouri and thiggins for their work. Backport of [14560] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14561 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/global_settings.py | 2 +- django/conf/project_template/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index e86f1055f544..7746be144aba 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -258,7 +258,7 @@ # Default file storage mechanism that holds media. DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' -# Absolute path to the directory that holds media. +# Absolute filesystem path to the directory that will hold user uploaded files. # Example: "/home/media/media.lawrence.com/" MEDIA_ROOT = '' diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py index c49df24ce579..256720c2d1e6 100644 --- a/django/conf/project_template/settings.py +++ b/django/conf/project_template/settings.py @@ -43,7 +43,7 @@ # calendars according to the current locale USE_L10N = True -# Absolute path to the directory that holds media. +# Absolute filesystem path to the directory that will hold user uploaded files. # Example: "/home/media/media.lawrence.com/" MEDIA_ROOT = '' From 2f91f76417ea949242b68db8e075bb43047fdd53 Mon Sep 17 00:00:00 2001 From: Ramiro Morales Date: Sun, 14 Nov 2010 23:36:40 +0000 Subject: [PATCH 448/902] [1.2.X] Fixed #3055 -- Validate that models target of a GenericRelation have a GenericForeignKey field. Thanks jason for diagnosing the problem and Marcos Moyano for the patch. Backport of [14563] from trunk git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14565 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management/validation.py | 13 +++++++++++++ django/utils/itercompat.py | 6 ++++++ tests/modeltests/invalid_models/models.py | 17 +++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/django/core/management/validation.py b/django/core/management/validation.py index feb3744781e8..0a0fe8f580e0 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -1,7 +1,14 @@ import sys + +from django.contrib.contenttypes.generic import GenericForeignKey, GenericRelation from django.core.management.color import color_style from django.utils.itercompat import is_iterable +try: + any +except NameError: + from django.utils.itercompat import any + class ModelErrorCollection: def __init__(self, outfile=sys.stdout): self.errors = [] @@ -216,6 +223,12 @@ def get_validation_errors(outfile, app=None): e.add(opts, "'%s' specifies an m2m relation through model %s, " "which has not been installed" % (f.name, f.rel.through) ) + elif isinstance(f, GenericRelation): + if not any([isinstance(vfield, GenericForeignKey) for vfield in f.rel.to._meta.virtual_fields]): + e.add(opts, "Model '%s' must have a GenericForeignKey in " + "order to create a GenericRelation that points to it." + % f.rel.to.__name__ + ) rel_opts = f.rel.to._meta rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py index ab27c3ee018e..d4ff2503c7f4 100644 --- a/django/utils/itercompat.py +++ b/django/utils/itercompat.py @@ -37,3 +37,9 @@ def all(iterable): if not item: return False return True + +def any(iterable): + for item in iterable: + if item: + return True + return False diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py index ef3edd8563f6..09301ed935fc 100644 --- a/tests/modeltests/invalid_models/models.py +++ b/tests/modeltests/invalid_models/models.py @@ -4,6 +4,7 @@ This example exists purely to point out errors in models. """ +from django.contrib.contenttypes import generic from django.db import models class FieldErrors(models.Model): @@ -210,6 +211,21 @@ class NonExistingOrderingWithSingleUnderscore(models.Model): class Meta: ordering = ("does_not_exist",) +class Tag(models.Model): + name = models.CharField("name", max_length=20) + +class TaggedObject(models.Model): + object_id = models.PositiveIntegerField("Object ID") + tag = models.ForeignKey(Tag) + content_object = generic.GenericForeignKey() + +class UserTaggedObject(models.Model): + object_tag = models.ForeignKey(TaggedObject) + +class ArticleAttachment(models.Model): + tags = generic.GenericRelation(TaggedObject) + user_tags = generic.GenericRelation(UserTaggedObject) + model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer. invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer. invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer. @@ -315,4 +331,5 @@ class Meta: invalid_models.nonuniquefktarget1: Field 'bad' under model 'FKTarget' must have a unique=True constraint. invalid_models.nonuniquefktarget2: Field 'bad' under model 'FKTarget' must have a unique=True constraint. invalid_models.nonexistingorderingwithsingleunderscore: "ordering" refers to "does_not_exist", a field that doesn't exist. +invalid_models.articleattachment: Model 'UserTaggedObject' must have a GenericForeignKey in order to create a GenericRelation that points to it. """ From bfab752286f72af8c6d4be3f30457d669610eab5 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 16 Nov 2010 02:21:38 +0000 Subject: [PATCH 449/902] [1.2.X] Fixed #14696, corrected some messed up syntax in the docs. Backport of [14566]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14567 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/ref/models/relations.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ref/models/relations.txt b/docs/ref/models/relations.txt index 0481644d7aea..8ca3be0be835 100644 --- a/docs/ref/models/relations.txt +++ b/docs/ref/models/relations.txt @@ -61,11 +61,11 @@ Related objects reference >>> b = Blog.objects.get(id=1) >>> e = Entry( - .... blog=b, - .... headline='Hello', - .... body_text='Hi', - .... pub_date=datetime.date(2005, 1, 1) - .... ) + ... blog=b, + ... headline='Hello', + ... body_text='Hi', + ... pub_date=datetime.date(2005, 1, 1) + ... ) >>> e.save(force_insert=True) Note that there's no need to specify the keyword argument of the model From 478a4e22ad147175ca8672963154e196dbeb1ab6 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 16 Nov 2010 02:36:00 +0000 Subject: [PATCH 450/902] [1.2.X] Added a test for using an `__in` lookup with a ValueListQueryset from a none() call. Refs #14622. Backport of [14568]. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14569 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/regressiontests/queries/tests.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py index 7302e700e4e9..71a0504f8b34 100644 --- a/tests/regressiontests/queries/tests.py +++ b/tests/regressiontests/queries/tests.py @@ -1430,15 +1430,21 @@ def test_evaluated_queryset_as_argument(self): class EmptyQuerySetTests(TestCase): def test_emptyqueryset_values(self): - # #14366 -- calling .values() on an EmptyQuerySet and then cloning that - # should not cause an error - self.assertEqual(list(Number.objects.none().values('num').order_by('num')), []) + # #14366 -- Calling .values() on an EmptyQuerySet and then cloning that + # should not cause an error" + self.assertQuerysetEqual( + Number.objects.none().values('num').order_by('num'), [] + ) def test_values_subquery(self): self.assertQuerysetEqual( Number.objects.filter(pk__in=Number.objects.none().values("pk")), [] ) + self.assertQuerysetEqual( + Number.objects.filter(pk__in=Number.objects.none().values_list("pk")), + [] + ) class WeirdQuerysetSlicingTests(BaseQuerysetTest): From 3cbcce429c0f3fdbd9cfca0a7678a5a994262676 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Tue, 16 Nov 2010 13:32:07 +0000 Subject: [PATCH 451/902] [1.2.X] Corrected 'email' to 'e-mail' in error reporting docs. Backport of [14571] from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14572 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- docs/howto/error-reporting.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index 2c197e1acbca..9c61c97f658e 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -9,7 +9,7 @@ revealed by the error pages. However, running with :setting:`DEBUG` set to ``False`` means you'll never see errors generated by your site -- everyone will just see your public error pages. You need to keep track of errors that occur in deployed sites, so Django can be -configured to email you details of those errors. +configured to e-mail you details of those errors. Server errors ------------- @@ -32,8 +32,8 @@ the HTTP request that caused the error. documentation ` for a full list of email-related settings. -By default, Django will send email from root@localhost. However, some mail -providers reject all email from this address. To use a different sender +By default, Django will send e-mail from root@localhost. However, some mail +providers reject all e-mail from this address. To use a different sender address, modify the :setting:`SERVER_EMAIL` setting. To disable this behavior, just remove all entries from the :setting:`ADMINS` @@ -42,8 +42,8 @@ setting. 404 errors ---------- -Django can also be configured to email errors about broken links (404 "page -not found" errors). Django sends emails about 404 errors when: +Django can also be configured to e-mail errors about broken links (404 "page +not found" errors). Django sends e-mails about 404 errors when: * :setting:`DEBUG` is ``False`` From 587c66f7a0020bedccda7368b93eb025c84d6ac0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 16 Nov 2010 14:48:52 +0000 Subject: [PATCH 452/902] [1.2.X] Migrated forms (minus localflavor) doctests. A huge thanks to Daniel Lindsley for the patch. Backport of r14570 from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14575 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 2 +- tests/regressiontests/forms/error_messages.py | 399 ---- tests/regressiontests/forms/forms.py | 1887 ----------------- tests/regressiontests/forms/formsets.py | 724 ------- .../forms/{tests.py => localflavortests.py} | 21 - tests/regressiontests/forms/media.py | 385 ---- tests/regressiontests/forms/models.py | 185 +- tests/regressiontests/forms/regressions.py | 135 -- tests/regressiontests/forms/tests/__init__.py | 14 + .../forms/tests/error_messages.py | 253 +++ .../forms/{ => tests}/extra.py | 564 +++-- .../forms/{ => tests}/fields.py | 142 +- tests/regressiontests/forms/tests/forms.py | 1700 +++++++++++++++ tests/regressiontests/forms/tests/formsets.py | 763 +++++++ .../forms/{ => tests}/input_formats.py | 0 tests/regressiontests/forms/tests/media.py | 460 ++++ tests/regressiontests/forms/tests/models.py | 169 ++ .../forms/tests/regressions.py | 122 ++ tests/regressiontests/forms/tests/util.py | 57 + .../forms/{ => tests}/validators.py | 0 tests/regressiontests/forms/tests/widgets.py | 1069 ++++++++++ tests/regressiontests/forms/util.py | 60 - tests/regressiontests/forms/widgets.py | 1330 ------------ 23 files changed, 4930 insertions(+), 5511 deletions(-) delete mode 100644 tests/regressiontests/forms/error_messages.py delete mode 100644 tests/regressiontests/forms/forms.py delete mode 100644 tests/regressiontests/forms/formsets.py rename tests/regressiontests/forms/{tests.py => localflavortests.py} (80%) delete mode 100644 tests/regressiontests/forms/media.py delete mode 100644 tests/regressiontests/forms/regressions.py create mode 100644 tests/regressiontests/forms/tests/__init__.py create mode 100644 tests/regressiontests/forms/tests/error_messages.py rename tests/regressiontests/forms/{ => tests}/extra.py (54%) rename tests/regressiontests/forms/{ => tests}/fields.py (95%) create mode 100644 tests/regressiontests/forms/tests/forms.py create mode 100644 tests/regressiontests/forms/tests/formsets.py rename tests/regressiontests/forms/{ => tests}/input_formats.py (100%) create mode 100644 tests/regressiontests/forms/tests/media.py create mode 100644 tests/regressiontests/forms/tests/models.py create mode 100644 tests/regressiontests/forms/tests/regressions.py create mode 100644 tests/regressiontests/forms/tests/util.py rename tests/regressiontests/forms/{ => tests}/validators.py (100%) create mode 100644 tests/regressiontests/forms/tests/widgets.py delete mode 100644 tests/regressiontests/forms/util.py delete mode 100644 tests/regressiontests/forms/widgets.py diff --git a/AUTHORS b/AUTHORS index 3f1ff28801fd..5929e5c1035f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -305,7 +305,7 @@ answer newbie questions, and generally made Django that much better: limodou Philip Lindborg Simon Litchfield - Daniel Lindsley + Daniel Lindsley Trey Long Laurent Luce Martin Mahner diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py deleted file mode 100644 index 038fa39f6bc8..000000000000 --- a/tests/regressiontests/forms/error_messages.py +++ /dev/null @@ -1,399 +0,0 @@ -# -*- coding: utf-8 -*- -tests = r""" ->>> from django.forms import * ->>> from django.core.files.uploadedfile import SimpleUploadedFile - -# CharField ################################################################### - ->>> e = {'required': 'REQUIRED'} ->>> e['min_length'] = 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s' ->>> e['max_length'] = 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s' ->>> f = CharField(min_length=5, max_length=10, error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('1234') -Traceback (most recent call last): -... -ValidationError: [u'LENGTH 4, MIN LENGTH 5'] ->>> f.clean('12345678901') -Traceback (most recent call last): -... -ValidationError: [u'LENGTH 11, MAX LENGTH 10'] - -# IntegerField ################################################################ - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> e['min_value'] = 'MIN VALUE IS %(limit_value)s' ->>> e['max_value'] = 'MAX VALUE IS %(limit_value)s' ->>> f = IntegerField(min_value=5, max_value=10, error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abc') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] ->>> f.clean('4') -Traceback (most recent call last): -... -ValidationError: [u'MIN VALUE IS 5'] ->>> f.clean('11') -Traceback (most recent call last): -... -ValidationError: [u'MAX VALUE IS 10'] - -# FloatField ################################################################## - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> e['min_value'] = 'MIN VALUE IS %(limit_value)s' ->>> e['max_value'] = 'MAX VALUE IS %(limit_value)s' ->>> f = FloatField(min_value=5, max_value=10, error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abc') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] ->>> f.clean('4') -Traceback (most recent call last): -... -ValidationError: [u'MIN VALUE IS 5'] ->>> f.clean('11') -Traceback (most recent call last): -... -ValidationError: [u'MAX VALUE IS 10'] - -# DecimalField ################################################################ - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> e['min_value'] = 'MIN VALUE IS %(limit_value)s' ->>> e['max_value'] = 'MAX VALUE IS %(limit_value)s' ->>> e['max_digits'] = 'MAX DIGITS IS %s' ->>> e['max_decimal_places'] = 'MAX DP IS %s' ->>> e['max_whole_digits'] = 'MAX DIGITS BEFORE DP IS %s' ->>> f = DecimalField(min_value=5, max_value=10, error_messages=e) ->>> f2 = DecimalField(max_digits=4, decimal_places=2, error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abc') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] ->>> f.clean('4') -Traceback (most recent call last): -... -ValidationError: [u'MIN VALUE IS 5'] ->>> f.clean('11') -Traceback (most recent call last): -... -ValidationError: [u'MAX VALUE IS 10'] ->>> f2.clean('123.45') -Traceback (most recent call last): -... -ValidationError: [u'MAX DIGITS IS 4'] ->>> f2.clean('1.234') -Traceback (most recent call last): -... -ValidationError: [u'MAX DP IS 2'] ->>> f2.clean('123.4') -Traceback (most recent call last): -... -ValidationError: [u'MAX DIGITS BEFORE DP IS 2'] - -# DateField ################################################################### - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> f = DateField(error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abc') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] - -# TimeField ################################################################### - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> f = TimeField(error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abc') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] - -# DateTimeField ############################################################### - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> f = DateTimeField(error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abc') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] - -# RegexField ################################################################## - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> e['min_length'] = 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s' ->>> e['max_length'] = 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s' ->>> f = RegexField(r'^\d+$', min_length=5, max_length=10, error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abcde') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] ->>> f.clean('1234') -Traceback (most recent call last): -... -ValidationError: [u'LENGTH 4, MIN LENGTH 5'] ->>> f.clean('12345678901') -Traceback (most recent call last): -... -ValidationError: [u'LENGTH 11, MAX LENGTH 10'] - -# EmailField ################################################################## - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> e['min_length'] = 'LENGTH %(show_value)s, MIN LENGTH %(limit_value)s' ->>> e['max_length'] = 'LENGTH %(show_value)s, MAX LENGTH %(limit_value)s' ->>> f = EmailField(min_length=8, max_length=10, error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abcdefgh') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] ->>> f.clean('a@b.com') -Traceback (most recent call last): -... -ValidationError: [u'LENGTH 7, MIN LENGTH 8'] ->>> f.clean('aye@bee.com') -Traceback (most recent call last): -... -ValidationError: [u'LENGTH 11, MAX LENGTH 10'] - -# FileField ################################################################## - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> e['missing'] = 'MISSING' ->>> e['empty'] = 'EMPTY FILE' ->>> f = FileField(error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abc') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] ->>> f.clean(SimpleUploadedFile('name', None)) -Traceback (most recent call last): -... -ValidationError: [u'EMPTY FILE'] ->>> f.clean(SimpleUploadedFile('name', '')) -Traceback (most recent call last): -... -ValidationError: [u'EMPTY FILE'] - -# URLField ################################################################## - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID' ->>> e['invalid_link'] = 'INVALID LINK' ->>> f = URLField(verify_exists=True, error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('abc.c') -Traceback (most recent call last): -... -ValidationError: [u'INVALID'] ->>> f.clean('http://www.broken.djangoproject.com') -Traceback (most recent call last): -... -ValidationError: [u'INVALID LINK'] - -# BooleanField ################################################################ - ->>> e = {'required': 'REQUIRED'} ->>> f = BooleanField(error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] - -# ChoiceField ################################################################# - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE' ->>> f = ChoiceField(choices=[('a', 'aye')], error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('b') -Traceback (most recent call last): -... -ValidationError: [u'b IS INVALID CHOICE'] - -# MultipleChoiceField ######################################################### - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE' ->>> e['invalid_list'] = 'NOT A LIST' ->>> f = MultipleChoiceField(choices=[('a', 'aye')], error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('b') -Traceback (most recent call last): -... -ValidationError: [u'NOT A LIST'] ->>> f.clean(['b']) -Traceback (most recent call last): -... -ValidationError: [u'b IS INVALID CHOICE'] - -# SplitDateTimeField ########################################################## - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid_date'] = 'INVALID DATE' ->>> e['invalid_time'] = 'INVALID TIME' ->>> f = SplitDateTimeField(error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean(['a', 'b']) -Traceback (most recent call last): -... -ValidationError: [u'INVALID DATE', u'INVALID TIME'] - -# IPAddressField ############################################################## - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid'] = 'INVALID IP ADDRESS' ->>> f = IPAddressField(error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('127.0.0') -Traceback (most recent call last): -... -ValidationError: [u'INVALID IP ADDRESS'] - -############################################################################### - -# Create choices for the model choice field tests below. - ->>> from regressiontests.forms.models import ChoiceModel ->>> ChoiceModel.objects.create(pk=1, name='a') - ->>> ChoiceModel.objects.create(pk=2, name='b') - ->>> ChoiceModel.objects.create(pk=3, name='c') - - -# ModelChoiceField ############################################################ - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid_choice'] = 'INVALID CHOICE' ->>> f = ModelChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('4') -Traceback (most recent call last): -... -ValidationError: [u'INVALID CHOICE'] - -# ModelMultipleChoiceField #################################################### - ->>> e = {'required': 'REQUIRED'} ->>> e['invalid_choice'] = '%s IS INVALID CHOICE' ->>> e['list'] = 'NOT A LIST OF VALUES' ->>> f = ModelMultipleChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e) ->>> f.clean('') -Traceback (most recent call last): -... -ValidationError: [u'REQUIRED'] ->>> f.clean('3') -Traceback (most recent call last): -... -ValidationError: [u'NOT A LIST OF VALUES'] ->>> f.clean(['4']) -Traceback (most recent call last): -... -ValidationError: [u'4 IS INVALID CHOICE'] - -# Subclassing ErrorList ####################################################### - ->>> from django.utils.safestring import mark_safe ->>> ->>> class TestForm(Form): -... first_name = CharField() -... last_name = CharField() -... birthday = DateField() -... -... def clean(self): -... raise ValidationError("I like to be awkward.") -... ->>> class CustomErrorList(util.ErrorList): -... def __unicode__(self): -... return self.as_divs() -... def as_divs(self): -... if not self: return u'' -... return mark_safe(u'
        %s
        ' -... % ''.join([u'

        %s

        ' % e for e in self])) -... - -This form should print errors the default way. - ->>> form1 = TestForm({'first_name': 'John'}) ->>> print form1['last_name'].errors -
        • This field is required.
        ->>> print form1.errors['__all__'] -
        • I like to be awkward.
        - -This one should wrap error groups in the customized way. - ->>> form2 = TestForm({'first_name': 'John'}, error_class=CustomErrorList) ->>> print form2['last_name'].errors -

        This field is required.

        ->>> print form2.errors['__all__'] -

        I like to be awkward.

        - -""" diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py deleted file mode 100644 index 58051fd13367..000000000000 --- a/tests/regressiontests/forms/forms.py +++ /dev/null @@ -1,1887 +0,0 @@ -# -*- coding: utf-8 -*- -tests = r""" ->>> from django.forms import * ->>> from django.core.files.uploadedfile import SimpleUploadedFile ->>> import datetime ->>> import time ->>> import re ->>> from decimal import Decimal - -######### -# Forms # -######### - -A Form is a collection of Fields. It knows how to validate a set of data and it -knows how to render itself in a couple of default ways (e.g., an HTML table). -You can pass it data in __init__(), as a dictionary. - -# Form ######################################################################## - ->>> class Person(Form): -... first_name = CharField() -... last_name = CharField() -... birthday = DateField() - -Pass a dictionary to a Form's __init__(). ->>> p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'}) ->>> p.is_bound -True ->>> p.errors -{} ->>> p.is_valid() -True ->>> p.errors.as_ul() -u'' ->>> p.errors.as_text() -u'' ->>> p.cleaned_data["first_name"], p.cleaned_data["last_name"], p.cleaned_data["birthday"] -(u'John', u'Lennon', datetime.date(1940, 10, 9)) ->>> print p['first_name'] - ->>> print p['last_name'] - ->>> print p['birthday'] - ->>> print p['nonexistentfield'] -Traceback (most recent call last): -... -KeyError: "Key 'nonexistentfield' not found in Form" - ->>> for boundfield in p: -... print boundfield - - - ->>> for boundfield in p: -... print boundfield.label, boundfield.data -First name John -Last name Lennon -Birthday 1940-10-9 ->>> print p -
        - - - -Empty dictionaries are valid, too. ->>> p = Person({}) ->>> p.is_bound -True ->>> p.errors['first_name'] -[u'This field is required.'] ->>> p.errors['last_name'] -[u'This field is required.'] ->>> p.errors['birthday'] -[u'This field is required.'] ->>> p.is_valid() -False ->>> p.cleaned_data -Traceback (most recent call last): -... -AttributeError: 'Person' object has no attribute 'cleaned_data' ->>> print p - - - ->>> print p.as_table() - - - ->>> print p.as_ul() -
        • This field is required.
      • -
        • This field is required.
      • -
        • This field is required.
      • ->>> print p.as_p() -
        • This field is required.
        -

        -
        • This field is required.
        -

        -
        • This field is required.
        -

        - -If you don't pass any values to the Form's __init__(), or if you pass None, -the Form will be considered unbound and won't do any validation. Form.errors -will be an empty dictionary *but* Form.is_valid() will return False. ->>> p = Person() ->>> p.is_bound -False ->>> p.errors -{} ->>> p.is_valid() -False ->>> p.cleaned_data -Traceback (most recent call last): -... -AttributeError: 'Person' object has no attribute 'cleaned_data' ->>> print p -
        - - ->>> print p.as_table() - - - ->>> print p.as_ul() -
      • -
      • -
      • ->>> print p.as_p() -

        -

        -

        - -Unicode values are handled properly. ->>> p = Person({'first_name': u'John', 'last_name': u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111', 'birthday': '1940-10-9'}) ->>> p.as_table() -u'
        \n\n' ->>> p.as_ul() -u'
      • \n
      • \n
      • ' ->>> p.as_p() -u'

        \n

        \n

        ' - ->>> p = Person({'last_name': u'Lennon'}) ->>> p.errors['first_name'] -[u'This field is required.'] ->>> p.errors['birthday'] -[u'This field is required.'] ->>> p.is_valid() -False ->>> p.errors.as_ul() -u'
        • first_name
          • This field is required.
        • birthday
          • This field is required.
        ' ->>> print p.errors.as_text() -* first_name - * This field is required. -* birthday - * This field is required. ->>> p.cleaned_data -Traceback (most recent call last): -... -AttributeError: 'Person' object has no attribute 'cleaned_data' ->>> p['first_name'].errors -[u'This field is required.'] ->>> p['first_name'].errors.as_ul() -u'
        • This field is required.
        ' ->>> p['first_name'].errors.as_text() -u'* This field is required.' - ->>> p = Person() ->>> print p['first_name'] - ->>> print p['last_name'] - ->>> print p['birthday'] - - -cleaned_data will always *only* contain a key for fields defined in the -Form, even if you pass extra data when you define the Form. In this -example, we pass a bunch of extra fields to the form constructor, -but cleaned_data contains only the form's fields. ->>> data = {'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9', 'extra1': 'hello', 'extra2': 'hello'} ->>> p = Person(data) ->>> p.is_valid() -True ->>> p.cleaned_data['first_name'] -u'John' ->>> p.cleaned_data['last_name'] -u'Lennon' ->>> p.cleaned_data['birthday'] -datetime.date(1940, 10, 9) - - -cleaned_data will include a key and value for *all* fields defined in the Form, -even if the Form's data didn't include a value for fields that are not -required. In this example, the data dictionary doesn't include a value for the -"nick_name" field, but cleaned_data includes it. For CharFields, it's set to the -empty string. ->>> class OptionalPersonForm(Form): -... first_name = CharField() -... last_name = CharField() -... nick_name = CharField(required=False) ->>> data = {'first_name': u'John', 'last_name': u'Lennon'} ->>> f = OptionalPersonForm(data) ->>> f.is_valid() -True ->>> f.cleaned_data['nick_name'] -u'' ->>> f.cleaned_data['first_name'] -u'John' ->>> f.cleaned_data['last_name'] -u'Lennon' - -For DateFields, it's set to None. ->>> class OptionalPersonForm(Form): -... first_name = CharField() -... last_name = CharField() -... birth_date = DateField(required=False) ->>> data = {'first_name': u'John', 'last_name': u'Lennon'} ->>> f = OptionalPersonForm(data) ->>> f.is_valid() -True ->>> print f.cleaned_data['birth_date'] -None ->>> f.cleaned_data['first_name'] -u'John' ->>> f.cleaned_data['last_name'] -u'Lennon' - -"auto_id" tells the Form to add an "id" attribute to each form element. -If it's a string that contains '%s', Django will use that as a format string -into which the field's name will be inserted. It will also put a
        - - ->>> print p.as_ul() -
      • -
      • -
      • ->>> print p.as_p() -

        -

        -

        - -If auto_id is any True value whose str() does not contain '%s', the "id" -attribute will be the name of the field. ->>> p = Person(auto_id=True) ->>> print p.as_ul() -
      • -
      • -
      • - -If auto_id is any False value, an "id" attribute won't be output unless it -was manually entered. ->>> p = Person(auto_id=False) ->>> print p.as_ul() -
      • First name:
      • -
      • Last name:
      • -
      • Birthday:
      • - -In this example, auto_id is False, but the "id" attribute for the "first_name" -field is given. Also note that field gets a
        - ->>> print f.as_ul() -
      • Name:
      • -
      • Language:
          -
        • -
        • -
      • - -Regarding auto_id and
        - ->>> print f.as_ul() -
      • -
        • -
        • -
        • -
      • ->>> print f.as_p() -

        -

          -
        • -
        • -

        - -MultipleChoiceField is a special case, as its data is required to be a list: ->>> class SongForm(Form): -... name = CharField() -... composers = MultipleChoiceField() ->>> f = SongForm(auto_id=False) ->>> print f['composers'] - ->>> class SongForm(Form): -... name = CharField() -... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')]) ->>> f = SongForm(auto_id=False) ->>> print f['composers'] - ->>> f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False) ->>> print f['name'] - ->>> print f['composers'] - - -MultipleChoiceField rendered as_hidden() is a special case. Because it can -have multiple values, its as_hidden() renders multiple -tags. ->>> f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False) ->>> print f['composers'].as_hidden() - ->>> f = SongForm({'name': 'From Me To You', 'composers': ['P', 'J']}, auto_id=False) ->>> print f['composers'].as_hidden() - - - -MultipleChoiceField can also be used with the CheckboxSelectMultiple widget. ->>> class SongForm(Form): -... name = CharField() -... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=CheckboxSelectMultiple) ->>> f = SongForm(auto_id=False) ->>> print f['composers'] -
          -
        • -
        • -
        ->>> f = SongForm({'composers': ['J']}, auto_id=False) ->>> print f['composers'] -
          -
        • -
        • -
        ->>> f = SongForm({'composers': ['J', 'P']}, auto_id=False) ->>> print f['composers'] -
          -
        • -
        • -
        - -Regarding auto_id, CheckboxSelectMultiple is a special case. Each checkbox -gets a distinct ID, formed by appending an underscore plus the checkbox's -zero-based index. ->>> f = SongForm(auto_id='%s_id') ->>> print f['composers'] -
          -
        • -
        • -
        - -Data for a MultipleChoiceField should be a list. QueryDict, MultiValueDict and -MergeDict (when created as a merge of MultiValueDicts) conveniently work with -this. ->>> data = {'name': 'Yesterday', 'composers': ['J', 'P']} ->>> f = SongForm(data) ->>> f.errors -{} ->>> from django.http import QueryDict ->>> data = QueryDict('name=Yesterday&composers=J&composers=P') ->>> f = SongForm(data) ->>> f.errors -{} ->>> from django.utils.datastructures import MultiValueDict ->>> data = MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])) ->>> f = SongForm(data) ->>> f.errors -{} ->>> from django.utils.datastructures import MergeDict ->>> data = MergeDict(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P']))) ->>> f = SongForm(data) ->>> f.errors -{} - -The MultipleHiddenInput widget renders multiple values as hidden fields. ->>> class SongFormHidden(Form): -... name = CharField() -... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput) ->>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False) ->>> print f.as_ul() -
      • Name: -
      • - -When using CheckboxSelectMultiple, the framework expects a list of input and -returns a list of input. ->>> f = SongForm({'name': 'Yesterday'}, auto_id=False) ->>> f.errors['composers'] -[u'This field is required.'] ->>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False) ->>> f.errors -{} ->>> f.cleaned_data['composers'] -[u'J'] ->>> f.cleaned_data['name'] -u'Yesterday' ->>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False) ->>> f.errors -{} ->>> f.cleaned_data['composers'] -[u'J', u'P'] ->>> f.cleaned_data['name'] -u'Yesterday' - -Validation errors are HTML-escaped when output as HTML. ->>> from django.utils.safestring import mark_safe ->>> class EscapingForm(Form): -... special_name = CharField(label="Special Field") -... special_safe_name = CharField(label=mark_safe("Special Field")) -... def clean_special_name(self): -... raise ValidationError("Something's wrong with '%s'" % self.cleaned_data['special_name']) -... def clean_special_safe_name(self): -... raise ValidationError(mark_safe("'%s' is a safe string" % self.cleaned_data['special_safe_name'])) - ->>> f = EscapingForm({'special_name': "Nothing to escape", 'special_safe_name': "Nothing to escape"}, auto_id=False) ->>> print f -
        - ->>> f = EscapingForm( -... {'special_name': "Should escape < & > and ", -... 'special_safe_name': "Do not escape"}, auto_id=False) ->>> print f - - - -""" + \ -r""" # [This concatenation is to keep the string below the jython's 32K limit]. -# Validating multiple fields in relation to another ########################### - -There are a couple of ways to do multiple-field validation. If you want the -validation message to be associated with a particular field, implement the -clean_XXX() method on the Form, where XXX is the field name. As in -Field.clean(), the clean_XXX() method should return the cleaned value. In the -clean_XXX() method, you have access to self.cleaned_data, which is a dictionary -of all the data that has been cleaned *so far*, in order by the fields, -including the current field (e.g., the field XXX if you're in clean_XXX()). ->>> class UserRegistration(Form): -... username = CharField(max_length=10) -... password1 = CharField(widget=PasswordInput) -... password2 = CharField(widget=PasswordInput) -... def clean_password2(self): -... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: -... raise ValidationError(u'Please make sure your passwords match.') -... return self.cleaned_data['password2'] ->>> f = UserRegistration(auto_id=False) ->>> f.errors -{} ->>> f = UserRegistration({}, auto_id=False) ->>> f.errors['username'] -[u'This field is required.'] ->>> f.errors['password1'] -[u'This field is required.'] ->>> f.errors['password2'] -[u'This field is required.'] ->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False) ->>> f.errors['password2'] -[u'Please make sure your passwords match.'] ->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) ->>> f.errors -{} ->>> f.cleaned_data['username'] -u'adrian' ->>> f.cleaned_data['password1'] -u'foo' ->>> f.cleaned_data['password2'] -u'foo' - -Another way of doing multiple-field validation is by implementing the -Form's clean() method. If you do this, any ValidationError raised by that -method will not be associated with a particular field; it will have a -special-case association with the field named '__all__'. -Note that in Form.clean(), you have access to self.cleaned_data, a dictionary of -all the fields/values that have *not* raised a ValidationError. Also note -Form.clean() is required to return a dictionary of all clean data. ->>> class UserRegistration(Form): -... username = CharField(max_length=10) -... password1 = CharField(widget=PasswordInput) -... password2 = CharField(widget=PasswordInput) -... def clean(self): -... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: -... raise ValidationError(u'Please make sure your passwords match.') -... return self.cleaned_data ->>> f = UserRegistration(auto_id=False) ->>> f.errors -{} ->>> f = UserRegistration({}, auto_id=False) ->>> print f.as_table() - - - ->>> f.errors['username'] -[u'This field is required.'] ->>> f.errors['password1'] -[u'This field is required.'] ->>> f.errors['password2'] -[u'This field is required.'] ->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False) ->>> f.errors['__all__'] -[u'Please make sure your passwords match.'] ->>> print f.as_table() - - - - ->>> print f.as_ul() -
        • Please make sure your passwords match.
      • -
      • Username:
      • -
      • Password1:
      • -
      • Password2:
      • ->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) ->>> f.errors -{} ->>> f.cleaned_data['username'] -u'adrian' ->>> f.cleaned_data['password1'] -u'foo' ->>> f.cleaned_data['password2'] -u'foo' - -# Dynamic construction ######################################################## - -It's possible to construct a Form dynamically by adding to the self.fields -dictionary in __init__(). Don't forget to call Form.__init__() within the -subclass' __init__(). ->>> class Person(Form): -... first_name = CharField() -... last_name = CharField() -... def __init__(self, *args, **kwargs): -... super(Person, self).__init__(*args, **kwargs) -... self.fields['birthday'] = DateField() ->>> p = Person(auto_id=False) ->>> print p -
        - - - -Instances of a dynamic Form do not persist fields from one Form instance to -the next. ->>> class MyForm(Form): -... def __init__(self, data=None, auto_id=False, field_list=[]): -... Form.__init__(self, data, auto_id=auto_id) -... for field in field_list: -... self.fields[field[0]] = field[1] ->>> field_list = [('field1', CharField()), ('field2', CharField())] ->>> my_form = MyForm(field_list=field_list) ->>> print my_form - - ->>> field_list = [('field3', CharField()), ('field4', CharField())] ->>> my_form = MyForm(field_list=field_list) ->>> print my_form - - - ->>> class MyForm(Form): -... default_field_1 = CharField() -... default_field_2 = CharField() -... def __init__(self, data=None, auto_id=False, field_list=[]): -... Form.__init__(self, data, auto_id=auto_id) -... for field in field_list: -... self.fields[field[0]] = field[1] ->>> field_list = [('field1', CharField()), ('field2', CharField())] ->>> my_form = MyForm(field_list=field_list) ->>> print my_form - - - - ->>> field_list = [('field3', CharField()), ('field4', CharField())] ->>> my_form = MyForm(field_list=field_list) ->>> print my_form - - - - - -Similarly, changes to field attributes do not persist from one Form instance -to the next. ->>> class Person(Form): -... first_name = CharField(required=False) -... last_name = CharField(required=False) -... def __init__(self, names_required=False, *args, **kwargs): -... super(Person, self).__init__(*args, **kwargs) -... if names_required: -... self.fields['first_name'].required = True -... self.fields['first_name'].widget.attrs['class'] = 'required' -... self.fields['last_name'].required = True -... self.fields['last_name'].widget.attrs['class'] = 'required' ->>> f = Person(names_required=False) ->>> f['first_name'].field.required, f['last_name'].field.required -(False, False) ->>> f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs -({}, {}) ->>> f = Person(names_required=True) ->>> f['first_name'].field.required, f['last_name'].field.required -(True, True) ->>> f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs -({'class': 'required'}, {'class': 'required'}) ->>> f = Person(names_required=False) ->>> f['first_name'].field.required, f['last_name'].field.required -(False, False) ->>> f['first_name'].field.widget.attrs, f['last_name'].field.widget.attrs -({}, {}) ->>> class Person(Form): -... first_name = CharField(max_length=30) -... last_name = CharField(max_length=30) -... def __init__(self, name_max_length=None, *args, **kwargs): -... super(Person, self).__init__(*args, **kwargs) -... if name_max_length: -... self.fields['first_name'].max_length = name_max_length -... self.fields['last_name'].max_length = name_max_length ->>> f = Person(name_max_length=None) ->>> f['first_name'].field.max_length, f['last_name'].field.max_length -(30, 30) ->>> f = Person(name_max_length=20) ->>> f['first_name'].field.max_length, f['last_name'].field.max_length -(20, 20) ->>> f = Person(name_max_length=None) ->>> f['first_name'].field.max_length, f['last_name'].field.max_length -(30, 30) - -HiddenInput widgets are displayed differently in the as_table(), as_ul() -and as_p() output of a Form -- their verbose names are not displayed, and a -separate row is not displayed. They're displayed in the last row of the -form, directly after that row's form element. ->>> class Person(Form): -... first_name = CharField() -... last_name = CharField() -... hidden_text = CharField(widget=HiddenInput) -... birthday = DateField() ->>> p = Person(auto_id=False) ->>> print p - - - ->>> print p.as_ul() -
      • First name:
      • -
      • Last name:
      • -
      • Birthday:
      • ->>> print p.as_p() -

        First name:

        -

        Last name:

        -

        Birthday:

        - -With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label. ->>> p = Person(auto_id='id_%s') ->>> print p -
        - - ->>> print p.as_ul() -
      • -
      • -
      • ->>> print p.as_p() -

        -

        -

        - -If a field with a HiddenInput has errors, the as_table() and as_ul() output -will include the error message(s) with the text "(Hidden field [fieldname]) " -prepended. This message is displayed at the top of the output, regardless of -its field's order in the form. ->>> p = Person({'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9'}, auto_id=False) ->>> print p -
        - - - ->>> print p.as_ul() -
        • (Hidden field hidden_text) This field is required.
      • -
      • First name:
      • -
      • Last name:
      • -
      • Birthday:
      • ->>> print p.as_p() -
        • (Hidden field hidden_text) This field is required.
        -

        First name:

        -

        Last name:

        -

        Birthday:

        - -A corner case: It's possible for a form to have only HiddenInputs. ->>> class TestForm(Form): -... foo = CharField(widget=HiddenInput) -... bar = CharField(widget=HiddenInput) ->>> p = TestForm(auto_id=False) ->>> print p.as_table() - ->>> print p.as_ul() - ->>> print p.as_p() - - -A Form's fields are displayed in the same order in which they were defined. ->>> class TestForm(Form): -... field1 = CharField() -... field2 = CharField() -... field3 = CharField() -... field4 = CharField() -... field5 = CharField() -... field6 = CharField() -... field7 = CharField() -... field8 = CharField() -... field9 = CharField() -... field10 = CharField() -... field11 = CharField() -... field12 = CharField() -... field13 = CharField() -... field14 = CharField() ->>> p = TestForm(auto_id=False) ->>> print p -
        - - - - - - - - - - - - - - -Some Field classes have an effect on the HTML attributes of their associated -Widget. If you set max_length in a CharField and its associated widget is -either a TextInput or PasswordInput, then the widget's rendered HTML will -include the "maxlength" attribute. ->>> class UserRegistration(Form): -... username = CharField(max_length=10) # uses TextInput by default -... password = CharField(max_length=10, widget=PasswordInput) -... realname = CharField(max_length=10, widget=TextInput) # redundantly define widget, just to test -... address = CharField() # no max_length defined here ->>> p = UserRegistration(auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • -
      • Realname:
      • -
      • Address:
      • - -If you specify a custom "attrs" that includes the "maxlength" attribute, -the Field's max_length attribute will override whatever "maxlength" you specify -in "attrs". ->>> class UserRegistration(Form): -... username = CharField(max_length=10, widget=TextInput(attrs={'maxlength': 20})) -... password = CharField(max_length=10, widget=PasswordInput) ->>> p = UserRegistration(auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • - -# Specifying labels ########################################################### - -You can specify the label for a field by using the 'label' argument to a Field -class. If you don't specify 'label', Django will use the field name with -underscores converted to spaces, and the initial letter capitalized. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, label='Your username') -... password1 = CharField(widget=PasswordInput) -... password2 = CharField(widget=PasswordInput, label='Password (again)') ->>> p = UserRegistration(auto_id=False) ->>> print p.as_ul() -
      • Your username:
      • -
      • Password1:
      • -
      • Password (again):
      • - -Labels for as_* methods will only end in a colon if they don't end in other -punctuation already. ->>> class Questions(Form): -... q1 = CharField(label='The first question') -... q2 = CharField(label='What is your name?') -... q3 = CharField(label='The answer to life is:') -... q4 = CharField(label='Answer this question!') -... q5 = CharField(label='The last question. Period.') ->>> print Questions(auto_id=False).as_p() -

        The first question:

        -

        What is your name?

        -

        The answer to life is:

        -

        Answer this question!

        -

        The last question. Period.

        ->>> print Questions().as_p() -

        -

        -

        -

        -

        - -A label can be a Unicode object or a bytestring with special characters. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, label='ŠĐĆŽćžšđ') -... password = CharField(widget=PasswordInput, label=u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111') ->>> p = UserRegistration(auto_id=False) ->>> p.as_ul() -u'
      • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
      • \n
      • \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111:
      • ' - -If a label is set to the empty string for a field, that field won't get a label. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, label='') -... password = CharField(widget=PasswordInput) ->>> p = UserRegistration(auto_id=False) ->>> print p.as_ul() -
      • -
      • Password:
      • ->>> p = UserRegistration(auto_id='id_%s') ->>> print p.as_ul() -
      • -
      • - -If label is None, Django will auto-create the label from the field name. This -is default behavior. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, label=None) -... password = CharField(widget=PasswordInput) ->>> p = UserRegistration(auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • ->>> p = UserRegistration(auto_id='id_%s') ->>> print p.as_ul() -
      • -
      • - - -# Label Suffix ################################################################ - -You can specify the 'label_suffix' argument to a Form class to modify the -punctuation symbol used at the end of a label. By default, the colon (:) is -used, and is only appended to the label if the label doesn't already end with a -punctuation symbol: ., !, ? or :. If you specify a different suffix, it will -be appended regardless of the last character of the label. - ->>> class FavoriteForm(Form): -... color = CharField(label='Favorite color?') -... animal = CharField(label='Favorite animal') -... ->>> f = FavoriteForm(auto_id=False) ->>> print f.as_ul() -
      • Favorite color?
      • -
      • Favorite animal:
      • ->>> f = FavoriteForm(auto_id=False, label_suffix='?') ->>> print f.as_ul() -
      • Favorite color?
      • -
      • Favorite animal?
      • ->>> f = FavoriteForm(auto_id=False, label_suffix='') ->>> print f.as_ul() -
      • Favorite color?
      • -
      • Favorite animal
      • ->>> f = FavoriteForm(auto_id=False, label_suffix=u'\u2192') ->>> f.as_ul() -u'
      • Favorite color?
      • \n
      • Favorite animal\u2192
      • ' - -""" + \ -r""" # [This concatenation is to keep the string below the jython's 32K limit]. - -# Initial data ################################################################ - -You can specify initial data for a field by using the 'initial' argument to a -Field class. This initial data is displayed when a Form is rendered with *no* -data. It is not displayed when a Form is rendered with any data (including an -empty dictionary). Also, the initial value is *not* used if data for a -particular required field isn't provided. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, initial='django') -... password = CharField(widget=PasswordInput) - -Here, we're not submitting any data, so the initial value will be displayed. ->>> p = UserRegistration(auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • - -Here, we're submitting data, so the initial value will *not* be displayed. ->>> p = UserRegistration({}, auto_id=False) ->>> print p.as_ul() -
        • This field is required.
        Username:
      • -
        • This field is required.
        Password:
      • ->>> p = UserRegistration({'username': u''}, auto_id=False) ->>> print p.as_ul() -
        • This field is required.
        Username:
      • -
        • This field is required.
        Password:
      • ->>> p = UserRegistration({'username': u'foo'}, auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
        • This field is required.
        Password:
      • - -An 'initial' value is *not* used as a fallback if data is not provided. In this -example, we don't provide a value for 'username', and the form raises a -validation error rather than using the initial value for 'username'. ->>> p = UserRegistration({'password': 'secret'}) ->>> p.errors['username'] -[u'This field is required.'] ->>> p.is_valid() -False - -# Dynamic initial data ######################################################## - -The previous technique dealt with "hard-coded" initial data, but it's also -possible to specify initial data after you've already created the Form class -(i.e., at runtime). Use the 'initial' parameter to the Form constructor. This -should be a dictionary containing initial values for one or more fields in the -form, keyed by field name. - ->>> class UserRegistration(Form): -... username = CharField(max_length=10) -... password = CharField(widget=PasswordInput) - -Here, we're not submitting any data, so the initial value will be displayed. ->>> p = UserRegistration(initial={'username': 'django'}, auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • ->>> p = UserRegistration(initial={'username': 'stephane'}, auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • - -The 'initial' parameter is meaningless if you pass data. ->>> p = UserRegistration({}, initial={'username': 'django'}, auto_id=False) ->>> print p.as_ul() -
        • This field is required.
        Username:
      • -
        • This field is required.
        Password:
      • ->>> p = UserRegistration({'username': u''}, initial={'username': 'django'}, auto_id=False) ->>> print p.as_ul() -
        • This field is required.
        Username:
      • -
        • This field is required.
        Password:
      • ->>> p = UserRegistration({'username': u'foo'}, initial={'username': 'django'}, auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
        • This field is required.
        Password:
      • - -A dynamic 'initial' value is *not* used as a fallback if data is not provided. -In this example, we don't provide a value for 'username', and the form raises a -validation error rather than using the initial value for 'username'. ->>> p = UserRegistration({'password': 'secret'}, initial={'username': 'django'}) ->>> p.errors['username'] -[u'This field is required.'] ->>> p.is_valid() -False - -If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(), -then the latter will get precedence. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, initial='django') -... password = CharField(widget=PasswordInput) ->>> p = UserRegistration(initial={'username': 'babik'}, auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • - -# Callable initial data ######################################################## - -The previous technique dealt with raw values as initial data, but it's also -possible to specify callable data. - ->>> class UserRegistration(Form): -... username = CharField(max_length=10) -... password = CharField(widget=PasswordInput) -... options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')]) - -We need to define functions that get called later. ->>> def initial_django(): -... return 'django' ->>> def initial_stephane(): -... return 'stephane' ->>> def initial_options(): -... return ['f','b'] ->>> def initial_other_options(): -... return ['b','w'] - - -Here, we're not submitting any data, so the initial value will be displayed. ->>> p = UserRegistration(initial={'username': initial_django, 'options': initial_options}, auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • -
      • Options:
      • - -The 'initial' parameter is meaningless if you pass data. ->>> p = UserRegistration({}, initial={'username': initial_django, 'options': initial_options}, auto_id=False) ->>> print p.as_ul() -
        • This field is required.
        Username:
      • -
        • This field is required.
        Password:
      • -
        • This field is required.
        Options:
      • ->>> p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False) ->>> print p.as_ul() -
        • This field is required.
        Username:
      • -
        • This field is required.
        Password:
      • -
        • This field is required.
        Options:
      • ->>> p = UserRegistration({'username': u'foo', 'options':['f','b']}, initial={'username': initial_django}, auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
        • This field is required.
        Password:
      • -
      • Options:
      • - -A callable 'initial' value is *not* used as a fallback if data is not provided. -In this example, we don't provide a value for 'username', and the form raises a -validation error rather than using the initial value for 'username'. ->>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django, 'options': initial_options}) ->>> p.errors['username'] -[u'This field is required.'] ->>> p.is_valid() -False - -If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(), -then the latter will get precedence. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, initial=initial_django) -... password = CharField(widget=PasswordInput) -... options = MultipleChoiceField(choices=[('f','foo'),('b','bar'),('w','whiz')], initial=initial_other_options) - ->>> p = UserRegistration(auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • -
      • Options:
      • ->>> p = UserRegistration(initial={'username': initial_stephane, 'options': initial_options}, auto_id=False) ->>> print p.as_ul() -
      • Username:
      • -
      • Password:
      • -
      • Options:
      • - -# Help text ################################################################### - -You can specify descriptive text for a field by using the 'help_text' argument -to a Field class. This help text is displayed when a Form is rendered. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, help_text='e.g., user@example.com') -... password = CharField(widget=PasswordInput, help_text='Choose wisely.') ->>> p = UserRegistration(auto_id=False) ->>> print p.as_ul() -
      • Username: e.g., user@example.com
      • -
      • Password: Choose wisely.
      • ->>> print p.as_p() -

        Username: e.g., user@example.com

        -

        Password: Choose wisely.

        ->>> print p.as_table() -
        - - -The help text is displayed whether or not data is provided for the form. ->>> p = UserRegistration({'username': u'foo'}, auto_id=False) ->>> print p.as_ul() -
      • Username: e.g., user@example.com
      • -
        • This field is required.
        Password: Choose wisely.
      • - -help_text is not displayed for hidden fields. It can be used for documentation -purposes, though. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, help_text='e.g., user@example.com') -... password = CharField(widget=PasswordInput) -... next = CharField(widget=HiddenInput, initial='/', help_text='Redirect destination') ->>> p = UserRegistration(auto_id=False) ->>> print p.as_ul() -
      • Username: e.g., user@example.com
      • -
      • Password:
      • - -Help text can include arbitrary Unicode characters. ->>> class UserRegistration(Form): -... username = CharField(max_length=10, help_text='ŠĐĆŽćžšđ') ->>> p = UserRegistration(auto_id=False) ->>> p.as_ul() -u'
      • Username: \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111
      • ' - -# Subclassing forms ########################################################### - -You can subclass a Form to add fields. The resulting form subclass will have -all of the fields of the parent Form, plus whichever fields you define in the -subclass. ->>> class Person(Form): -... first_name = CharField() -... last_name = CharField() -... birthday = DateField() ->>> class Musician(Person): -... instrument = CharField() ->>> p = Person(auto_id=False) ->>> print p.as_ul() -
      • First name:
      • -
      • Last name:
      • -
      • Birthday:
      • ->>> m = Musician(auto_id=False) ->>> print m.as_ul() -
      • First name:
      • -
      • Last name:
      • -
      • Birthday:
      • -
      • Instrument:
      • - -Yes, you can subclass multiple forms. The fields are added in the order in -which the parent classes are listed. ->>> class Person(Form): -... first_name = CharField() -... last_name = CharField() -... birthday = DateField() ->>> class Instrument(Form): -... instrument = CharField() ->>> class Beatle(Person, Instrument): -... haircut_type = CharField() ->>> b = Beatle(auto_id=False) ->>> print b.as_ul() -
      • First name:
      • -
      • Last name:
      • -
      • Birthday:
      • -
      • Instrument:
      • -
      • Haircut type:
      • - -# Forms with prefixes ######################################################### - -Sometimes it's necessary to have multiple forms display on the same HTML page, -or multiple copies of the same form. We can accomplish this with form prefixes. -Pass the keyword argument 'prefix' to the Form constructor to use this feature. -This value will be prepended to each HTML form field name. One way to think -about this is "namespaces for HTML forms". Notice that in the data argument, -each field's key has the prefix, in this case 'person1', prepended to the -actual field name. ->>> class Person(Form): -... first_name = CharField() -... last_name = CharField() -... birthday = DateField() ->>> data = { -... 'person1-first_name': u'John', -... 'person1-last_name': u'Lennon', -... 'person1-birthday': u'1940-10-9' -... } ->>> p = Person(data, prefix='person1') ->>> print p.as_ul() -
      • -
      • -
      • ->>> print p['first_name'] - ->>> print p['last_name'] - ->>> print p['birthday'] - ->>> p.errors -{} ->>> p.is_valid() -True ->>> p.cleaned_data['first_name'] -u'John' ->>> p.cleaned_data['last_name'] -u'Lennon' ->>> p.cleaned_data['birthday'] -datetime.date(1940, 10, 9) - -Let's try submitting some bad data to make sure form.errors and field.errors -work as expected. ->>> data = { -... 'person1-first_name': u'', -... 'person1-last_name': u'', -... 'person1-birthday': u'' -... } ->>> p = Person(data, prefix='person1') ->>> p.errors['first_name'] -[u'This field is required.'] ->>> p.errors['last_name'] -[u'This field is required.'] ->>> p.errors['birthday'] -[u'This field is required.'] ->>> p['first_name'].errors -[u'This field is required.'] ->>> p['person1-first_name'].errors -Traceback (most recent call last): -... -KeyError: "Key 'person1-first_name' not found in Form" - -In this example, the data doesn't have a prefix, but the form requires it, so -the form doesn't "see" the fields. ->>> data = { -... 'first_name': u'John', -... 'last_name': u'Lennon', -... 'birthday': u'1940-10-9' -... } ->>> p = Person(data, prefix='person1') ->>> p.errors['first_name'] -[u'This field is required.'] ->>> p.errors['last_name'] -[u'This field is required.'] ->>> p.errors['birthday'] -[u'This field is required.'] - -With prefixes, a single data dictionary can hold data for multiple instances -of the same form. ->>> data = { -... 'person1-first_name': u'John', -... 'person1-last_name': u'Lennon', -... 'person1-birthday': u'1940-10-9', -... 'person2-first_name': u'Jim', -... 'person2-last_name': u'Morrison', -... 'person2-birthday': u'1943-12-8' -... } ->>> p1 = Person(data, prefix='person1') ->>> p1.is_valid() -True ->>> p1.cleaned_data['first_name'] -u'John' ->>> p1.cleaned_data['last_name'] -u'Lennon' ->>> p1.cleaned_data['birthday'] -datetime.date(1940, 10, 9) ->>> p2 = Person(data, prefix='person2') ->>> p2.is_valid() -True ->>> p2.cleaned_data['first_name'] -u'Jim' ->>> p2.cleaned_data['last_name'] -u'Morrison' ->>> p2.cleaned_data['birthday'] -datetime.date(1943, 12, 8) - -By default, forms append a hyphen between the prefix and the field name, but a -form can alter that behavior by implementing the add_prefix() method. This -method takes a field name and returns the prefixed field, according to -self.prefix. ->>> class Person(Form): -... first_name = CharField() -... last_name = CharField() -... birthday = DateField() -... def add_prefix(self, field_name): -... return self.prefix and '%s-prefix-%s' % (self.prefix, field_name) or field_name ->>> p = Person(prefix='foo') ->>> print p.as_ul() -
      • -
      • -
      • ->>> data = { -... 'foo-prefix-first_name': u'John', -... 'foo-prefix-last_name': u'Lennon', -... 'foo-prefix-birthday': u'1940-10-9' -... } ->>> p = Person(data, prefix='foo') ->>> p.is_valid() -True ->>> p.cleaned_data['first_name'] -u'John' ->>> p.cleaned_data['last_name'] -u'Lennon' ->>> p.cleaned_data['birthday'] -datetime.date(1940, 10, 9) - -# Forms with NullBooleanFields ################################################ - -NullBooleanField is a bit of a special case because its presentation (widget) -is different than its data. This is handled transparently, though. - ->>> class Person(Form): -... name = CharField() -... is_cool = NullBooleanField() ->>> p = Person({'name': u'Joe'}, auto_id=False) ->>> print p['is_cool'] - ->>> p = Person({'name': u'Joe', 'is_cool': u'1'}, auto_id=False) ->>> print p['is_cool'] - ->>> p = Person({'name': u'Joe', 'is_cool': u'2'}, auto_id=False) ->>> print p['is_cool'] - ->>> p = Person({'name': u'Joe', 'is_cool': u'3'}, auto_id=False) ->>> print p['is_cool'] - ->>> p = Person({'name': u'Joe', 'is_cool': True}, auto_id=False) ->>> print p['is_cool'] - ->>> p = Person({'name': u'Joe', 'is_cool': False}, auto_id=False) ->>> print p['is_cool'] - - -# Forms with FileFields ################################################ - -FileFields are a special case because they take their data from the request.FILES, -not request.POST. - ->>> class FileForm(Form): -... file1 = FileField() ->>> f = FileForm(auto_id=False) ->>> print f -
        - ->>> f = FileForm(data={}, files={}, auto_id=False) ->>> print f - - ->>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', '')}, auto_id=False) ->>> print f - - ->>> f = FileForm(data={}, files={'file1': 'something that is not a file'}, auto_id=False) ->>> print f - - ->>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('name', 'some content')}, auto_id=False) ->>> print f - ->>> f.is_valid() -True - ->>> f = FileForm(data={}, files={'file1': SimpleUploadedFile('我隻氣墊船裝滿晒鱔.txt', 'मेरी मँडराने वाली नाव सर्पमीनों से भरी ह')}, auto_id=False) ->>> print f - - -# Basic form processing in a view ############################################# - ->>> from django.template import Template, Context ->>> class UserRegistration(Form): -... username = CharField(max_length=10) -... password1 = CharField(widget=PasswordInput) -... password2 = CharField(widget=PasswordInput) -... def clean(self): -... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: -... raise ValidationError(u'Please make sure your passwords match.') -... return self.cleaned_data ->>> def my_function(method, post_data): -... if method == 'POST': -... form = UserRegistration(post_data, auto_id=False) -... else: -... form = UserRegistration(auto_id=False) -... if form.is_valid(): -... return 'VALID: %r' % form.cleaned_data -... t = Template('
        \n
        %s%s element.') + row_html = '
        nameParent object
        %s element.') + # make sure that hidden fields are in the correct place + hiddenfields_div = '
        ' + self.failIf(table_output.find(hiddenfields_div) == -1, + 'Failed to find hidden fields in: %s' % table_output) + # make sure that list editable fields are rendered in divs correctly + editable_name_field = '' + self.failIf('
        %s
        Comment:
        • This field is required.
        • This field is required.
        • This field is required.
        • This field is required.
        • This field is required.
        • This field is required.
        Name:
        Language:
          -
        • -
        • -
          -
        • -
        • -
        <em>Special</em> Field:
        • Something's wrong with 'Nothing to escape'
        Special Field:
        • 'Nothing to escape' is a safe string
        <em>Special</em> Field:
        • Something's wrong with 'Should escape < & > and <script>alert('xss')</script>'
        Special Field:
        • 'Do not escape' is a safe string
        Username:
        • This field is required.
        Password1:
        • This field is required.
        Password2:
        • This field is required.
        • Please make sure your passwords match.
        Username:
        Password1:
        Password2:
        First name:
        Last name:
        Birthday:
        Field1:
        Field2:
        Field3:
        Field4:
        Default field 1:
        Default field 2:
        Field1:
        Field2:
        Default field 1:
        Default field 2:
        Field3:
        Field4:
        First name:
        Last name:
        Birthday:
        • (Hidden field hidden_text) This field is required.
        First name:
        Last name:
        Birthday:
        Field1:
        Field2:
        Field3:
        Field4:
        Field5:
        Field6:
        Field7:
        Field8:
        Field9:
        Field10:
        Field11:
        Field12:
        Field13:
        Field14:
        Username:
        e.g., user@example.com
        Password:
        Choose wisely.
        File1:
        File1:
        • This field is required.
        File1:
        • The submitted file is empty.
        File1:
        • No file was submitted. Check the encoding type on the form.
        File1:
        File1:
        \n{{ form }}\n
        \n\n') -... return t.render(Context({'form': form})) - -Case 1: GET (an empty form, with no errors). ->>> print my_function('GET', {}) -
        - - - - -
        Username:
        Password1:
        Password2:
        - -
        - -Case 2: POST with erroneous data (a redisplayed form, with errors). ->>> print my_function('POST', {'username': 'this-is-a-long-username', 'password1': 'foo', 'password2': 'bar'}) -
        - - - - - -
        • Please make sure your passwords match.
        Username:
        • Ensure this value has at most 10 characters (it has 23).
        Password1:
        Password2:
        - -
        - -Case 3: POST with valid data (the success message). ->>> print my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}) -VALID: {'username': u'adrian', 'password1': u'secret', 'password2': u'secret'} - -# Some ideas for using templates with forms ################################### - ->>> class UserRegistration(Form): -... username = CharField(max_length=10, help_text="Good luck picking a username that doesn't already exist.") -... password1 = CharField(widget=PasswordInput) -... password2 = CharField(widget=PasswordInput) -... def clean(self): -... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: -... raise ValidationError(u'Please make sure your passwords match.') -... return self.cleaned_data - -You have full flexibility in displaying form fields in a template. Just pass a -Form instance to the template, and use "dot" access to refer to individual -fields. Note, however, that this flexibility comes with the responsibility of -displaying all the errors, including any that might not be associated with a -particular field. ->>> t = Template('''
        -... {{ form.username.errors.as_ul }}

        -... {{ form.password1.errors.as_ul }}

        -... {{ form.password2.errors.as_ul }}

        -... -...
        ''') ->>> print t.render(Context({'form': UserRegistration(auto_id=False)})) -
        -

        -

        -

        - -
        ->>> print t.render(Context({'form': UserRegistration({'username': 'django'}, auto_id=False)})) -
        -

        -
        • This field is required.

        -
        • This field is required.

        - -
        - -Use form.[field].label to output a field's label. You can specify the label for -a field by using the 'label' argument to a Field class. If you don't specify -'label', Django will use the field name with underscores converted to spaces, -and the initial letter capitalized. ->>> t = Template('''
        -...

        -...

        -...

        -... -...
        ''') ->>> print t.render(Context({'form': UserRegistration(auto_id=False)})) -
        -

        -

        -

        - -
        - -User form.[field].label_tag to output a field's label with a
        • This field is required.
        • This field is required.
        Choice:
        Votes:
        • (Hidden field data) This field is required.
        <em>Special</em> Field:
        • Something's wrong with 'Should escape < & > and <script>alert('xss')</script>'
        Special Field:
        • 'Do not escape' is a safe string
        Username:
        • This field is required.
        Password1:
        • This field is required.
        Password2:
        • This field is required.
        • Please make sure your passwords match.
        Username:
        Password1:
        Password2:
        First name:
        Last name:
        Birthday:
        Field1:
        Field2:
        Field3:
        Field4:
        Default field 1:
        Default field 2:
        Field1:
        Field2:
        Default field 1:
        Default field 2:
        Field3:
        Field4:
        First name:
        Last name:
        Birthday:
        • (Hidden field hidden_text) This field is required.
        First name:
        Last name:
        Birthday:
        Field1:
        Field2:
        Field3:
        Field4:
        Field5:
        Field6:
        Field7:
        Field8:
        Field9:
        Field10:
        Field11:
        Field12:
        Field13:
        Field14:
        Username:
        e.g., user@example.com
        Password:
        Choose wisely.
        File1:
        File1:
        • This field is required.
        File1:
        • The submitted file is empty.
        File1:
        • No file was submitted. Check the encoding type on the form.
        File1:
        File1:
        \n{{ form }}\n
        \n\n') + return t.render(Context({'form': form})) + + # Case 1: GET (an empty form, with no errors).) + self.assertEqual(my_function('GET', {}), """
        + + + + +
        Username:
        Password1:
        Password2:
        + +
        """) + # Case 2: POST with erroneous data (a redisplayed form, with errors).) + self.assertEqual(my_function('POST', {'username': 'this-is-a-long-username', 'password1': 'foo', 'password2': 'bar'}), """
        + + + + + +
        • Please make sure your passwords match.
        Username:
        • Ensure this value has at most 10 characters (it has 23).
        Password1:
        Password2:
        + +
        """) + # Case 3: POST with valid data (the success message).) + self.assertEqual(my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}), "VALID: {'username': u'adrian', 'password1': u'secret', 'password2': u'secret'}") + + def test_templates_with_forms(self): + class UserRegistration(Form): + username = CharField(max_length=10, help_text="Good luck picking a username that doesn't already exist.") + password1 = CharField(widget=PasswordInput) + password2 = CharField(widget=PasswordInput) + + def clean(self): + if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: + raise ValidationError(u'Please make sure your passwords match.') + + return self.cleaned_data + + # You have full flexibility in displaying form fields in a template. Just pass a + # Form instance to the template, and use "dot" access to refer to individual + # fields. Note, however, that this flexibility comes with the responsibility of + # displaying all the errors, including any that might not be associated with a + # particular field. + t = Template('''
        +{{ form.username.errors.as_ul }}

        +{{ form.password1.errors.as_ul }}

        +{{ form.password2.errors.as_ul }}

        + +
        ''') + self.assertEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """
        +

        +

        +

        + +
        """) + self.assertEqual(t.render(Context({'form': UserRegistration({'username': 'django'}, auto_id=False)})), """
        +

        +
        • This field is required.

        +
        • This field is required.

        + +
        """) + + # Use form.[field].label to output a field's label. You can specify the label for + # a field by using the 'label' argument to a Field class. If you don't specify + # 'label', Django will use the field name with underscores converted to spaces, + # and the initial letter capitalized. + t = Template('''
        +

        +

        +

        + +
        ''') + self.assertEqual(t.render(Context({'form': UserRegistration(auto_id=False)})), """
        +

        +

        +

        + +
        """) + + # User form.[field].label_tag to output a field's label with a