summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Bronn <jbronn@gmail.com>2007-12-15 01:38:43 +0000
committerJustin Bronn <jbronn@gmail.com>2007-12-15 01:38:43 +0000
commit34560a01daee3c42a7c5ec462f38a485cccf4df7 (patch)
treec332688943fb10b1503654815a06093c1d52e493
parent5799c2e048ff829300af88ae839de20e1763ee1d (diff)
downloaddjango-34560a01daee3c42a7c5ec462f38a485cccf4df7.tar.gz
gis: Merged revisions 6672,6686-6688,6690,6693,6707-6708,6726,6730,6753,6755-6762,6764,6776-6777,6779,6782-6919 via svnmerge from trunk; reverted oracle backend `base.py` due to ikelly's patch in r6905.
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6920 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--AUTHORS8
-rw-r--r--django/conf/__init__.py6
-rw-r--r--django/conf/global_settings.py6
-rw-r--r--django/conf/locale/ca/LC_MESSAGES/django.mobin61655 -> 62260 bytes
-rw-r--r--django/conf/locale/ca/LC_MESSAGES/django.po392
-rw-r--r--django/conf/locale/ca/LC_MESSAGES/djangojs.mobin1654 -> 1675 bytes
-rw-r--r--django/conf/locale/ca/LC_MESSAGES/djangojs.po14
-rw-r--r--django/contrib/admin/templatetags/admin_list.py2
-rw-r--r--django/contrib/auth/models.py2
-rw-r--r--django/contrib/auth/tests.py12
-rw-r--r--django/contrib/contenttypes/generic.py42
-rw-r--r--django/contrib/localflavor/mx/__init__.py0
-rw-r--r--django/contrib/localflavor/mx/forms.py14
-rw-r--r--django/contrib/localflavor/mx/mx_states.py45
-rw-r--r--django/contrib/localflavor/uk/forms.py18
-rw-r--r--django/contrib/localflavor/uk/uk_regions.py97
-rw-r--r--django/contrib/localflavor/za/__init__.py0
-rw-r--r--django/contrib/localflavor/za/forms.py57
-rw-r--r--django/contrib/localflavor/za/za_provinces.py13
-rw-r--r--django/contrib/markup/templatetags/markup.py31
-rw-r--r--django/contrib/markup/tests.py9
-rw-r--r--django/contrib/sessions/backends/base.py8
-rw-r--r--django/contrib/sessions/backends/file.py13
-rw-r--r--django/contrib/sessions/models.py34
-rw-r--r--django/contrib/sessions/tests.py13
-rw-r--r--django/core/cache/__init__.py11
-rw-r--r--django/core/cache/backends/filebased.py131
-rw-r--r--django/core/cache/backends/locmem.py105
-rw-r--r--django/core/cache/backends/simple.py73
-rw-r--r--django/core/exceptions.py4
-rw-r--r--django/core/handlers/base.py5
-rw-r--r--django/core/handlers/modpython.py9
-rw-r--r--django/core/handlers/wsgi.py4
-rw-r--r--django/core/management/__init__.py82
-rw-r--r--django/core/management/commands/loaddata.py6
-rw-r--r--django/core/management/commands/syncdb.py5
-rw-r--r--django/core/management/validation.py16
-rw-r--r--django/core/serializers/pyyaml.py16
-rw-r--r--django/db/__init__.py4
-rw-r--r--django/db/backends/oracle/base.py36
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py2
-rw-r--r--django/db/models/base.py4
-rw-r--r--django/db/models/fields/__init__.py2
-rw-r--r--django/db/models/options.py6
-rw-r--r--django/db/models/query.py3
-rw-r--r--django/http/__init__.py2
-rw-r--r--django/http/utils.py5
-rw-r--r--django/middleware/common.py58
-rw-r--r--django/newforms/fields.py26
-rw-r--r--django/newforms/models.py168
-rw-r--r--django/shortcuts/__init__.py2
-rw-r--r--django/template/context.py7
-rw-r--r--django/template/defaultfilters.py28
-rw-r--r--django/test/_doctest.py11
-rw-r--r--django/utils/datastructures.py6
-rw-r--r--django/utils/html.py24
-rw-r--r--django/utils/maxlength.py18
-rw-r--r--django/utils/safestring.py21
-rw-r--r--django/views/debug.py93
-rw-r--r--django/views/generic/date_based.py2
-rw-r--r--django/views/generic/list_detail.py2
-rw-r--r--docs/add_ons.txt41
-rw-r--r--docs/authentication.txt55
-rw-r--r--docs/cache.txt4
-rw-r--r--docs/contributing.txt19
-rw-r--r--docs/custom_model_fields.txt273
-rw-r--r--docs/databases.txt5
-rw-r--r--docs/email.txt8
-rw-r--r--docs/form_for_model.txt418
-rw-r--r--docs/generic_views.txt4
-rw-r--r--docs/install.txt16
-rw-r--r--docs/localflavor.txt654
-rw-r--r--docs/middleware.txt19
-rw-r--r--docs/model-api.txt5
-rw-r--r--docs/modelforms.txt313
-rw-r--r--docs/newforms.txt447
-rw-r--r--docs/request_response.txt18
-rw-r--r--docs/serialization.txt9
-rw-r--r--docs/sessions.txt2
-rw-r--r--docs/settings.txt47
-rw-r--r--docs/shortcuts.txt4
-rw-r--r--docs/syndication_feeds.txt13
-rw-r--r--docs/templates.txt273
-rw-r--r--docs/templates_python.txt269
-rw-r--r--docs/tutorial02.txt50
-rw-r--r--tests/modeltests/empty/models.py2
-rw-r--r--tests/modeltests/field_defaults/models.py5
-rw-r--r--tests/modeltests/generic_relations/models.py30
-rw-r--r--tests/modeltests/get_object_or_404/models.py2
-rw-r--r--tests/modeltests/invalid_models/models.py6
-rw-r--r--tests/modeltests/model_forms/models.py294
-rw-r--r--tests/modeltests/select_related/models.py2
-rw-r--r--tests/modeltests/serializers/models.py40
-rw-r--r--tests/modeltests/test_client/models.py2
-rw-r--r--tests/modeltests/user_commands/models.py4
-rw-r--r--tests/regressiontests/cache/tests.py60
-rw-r--r--tests/regressiontests/defaultfilters/tests.py12
-rw-r--r--tests/regressiontests/forms/localflavor/za.py40
-rw-r--r--tests/regressiontests/forms/tests.py2
-rw-r--r--tests/regressiontests/i18n/tests.py15
-rw-r--r--tests/regressiontests/maxlength/tests.py6
-rw-r--r--tests/regressiontests/middleware/__init__.py0
-rw-r--r--tests/regressiontests/middleware/tests.py93
-rw-r--r--tests/regressiontests/middleware/urls.py7
-rw-r--r--tests/regressiontests/model_regress/models.py10
-rw-r--r--tests/regressiontests/string_lookup/models.py8
-rw-r--r--tests/regressiontests/templates/context.py18
-rw-r--r--tests/regressiontests/templates/tests.py2
-rw-r--r--tests/regressiontests/views/views.py2
-rwxr-xr-xtests/runtests.py3
-rw-r--r--tests/urls.py3
111 files changed, 3823 insertions, 1644 deletions
diff --git a/AUTHORS b/AUTHORS
index 0768645f1c..ffb9e87690 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -89,6 +89,7 @@ answer newbie questions, and generally made Django that much better:
Paul Collier <paul@paul-collier.com>
Pete Crosier <pete.crosier@gmail.com>
Matt Croydon <http://www.postneo.com/>
+ Leah Culver <leah@pownce.com>
flavio.curella@gmail.com
Jure Cuhalev <gandalf@owca.info>
John D'Agostino <john.dagostino@gmail.com>
@@ -132,6 +133,7 @@ answer newbie questions, and generally made Django that much better:
Jorge Gajon <gajon@gajon.org>
gandalf@owca.info
Marc Garcia <marc.garcia@accopensys.com>
+ Andy Gayton <andy-django@thecablelounge.com>
Baishampayan Ghose
Dimitris Glezos <dimitris@glezos.com>
glin@seznam.cz
@@ -171,6 +173,7 @@ answer newbie questions, and generally made Django that much better:
junzhang.jn@gmail.com
Antti Kaihola <http://akaihola.blogspot.com/>
Nagy Károly <charlie@rendszergazda.com>
+ Erik Karulf <erik@karulf.com>
Ben Dean Kawamura <ben.dean.kawamura@gmail.com>
Ian G. Kelly <ian.g.kelly@gmail.com>
Thomas Kerpe <thomas@kerpe.net>
@@ -190,6 +193,7 @@ answer newbie questions, and generally made Django that much better:
Joseph Kocherhans
konrad@gwu.edu
knox <christobzr@gmail.com>
+ David Krauth
kurtiss@meetro.com
lakin.wecker@gmail.com
Nick Lane <nick.lane.au@gmail.com>
@@ -204,6 +208,7 @@ answer newbie questions, and generally made Django that much better:
Waylan Limberg <waylan@gmail.com>
limodou
Philip Lindborg <philip.lindborg@gmail.com>
+ Trey Long <trey@ktrl.com>
msaelices <msaelices@gmail.com>
Matt McClanahan <http://mmcc.cx/>
Martin Maney <http://www.chipy.org/Martin_Maney>
@@ -251,6 +256,7 @@ answer newbie questions, and generally made Django that much better:
Gustavo Picon
Luke Plant <http://lukeplant.me.uk/>
plisk
+ Mihai Preda <mihai_preda@yahoo.com>
Daniel Poelzleithner <http://poelzi.org/>
polpak@yahoo.com
Jyrki Pulliainen <jyrki.pulliainen@gmail.com>
@@ -262,6 +268,7 @@ answer newbie questions, and generally made Django that much better:
Massimiliano Ravelli <massimiliano.ravelli@gmail.com>
Brian Ray <http://brianray.chipy.org/>
remco@diji.biz
+ David Reynolds <david@reynoldsfamily.org.uk>
rhettg@gmail.com
ricardojbarrios@gmail.com
Matt Riggott
@@ -281,6 +288,7 @@ answer newbie questions, and generally made Django that much better:
Pete Shinners <pete@shinners.org>
jason.sidabras@gmail.com
Jozko Skrablin <jozko.skrablin@gmail.com>
+ Ben Slavin <benjamin.slavin@gmail.com>
SmileyChris <smileychris@gmail.com>
smurf@smurf.noris.de
Vsevolod Solovyov
diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index 0177490df9..c24e87e6ed 100644
--- a/django/conf/__init__.py
+++ b/django/conf/__init__.py
@@ -52,7 +52,7 @@ class LazySettings(object):
if not settings_module: # If it's set but is an empty string.
raise KeyError
except KeyError:
- raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE
+ raise ImportError, "Environment variable %s is undefined so settings cannot be imported." % ENVIRONMENT_VARIABLE # NOTE: This is arguably an EnvironmentError, but that causes problems with Python's interactive help
self._target = Settings(settings_module)
@@ -63,7 +63,7 @@ class LazySettings(object):
argument must support attribute access (__getattr__)).
"""
if self._target != None:
- raise EnvironmentError, 'Settings already configured.'
+ raise RuntimeError, 'Settings already configured.'
holder = UserSettingsHolder(default_settings)
for name, value in options.items():
setattr(holder, name, value)
@@ -82,7 +82,7 @@ class Settings(object):
try:
mod = __import__(self.SETTINGS_MODULE, {}, {}, [''])
except ImportError, e:
- raise EnvironmentError, "Could not import settings '%s' (Is it on sys.path? Does it have syntax errors?): %s" % (self.SETTINGS_MODULE, e)
+ raise ImportError, "Could not import settings '%s' (Is it on sys.path? Does it have syntax errors?): %s" % (self.SETTINGS_MODULE, e)
# Settings that should be converted into tuples if they're mistakenly entered
# as strings.
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 2853d6c618..187eb0645e 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -253,6 +253,10 @@ TRANSACTIONS_MANAGED = False
from django import get_version
URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_version()
+# The tablespaces to use for each model when not specified otherwise.
+DEFAULT_TABLESPACE = ''
+DEFAULT_INDEX_TABLESPACE = ''
+
##############
# MIDDLEWARE #
##############
@@ -289,7 +293,7 @@ SESSION_FILE_PATH = '/tmp/' # Directory to store ses
# The cache backend to use. See the docstring in django.core.cache for the
# possible values.
-CACHE_BACKEND = 'simple://'
+CACHE_BACKEND = 'locmem://'
CACHE_MIDDLEWARE_KEY_PREFIX = ''
CACHE_MIDDLEWARE_SECONDS = 600
diff --git a/django/conf/locale/ca/LC_MESSAGES/django.mo b/django/conf/locale/ca/LC_MESSAGES/django.mo
index fdba04abd2..a39a4551bb 100644
--- a/django/conf/locale/ca/LC_MESSAGES/django.mo
+++ b/django/conf/locale/ca/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/ca/LC_MESSAGES/django.po b/django/conf/locale/ca/LC_MESSAGES/django.po
index 9f561acc5b..2929ea114e 100644
--- a/django/conf/locale/ca/LC_MESSAGES/django.po
+++ b/django/conf/locale/ca/LC_MESSAGES/django.po
@@ -5,9 +5,9 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-11-05 17:33+0100\n"
-"PO-Revision-Date: 2007-11-05 17:34+0100\n"
-"Last-Translator: Marc Garcia <marc.garcia@accopensys.com>\n"
+"POT-Creation-Date: 2007-12-02 22:26+0100\n"
+"PO-Revision-Date: 2007-12-02 22:32+0100\n"
+"Last-Translator: Marc Fargas <telenieko@telenieko.com>\n"
"Language-Team: <es@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -194,7 +194,7 @@ msgstr "Xinés simplificat"
msgid "Traditional Chinese"
msgstr "Xinés tradicional"
-#: contrib/admin/filterspecs.py:42
+#: contrib/admin/filterspecs.py:44
#, python-format
msgid ""
"<h3>By %s:</h3>\n"
@@ -203,71 +203,71 @@ msgstr ""
"<h3>Per %s:</h3>\n"
"<ul>\n"
-#: contrib/admin/filterspecs.py:72 contrib/admin/filterspecs.py:90
-#: contrib/admin/filterspecs.py:145 contrib/admin/filterspecs.py:171
+#: contrib/admin/filterspecs.py:74 contrib/admin/filterspecs.py:92
+#: contrib/admin/filterspecs.py:147 contrib/admin/filterspecs.py:173
msgid "All"
msgstr "Tots"
-#: contrib/admin/filterspecs.py:111
+#: contrib/admin/filterspecs.py:113
msgid "Any date"
msgstr "Qualsevol data"
-#: contrib/admin/filterspecs.py:112
+#: contrib/admin/filterspecs.py:114
msgid "Today"
msgstr "Avui"
-#: contrib/admin/filterspecs.py:115
+#: contrib/admin/filterspecs.py:117
msgid "Past 7 days"
msgstr "Últims 7 dies"
-#: contrib/admin/filterspecs.py:117
+#: contrib/admin/filterspecs.py:119
msgid "This month"
msgstr "Aquest mes"
-#: contrib/admin/filterspecs.py:119
+#: contrib/admin/filterspecs.py:121
msgid "This year"
msgstr "Aquest any"
-#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221
-#: oldforms/__init__.py:591
+#: contrib/admin/filterspecs.py:147 newforms/widgets.py:231
+#: oldforms/__init__.py:592
msgid "Yes"
msgstr "Si"
-#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221
-#: oldforms/__init__.py:591
+#: contrib/admin/filterspecs.py:147 newforms/widgets.py:231
+#: oldforms/__init__.py:592
msgid "No"
msgstr "No"
-#: contrib/admin/filterspecs.py:152 newforms/widgets.py:221
-#: oldforms/__init__.py:591
+#: contrib/admin/filterspecs.py:154 newforms/widgets.py:231
+#: oldforms/__init__.py:592
msgid "Unknown"
msgstr "Desconegut"
-#: contrib/admin/models.py:17
+#: contrib/admin/models.py:18
msgid "action time"
msgstr "moment de l'acció"
-#: contrib/admin/models.py:20
+#: contrib/admin/models.py:21
msgid "object id"
msgstr "id del objecte"
-#: contrib/admin/models.py:21
+#: contrib/admin/models.py:22
msgid "object repr"
msgstr "'repr' de l'objecte"
-#: contrib/admin/models.py:22
+#: contrib/admin/models.py:23
msgid "action flag"
msgstr "marca de l'acció"
-#: contrib/admin/models.py:23
+#: contrib/admin/models.py:24
msgid "change message"
msgstr "missatge del canvi"
-#: contrib/admin/models.py:26
+#: contrib/admin/models.py:27
msgid "log entry"
msgstr "entrada del registre"
-#: contrib/admin/models.py:27
+#: contrib/admin/models.py:28
msgid "log entries"
msgstr "entrades del registre"
@@ -469,7 +469,7 @@ msgid "Password:"
msgstr "Contrasenya:"
#: contrib/admin/templates/admin/login.html:25
-#: contrib/admin/views/decorators.py:24
+#: contrib/admin/views/decorators.py:25
msgid "Log in"
msgstr "Iniciar sessió"
@@ -769,17 +769,17 @@ msgstr "Actualment:"
msgid "Change:"
msgstr "Modificar:"
-#: contrib/admin/templatetags/admin_list.py:254
+#: contrib/admin/templatetags/admin_list.py:257
msgid "All dates"
msgstr "Totes les dates"
-#: contrib/admin/views/auth.py:20 contrib/admin/views/main.py:264
+#: contrib/admin/views/auth.py:20 contrib/admin/views/main.py:267
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr "El/la %(name)s \"%(obj)s\".ha estat agregat/da amb èxit."
-#: contrib/admin/views/auth.py:25 contrib/admin/views/main.py:268
-#: contrib/admin/views/main.py:354
+#: contrib/admin/views/auth.py:25 contrib/admin/views/main.py:271
+#: contrib/admin/views/main.py:356
msgid "You may edit it again below."
msgstr "Pot editar-lo de nou abaix."
@@ -796,7 +796,7 @@ msgstr "Canvi de clau exitós"
msgid "Change password: %s"
msgstr "Canviar clau: %s"
-#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:60
+#: contrib/admin/views/decorators.py:11 contrib/auth/forms.py:60
msgid ""
"Please enter a correct username and password. Note that both fields are case-"
"sensitive."
@@ -804,7 +804,7 @@ msgstr ""
"Si us plau, introdueixi un nom d'usuari i contrasenya vàlids. Tingui en "
"compte que tots dos camps son sensibles a majúscules i minúscules."
-#: contrib/admin/views/decorators.py:62
+#: contrib/admin/views/decorators.py:63
msgid ""
"Please log in again, because your session has expired. Don't worry: Your "
"submission has been saved."
@@ -812,7 +812,7 @@ msgstr ""
"Si us plau, identifiquis de nou doncs la seva sessió ha expirat. No es "
"preocupi, el seu enviament està emmagatzemat."
-#: contrib/admin/views/decorators.py:69
+#: contrib/admin/views/decorators.py:70
msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again."
@@ -821,247 +821,247 @@ msgstr ""
"'cookies' (galetes). Si us plau, habiliti les 'cookies', recarregui aquesta "
"pàgina i provi-ho de nou. "
-#: contrib/admin/views/decorators.py:83
+#: contrib/admin/views/decorators.py:84
msgid "Usernames cannot contain the '@' character."
msgstr "Els noms d'usuari no poden contenir el caracter '@'."
-#: contrib/admin/views/decorators.py:85
+#: contrib/admin/views/decorators.py:86
#, python-format
msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr ""
"La seva adreça de correu no és el seu nom d'usuari. Provi '%s' en tot cas."
-#: contrib/admin/views/doc.py:47 contrib/admin/views/doc.py:49
-#: contrib/admin/views/doc.py:51
+#: contrib/admin/views/doc.py:48 contrib/admin/views/doc.py:50
+#: contrib/admin/views/doc.py:52
msgid "tag:"
msgstr "etiqueta:"
-#: contrib/admin/views/doc.py:78 contrib/admin/views/doc.py:80
-#: contrib/admin/views/doc.py:82
+#: contrib/admin/views/doc.py:79 contrib/admin/views/doc.py:81
+#: contrib/admin/views/doc.py:83
msgid "filter:"
msgstr "filtre:"
-#: contrib/admin/views/doc.py:136 contrib/admin/views/doc.py:138
-#: contrib/admin/views/doc.py:140
+#: contrib/admin/views/doc.py:137 contrib/admin/views/doc.py:139
+#: contrib/admin/views/doc.py:141
msgid "view:"
msgstr "vista:"
-#: contrib/admin/views/doc.py:165
+#: contrib/admin/views/doc.py:166
#, python-format
msgid "App %r not found"
msgstr "La aplicació %r no s'ha pogut trobar"
-#: contrib/admin/views/doc.py:172
+#: contrib/admin/views/doc.py:173
#, python-format
msgid "Model %(name)r not found in app %(label)r"
msgstr "El model %(name)r no s'ha trobat en la aplicació %(label)r"
-#: contrib/admin/views/doc.py:184
+#: contrib/admin/views/doc.py:185
#, python-format
msgid "the related `%(label)s.%(type)s` object"
msgstr "el objecte relacionat `%(label)s.%(type)s`"
-#: contrib/admin/views/doc.py:184 contrib/admin/views/doc.py:206
-#: contrib/admin/views/doc.py:220 contrib/admin/views/doc.py:225
+#: contrib/admin/views/doc.py:185 contrib/admin/views/doc.py:207
+#: contrib/admin/views/doc.py:221 contrib/admin/views/doc.py:226
msgid "model:"
msgstr "model:"
-#: contrib/admin/views/doc.py:215
+#: contrib/admin/views/doc.py:216
#, python-format
msgid "related `%(label)s.%(name)s` objects"
msgstr "objectes relacionats `%(label)s.%(name)s`"
-#: contrib/admin/views/doc.py:220
+#: contrib/admin/views/doc.py:221
#, python-format
msgid "all %s"
msgstr "tots %s"
-#: contrib/admin/views/doc.py:225
+#: contrib/admin/views/doc.py:226
#, python-format
msgid "number of %s"
msgstr "nombre de %s"
-#: contrib/admin/views/doc.py:230
+#: contrib/admin/views/doc.py:231
#, python-format
msgid "Fields on %s objects"
msgstr "Camps en objectes %s"
-#: contrib/admin/views/doc.py:292 contrib/admin/views/doc.py:303
-#: contrib/admin/views/doc.py:305 contrib/admin/views/doc.py:311
-#: contrib/admin/views/doc.py:312 contrib/admin/views/doc.py:314
+#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:304
+#: contrib/admin/views/doc.py:306 contrib/admin/views/doc.py:312
+#: contrib/admin/views/doc.py:313 contrib/admin/views/doc.py:315
msgid "Integer"
msgstr "Enter"
-#: contrib/admin/views/doc.py:293
+#: contrib/admin/views/doc.py:294
msgid "Boolean (Either True or False)"
msgstr "Booleà (Verdader o Fals)"
-#: contrib/admin/views/doc.py:294 contrib/admin/views/doc.py:313
+#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:314
#, python-format
msgid "String (up to %(max_length)s)"
msgstr "Cadena (de fins a %(max_length)s)"
-#: contrib/admin/views/doc.py:295
+#: contrib/admin/views/doc.py:296
msgid "Comma-separated integers"
msgstr "Enters separats per comes"
-#: contrib/admin/views/doc.py:296
+#: contrib/admin/views/doc.py:297
msgid "Date (without time)"
msgstr "Data (sense hora)"
-#: contrib/admin/views/doc.py:297
+#: contrib/admin/views/doc.py:298
msgid "Date (with time)"
msgstr "Data (amb hora)"
-#: contrib/admin/views/doc.py:298
+#: contrib/admin/views/doc.py:299
msgid "Decimal number"
msgstr "Número decimal"
-#: contrib/admin/views/doc.py:299
+#: contrib/admin/views/doc.py:300
msgid "E-mail address"
msgstr "Adreça de correu electrònic"
-#: contrib/admin/views/doc.py:300 contrib/admin/views/doc.py:301
-#: contrib/admin/views/doc.py:304
+#: contrib/admin/views/doc.py:301 contrib/admin/views/doc.py:302
+#: contrib/admin/views/doc.py:305
msgid "File path"
msgstr "Ruta del fitxer"
-#: contrib/admin/views/doc.py:302
+#: contrib/admin/views/doc.py:303
msgid "Floating point number"
msgstr "Número amb punt de coma flotant"
-#: contrib/admin/views/doc.py:306 contrib/comments/models.py:85
+#: contrib/admin/views/doc.py:307 contrib/comments/models.py:85
msgid "IP address"
msgstr "Adreça IP"
-#: contrib/admin/views/doc.py:308
+#: contrib/admin/views/doc.py:309
msgid "Boolean (Either True, False or None)"
msgstr "Booleà (Verdader, Fals o 'None' (cap))"
-#: contrib/admin/views/doc.py:309
+#: contrib/admin/views/doc.py:310
msgid "Relation to parent model"
msgstr "Relació amb el model pare"
-#: contrib/admin/views/doc.py:310
+#: contrib/admin/views/doc.py:311
msgid "Phone number"
msgstr "Número de telèfon"
-#: contrib/admin/views/doc.py:315
+#: contrib/admin/views/doc.py:316
msgid "Text"
msgstr "Texte"
-#: contrib/admin/views/doc.py:316
+#: contrib/admin/views/doc.py:317
msgid "Time"
msgstr "Hora"
-#: contrib/admin/views/doc.py:317 contrib/flatpages/models.py:7
+#: contrib/admin/views/doc.py:318 contrib/flatpages/models.py:7
msgid "URL"
msgstr "URL"
-#: contrib/admin/views/doc.py:318
+#: contrib/admin/views/doc.py:319
msgid "U.S. state (two uppercase letters)"
msgstr "Estat dels E.U.A. (dos lletres majúscules)"
-#: contrib/admin/views/doc.py:319
+#: contrib/admin/views/doc.py:320
msgid "XML text"
msgstr "Texte XML"
-#: contrib/admin/views/doc.py:345
+#: contrib/admin/views/doc.py:346
#, python-format
msgid "%s does not appear to be a urlpattern object"
msgstr "%s no sembla ser un objecte 'urlpattern'"
-#: contrib/admin/views/main.py:230
+#: contrib/admin/views/main.py:233
msgid "Site administration"
msgstr "Lloc administratiu"
-#: contrib/admin/views/main.py:278 contrib/admin/views/main.py:363
+#: contrib/admin/views/main.py:280 contrib/admin/views/main.py:365
#, python-format
msgid "You may add another %s below."
msgstr "Pot afegir un altre %s a baix."
-#: contrib/admin/views/main.py:296
+#: contrib/admin/views/main.py:298
#, python-format
msgid "Add %s"
msgstr "Afegir %s"
-#: contrib/admin/views/main.py:342
+#: contrib/admin/views/main.py:344
#, python-format
msgid "Added %s."
msgstr "Agregat %s."
-#: contrib/admin/views/main.py:342 contrib/admin/views/main.py:344
-#: contrib/admin/views/main.py:346 core/validators.py:283
+#: contrib/admin/views/main.py:344 contrib/admin/views/main.py:346
+#: contrib/admin/views/main.py:348 core/validators.py:283
#: db/models/manipulators.py:309
msgid "and"
msgstr "i"
-#: contrib/admin/views/main.py:344
+#: contrib/admin/views/main.py:346
#, python-format
msgid "Changed %s."
msgstr "Modificat %s."
-#: contrib/admin/views/main.py:346
+#: contrib/admin/views/main.py:348
#, python-format
msgid "Deleted %s."
msgstr "Eliminat %s."
-#: contrib/admin/views/main.py:349
+#: contrib/admin/views/main.py:351
msgid "No fields changed."
msgstr "Cap camp canviat."
-#: contrib/admin/views/main.py:352
+#: contrib/admin/views/main.py:354
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "S'ha modificat amb èxist el/la %(name)s \"%(obj)s."
-#: contrib/admin/views/main.py:360
+#: contrib/admin/views/main.py:362
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr ""
"S'ha agregat amb èxit el/la %(name)s \"%(obj)s\". Pot editar-lo de nou abaix."
-#: contrib/admin/views/main.py:398
+#: contrib/admin/views/main.py:400
#, python-format
msgid "Change %s"
msgstr "Modificar %s"
-#: contrib/admin/views/main.py:483
+#: contrib/admin/views/main.py:487
#, python-format
msgid "One or more %(fieldname)s in %(name)s: %(obj)s"
msgstr "Un o més %(fieldname)s en %(name)s: %(obj)s"
-#: contrib/admin/views/main.py:488
+#: contrib/admin/views/main.py:492
#, python-format
msgid "One or more %(fieldname)s in %(name)s:"
msgstr "Un o més %(fieldname)s en %(name)s:"
-#: contrib/admin/views/main.py:520
+#: contrib/admin/views/main.py:524
#, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "El/la %(name)s \"%(obj)s\".ha estat eliminat amb èxit."
-#: contrib/admin/views/main.py:523
+#: contrib/admin/views/main.py:527
msgid "Are you sure?"
msgstr "Està segur?"
-#: contrib/admin/views/main.py:545
+#: contrib/admin/views/main.py:549
#, python-format
msgid "Change history: %s"
msgstr "Modificar històric: %s"
-#: contrib/admin/views/main.py:579
+#: contrib/admin/views/main.py:583
#, python-format
msgid "Select %s"
msgstr "Seleccioni %s"
-#: contrib/admin/views/main.py:579
+#: contrib/admin/views/main.py:583
#, python-format
msgid "Select %s to change"
msgstr "Seleccioni %s per modificar"
-#: contrib/admin/views/main.py:780
+#: contrib/admin/views/main.py:784
msgid "Database error"
msgstr "Error de/en la base de dades"
@@ -1622,72 +1622,72 @@ msgstr "n"
msgid "rd"
msgstr "r"
-#: contrib/humanize/templatetags/humanize.py:50
+#: contrib/humanize/templatetags/humanize.py:52
#, python-format
msgid "%(value).1f million"
msgid_plural "%(value).1f million"
msgstr[0] "%(value).1f milió"
msgstr[1] "%(value).1f milions"
-#: contrib/humanize/templatetags/humanize.py:53
+#: contrib/humanize/templatetags/humanize.py:55
#, python-format
msgid "%(value).1f billion"
msgid_plural "%(value).1f billion"
msgstr[0] "%(value).1f bilió"
msgstr[1] "%(value).1f bilions"
-#: contrib/humanize/templatetags/humanize.py:56
+#: contrib/humanize/templatetags/humanize.py:58
#, python-format
msgid "%(value).1f trillion"
msgid_plural "%(value).1f trillion"
msgstr[0] "%(value).1f trilió"
msgstr[1] "%(value).1f trilions"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "one"
msgstr "un"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "two"
msgstr "dos"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "three"
msgstr "tres"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "four"
msgstr "cuatre"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "five"
msgstr "cinc"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "six"
msgstr "sis"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "seven"
msgstr "set"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "eight"
msgstr "vuit"
-#: contrib/humanize/templatetags/humanize.py:71
+#: contrib/humanize/templatetags/humanize.py:74
msgid "nine"
msgstr "nou"
-#: contrib/humanize/templatetags/humanize.py:90
+#: contrib/humanize/templatetags/humanize.py:94
msgid "today"
msgstr "avui"
-#: contrib/humanize/templatetags/humanize.py:92
+#: contrib/humanize/templatetags/humanize.py:96
msgid "tomorrow"
msgstr "demà"
-#: contrib/humanize/templatetags/humanize.py:94
+#: contrib/humanize/templatetags/humanize.py:98
msgid "yesterday"
msgstr "ahir"
@@ -3024,6 +3024,50 @@ msgstr ""
"Introdueixi un número vàlid de la Seguretat Social dels E.U.A. en el format "
"XXX-XX-XXXX."
+#: contrib/localflavor/za/forms.py:22
+msgid "Enter a valid South African ID number"
+msgstr "Introdueixi un número d'Identitat Sud Africà valid"
+
+#: contrib/localflavor/za/forms.py:57
+msgid "Enter a valid South African postal code"
+msgstr "Introdueixi un codi postal Sud Africà vàlid."
+
+#: contrib/localflavor/za/za_provinces.py:4
+msgid "Eastern Cape"
+msgstr "Eastern Cape"
+
+#: contrib/localflavor/za/za_provinces.py:5
+msgid "Free State"
+msgstr "Free State"
+
+#: contrib/localflavor/za/za_provinces.py:6
+msgid "Gauteng"
+msgstr "Gauteng"
+
+#: contrib/localflavor/za/za_provinces.py:7
+msgid "KwaZulu-Natal"
+msgstr "KwaZulu-Natal"
+
+#: contrib/localflavor/za/za_provinces.py:8
+msgid "Limpopo"
+msgstr "Limpopo"
+
+#: contrib/localflavor/za/za_provinces.py:9
+msgid "Mpumalanga"
+msgstr "Mpumalanga"
+
+#: contrib/localflavor/za/za_provinces.py:10
+msgid "Northern Cape"
+msgstr "Northern Cape"
+
+#: contrib/localflavor/za/za_provinces.py:11
+msgid "North West"
+msgstr "North West"
+
+#: contrib/localflavor/za/za_provinces.py:12
+msgid "Western Cape"
+msgstr "Western Cape"
+
#: contrib/redirects/models.py:7
msgid "redirect from"
msgstr "redreçar des de"
@@ -3056,23 +3100,23 @@ msgstr "redreçament"
msgid "redirects"
msgstr "redreçaments"
-#: contrib/sessions/models.py:80
+#: contrib/sessions/models.py:46
msgid "session key"
msgstr "clau de la sessió"
-#: contrib/sessions/models.py:81
+#: contrib/sessions/models.py:47
msgid "session data"
msgstr "dades de la sessió"
-#: contrib/sessions/models.py:82
+#: contrib/sessions/models.py:48
msgid "expire date"
msgstr "data de caducitat"
-#: contrib/sessions/models.py:87
+#: contrib/sessions/models.py:53
msgid "session"
msgstr "sessió"
-#: contrib/sessions/models.py:88
+#: contrib/sessions/models.py:54
msgid "sessions"
msgstr "sessions"
@@ -3141,7 +3185,7 @@ msgstr "No s'admeten caracters no numèrics."
msgid "This value can't be comprised solely of digits."
msgstr "Aquest valor no pot contenir només dígits."
-#: core/validators.py:128 newforms/fields.py:157
+#: core/validators.py:128 newforms/fields.py:151
msgid "Enter a whole number."
msgstr "Introdueixi un número sencer."
@@ -3170,17 +3214,17 @@ msgstr "Introdueixi una hora vàlida en el format HH:MM."
msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
msgstr "Introdueixi un data/hora vàlida en format YYYY-MM-DD HH:MM."
-#: core/validators.py:170 newforms/fields.py:408
+#: core/validators.py:170 newforms/fields.py:402
msgid "Enter a valid e-mail address."
msgstr "Introdueixi una adreça de correu vàlida."
-#: core/validators.py:182 core/validators.py:474 newforms/fields.py:438
-#: oldforms/__init__.py:686
+#: core/validators.py:182 core/validators.py:474 newforms/fields.py:432
+#: oldforms/__init__.py:687
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
"No s'ha enviat cap fitxer. Comprovi el tipus de codificació del formulari."
-#: core/validators.py:193 newforms/fields.py:462
+#: core/validators.py:193 newforms/fields.py:456
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
@@ -3431,7 +3475,7 @@ msgstr "Ja existeix %(optname)s amb auqest %(fieldname)s."
#: db/models/fields/__init__.py:161 db/models/fields/__init__.py:318
#: db/models/fields/__init__.py:735 db/models/fields/__init__.py:746
-#: newforms/fields.py:45 newforms/models.py:211 oldforms/__init__.py:373
+#: newforms/fields.py:45 oldforms/__init__.py:374
msgid "This field is required."
msgstr "Aquest camp és obligatori."
@@ -3484,150 +3528,150 @@ msgstr[1] ""
msgid "Enter a valid value."
msgstr "Introdueixi un valor vàlid."
-#: newforms/fields.py:129
+#: newforms/fields.py:123
#, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
msgstr ""
"Asseguris de que el valor té com a màxim %(max)d caràcters (en té %(length)"
"d)."
-#: newforms/fields.py:130
+#: newforms/fields.py:124
#, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
msgstr ""
"Asseguris de que el valor té com a mínim %(min)d caràcters (en té %(length)"
"d)."
-#: newforms/fields.py:158 newforms/fields.py:187 newforms/fields.py:216
+#: newforms/fields.py:152 newforms/fields.py:181 newforms/fields.py:210
#, python-format
msgid "Ensure this value is less than or equal to %s."
msgstr "Aquest valor ha de ser menor o igual a %s."
-#: newforms/fields.py:159 newforms/fields.py:188 newforms/fields.py:217
+#: newforms/fields.py:153 newforms/fields.py:182 newforms/fields.py:211
#, python-format
msgid "Ensure this value is greater than or equal to %s."
msgstr "Asseguris de que aquest valor sigui superior o igual a %s."
-#: newforms/fields.py:186 newforms/fields.py:215
+#: newforms/fields.py:180 newforms/fields.py:209
msgid "Enter a number."
msgstr "Introdueixi un número."
-#: newforms/fields.py:218
+#: newforms/fields.py:212
#, python-format
msgid "Ensure that there are no more than %s digits in total."
msgstr "Asseguris de que no hi ha més de %s dígits en total."
-#: newforms/fields.py:219
+#: newforms/fields.py:213
#, python-format
msgid "Ensure that there are no more than %s decimal places."
msgstr "Asseguris de que no hi ha més de %s decimals."
-#: newforms/fields.py:220
+#: newforms/fields.py:214
#, python-format
msgid "Ensure that there are no more than %s digits before the decimal point."
msgstr "Asseguris de que no hia ha més de %s dígits decimals."
-#: newforms/fields.py:268 newforms/fields.py:724
+#: newforms/fields.py:262 newforms/fields.py:719
msgid "Enter a valid date."
msgstr "Introdueixi una data vàlida."
-#: newforms/fields.py:301 newforms/fields.py:725
+#: newforms/fields.py:295 newforms/fields.py:720
msgid "Enter a valid time."
msgstr "Introdueixi una hora vàlida."
-#: newforms/fields.py:340
+#: newforms/fields.py:334
msgid "Enter a valid date/time."
msgstr "Introdueixi una data/hora vàlides."
-#: newforms/fields.py:439
+#: newforms/fields.py:433
msgid "No file was submitted."
msgstr "No s'ha enviat cap fitxer."
-#: newforms/fields.py:440 oldforms/__init__.py:688
+#: newforms/fields.py:434 oldforms/__init__.py:689
msgid "The submitted file is empty."
msgstr "El fitxer enviat està buit."
-#: newforms/fields.py:498
+#: newforms/fields.py:492
msgid "Enter a valid URL."
msgstr "Introdueixi una URL vàlida."
-#: newforms/fields.py:499
+#: newforms/fields.py:493
msgid "This URL appears to be a broken link."
msgstr "Aquesta URL sembla ser un enllaç trencat."
-#: newforms/fields.py:560 newforms/models.py:194
+#: newforms/fields.py:555 newforms/models.py:155
msgid "Select a valid choice. That choice is not one of the available choices."
msgstr ""
"Esculli una opció vàlida; Aquesta opció no és una de les opcions disponibles."
-#: newforms/fields.py:599
+#: newforms/fields.py:594
#, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr "Esculli una opció vàlida. %(value)s no és una de les opcions vàlides."
-#: newforms/fields.py:600 newforms/fields.py:662 newforms/models.py:215
+#: newforms/fields.py:595 newforms/fields.py:657 newforms/models.py:215
msgid "Enter a list of values."
msgstr "Introdueixi una llista de valors."
-#: newforms/fields.py:753
+#: newforms/fields.py:748
msgid "Enter a valid IPv4 address."
msgstr "Introdueixi una adreça IPv4 vàlida."
-#: newforms/models.py:221
+#: newforms/models.py:216
#, python-format
msgid "Select a valid choice. %s is not one of the available choices."
msgstr "Esculli una opció vàlida; %s' no és una de les opcions vàlides."
-#: oldforms/__init__.py:408
+#: oldforms/__init__.py:409
#, python-format
msgid "Ensure your text is less than %s character."
msgid_plural "Ensure your text is less than %s characters."
msgstr[0] "Asseguris de que el seu texte té menys de %s caracter."
msgstr[1] "Asseguris de que el seu texte té menys de %s caracters."
-#: oldforms/__init__.py:413
+#: oldforms/__init__.py:414
msgid "Line breaks are not allowed here."
msgstr "No es permeten salts de línia."
-#: oldforms/__init__.py:511 oldforms/__init__.py:585 oldforms/__init__.py:624
+#: oldforms/__init__.py:512 oldforms/__init__.py:586 oldforms/__init__.py:625
#, python-format
msgid "Select a valid choice; '%(data)s' is not in %(choices)s."
msgstr "Esculli una opció vàlida; %(data)s' no està dintre de %(choices)s."
-#: oldforms/__init__.py:744
+#: oldforms/__init__.py:745
msgid "Enter a whole number between -32,768 and 32,767."
msgstr "Introdueixi un número enter entre -32,768 i 32,767."
-#: oldforms/__init__.py:754
+#: oldforms/__init__.py:755
msgid "Enter a positive number."
msgstr "Introdueixi un número positiu."
-#: oldforms/__init__.py:764
+#: oldforms/__init__.py:765
msgid "Enter a whole number between 0 and 32,767."
msgstr "Introdueixi un número entre 0 i 32,767."
-#: template/defaultfilters.py:555
+#: template/defaultfilters.py:658
msgid "yes,no,maybe"
msgstr "si,no,potser"
-#: template/defaultfilters.py:585
+#: template/defaultfilters.py:689
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d byte"
msgstr[1] "%(size)d bytes"
-#: template/defaultfilters.py:587
+#: template/defaultfilters.py:691
#, python-format
msgid "%.1f KB"
msgstr "%.1f KB"
-#: template/defaultfilters.py:589
+#: template/defaultfilters.py:693
#, python-format
msgid "%.1f MB"
msgstr "%.1f MB"
-#: template/defaultfilters.py:590
+#: template/defaultfilters.py:694
#, python-format
msgid "%.1f GB"
msgstr "%.1f GB"
@@ -3890,23 +3934,23 @@ msgstr "%(number)d %(type)s"
msgid ", %(number)d %(type)s"
msgstr ", %(number)d %(type)s"
-#: utils/translation/trans_real.py:395
+#: utils/translation/trans_real.py:399
msgid "DATE_FORMAT"
msgstr "F j, Y"
-#: utils/translation/trans_real.py:396
+#: utils/translation/trans_real.py:400
msgid "DATETIME_FORMAT"
msgstr "F j, Y, H:i"
-#: utils/translation/trans_real.py:397
+#: utils/translation/trans_real.py:401
msgid "TIME_FORMAT"
msgstr "H:i"
-#: utils/translation/trans_real.py:413
+#: utils/translation/trans_real.py:417
msgid "YEAR_MONTH_FORMAT"
msgstr "j de/d' F del Y"
-#: utils/translation/trans_real.py:414
+#: utils/translation/trans_real.py:418
msgid "MONTH_DAY_FORMAT"
msgstr "j de/d' F del Y"
@@ -3924,43 +3968,3 @@ msgstr "El/La %(verbose_name)s s'ha actualtzat amb èxit."
#, python-format
msgid "The %(verbose_name)s was deleted."
msgstr "El %(verbose_name)s s'ha eliminat."
-
-#~ msgid ""
-#~ "This comment was posted by a user who has posted fewer than %(count)s "
-#~ "comment:\n"
-#~ "\n"
-#~ "%(text)sThis comment was posted by a user who has posted fewer than %"
-#~ "(count)s comments:\n"
-#~ "\n"
-#~ "%(text)s"
-#~ msgstr ""
-#~ "Aquest comentari el va enviar un usuari que ha enviat menys de %(count)s "
-#~ "comentari:\n"
-#~ "\n"
-#~ "%(text)sAquest comentari el va enviar un usuari que ha enviat menys de %"
-#~ "(count)s comentaris:\n"
-#~ "\n"
-#~ "%(text)s"
-
-#~ msgid "AnonymousUser"
-#~ msgstr "AnonymousUser"
-
-#~ msgid ""
-#~ "Please enter a valid decimal number with a whole part of at most %s digit."
-#~ "Please enter a valid decimal number with a whole part of at most %s "
-#~ "digits."
-#~ msgstr ""
-#~ "Si us plau, introdueixi un número decimal vàlid amb la part entera amb "
-#~ "com a màxim %s dígit.Si us plau, introdueixi un número decimal vàlid amb "
-#~ "la part entera amb com a màxim %s dígits."
-
-#~ msgid ""
-#~ "Please enter a valid decimal number with at most %s decimal place.Please "
-#~ "enter a valid decimal number with at most %s decimal places."
-#~ msgstr ""
-#~ "Si us plau, introdueixi un número decimal vàlid amb no més de %s dígit en "
-#~ "la part decimal.Si us plau, introdueixi un número decimal vàlid amb no "
-#~ "més de %s dígits en la part decimal."
-
-#~ msgid "%d milliseconds"
-#~ msgstr "%d milisegons"
diff --git a/django/conf/locale/ca/LC_MESSAGES/djangojs.mo b/django/conf/locale/ca/LC_MESSAGES/djangojs.mo
index 581b176be4..ddd7859675 100644
--- a/django/conf/locale/ca/LC_MESSAGES/djangojs.mo
+++ b/django/conf/locale/ca/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/django/conf/locale/ca/LC_MESSAGES/djangojs.po b/django/conf/locale/ca/LC_MESSAGES/djangojs.po
index 3ae0e9bad8..47f41acdda 100644
--- a/django/conf/locale/ca/LC_MESSAGES/djangojs.po
+++ b/django/conf/locale/ca/LC_MESSAGES/djangojs.po
@@ -1,19 +1,21 @@
+# translation of djangojs.po to español
# translation of djangojs.po to
# Catalan translation for the django-admin JS files.
# This file is distributed under the same license as the Django package.
#
+# Antoni Aloy <antoni.aloy@trespams.com>, 2007.
msgid ""
msgstr ""
"Project-Id-Version: djangojs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-05-20 18:25+0200\n"
-"PO-Revision-Date: 2007-06-25 17:47+0200\n"
-"Last-Translator: Marc Fargas <telenieko@telenieko.com>\n"
-"Language-Team: <es@li.org>\n"
+"PO-Revision-Date: 2007-12-01 12:06+0100\n"
+"Last-Translator: Antoni Aloy <antoni.aloy@trespams.com>\n"
+"Language-Team: español <ca@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: VIM 7.0\n"
+"X-Generator: KBabel 1.11.4\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: contrib/admin/media/js/SelectFilter2.js:33
@@ -51,8 +53,7 @@ msgstr "Deseleccionar tots"
msgid ""
"January February March April May June July August September October November "
"December"
-msgstr ""
-"Febrer Març Abril Maig Juny Juliol Agost Setembre Octubre Novembre Desembre"
+msgstr "Gener Febrer Març Abril Maig Juny Juliol Agost Setembre Octubre Novembre Desembre"
#: contrib/admin/media/js/dateparse.js:33
msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
@@ -117,3 +118,4 @@ msgstr "Mostrar"
#: contrib/admin/media/js/admin/CollapsedFieldsets.js:63
msgid "Hide"
msgstr "Ocultar"
+
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index a4e6269b6f..e45b6ed0bb 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -114,7 +114,7 @@ def result_headers(cl):
yield {"text": header,
"sortable": True,
"url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
- "class_attrib": (th_classes and ' class="%s"' % ' '.join(th_classes) or '')}
+ "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')}
def _boolean_icon(field_val):
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 33f92dc854..4b1590264b 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -322,7 +322,7 @@ class AnonymousUser(object):
id = None
username = ''
is_staff = False
- is_active = True
+ is_active = False
is_superuser = False
_groups = EmptyManager()
_user_permissions = EmptyManager()
diff --git a/django/contrib/auth/tests.py b/django/contrib/auth/tests.py
index 329049c546..d369ac524c 100644
--- a/django/contrib/auth/tests.py
+++ b/django/contrib/auth/tests.py
@@ -16,9 +16,21 @@ False
>>> u2 = User.objects.create_user('testuser2', 'test2@example.com')
>>> u2.has_usable_password()
False
+
+>>> u.is_authenticated()
+True
+>>> u.is_staff
+False
+>>> u.is_active
+True
+
>>> a = AnonymousUser()
+>>> a.is_authenticated()
+False
>>> a.is_staff
False
+>>> a.is_active
+False
>>> a.groups.all()
[]
>>> a.user_permissions.all()
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index b738a269ec..e496ed1af8 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -16,18 +16,18 @@ class GenericForeignKey(object):
Provides a generic relation to any object through content-type/object-id
fields.
"""
-
+
def __init__(self, ct_field="content_type", fk_field="object_id"):
self.ct_field = ct_field
self.fk_field = fk_field
-
+
def contribute_to_class(self, cls, name):
- # Make sure the fields exist (these raise FieldDoesNotExist,
+ # Make sure the fields exist (these raise FieldDoesNotExist,
# which is a fine error to raise here)
self.name = name
self.model = cls
self.cache_attr = "_%s_cache" % name
-
+
# For some reason I don't totally understand, using weakrefs here doesn't work.
dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False)
@@ -35,18 +35,18 @@ class GenericForeignKey(object):
setattr(cls, name, self)
def instance_pre_init(self, signal, sender, args, kwargs):
- # Handle initalizing an object with the generic FK instaed of
- # content-type/object-id fields.
+ # Handle initalizing an object with the generic FK instaed of
+ # content-type/object-id fields.
if self.name in kwargs:
value = kwargs.pop(self.name)
kwargs[self.ct_field] = self.get_content_type(value)
kwargs[self.fk_field] = value._get_pk_val()
-
+
def get_content_type(self, obj):
# Convenience function using get_model avoids a circular import when using this model
ContentType = get_model("contenttypes", "contenttype")
return ContentType.objects.get_for_model(obj)
-
+
def __get__(self, instance, instance_type=None):
if instance is None:
raise AttributeError, u"%s must be accessed via instance" % self.name
@@ -77,21 +77,21 @@ class GenericForeignKey(object):
setattr(instance, self.ct_field, ct)
setattr(instance, self.fk_field, fk)
setattr(instance, self.cache_attr, value)
-
+
class GenericRelation(RelatedField, Field):
"""Provides an accessor to generic related objects (i.e. comments)"""
def __init__(self, to, **kwargs):
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
- kwargs['rel'] = GenericRel(to,
+ kwargs['rel'] = GenericRel(to,
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
symmetrical=kwargs.pop('symmetrical', True))
-
+
# Override content-type/object-id field names on the related class
self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
- self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
-
+ self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
+
kwargs['blank'] = True
kwargs['editable'] = False
kwargs['serialize'] = False
@@ -116,9 +116,9 @@ class GenericRelation(RelatedField, Field):
def m2m_column_name(self):
return self.object_id_field_name
-
+
def m2m_reverse_name(self):
- return self.object_id_field_name
+ return self.model._meta.pk.column
def contribute_to_class(self, cls, name):
super(GenericRelation, self).contribute_to_class(cls, name)
@@ -131,13 +131,13 @@ class GenericRelation(RelatedField, Field):
def contribute_to_related_class(self, cls, related):
pass
-
+
def set_attributes_from_rel(self):
pass
def get_internal_type(self):
return "ManyToManyField"
-
+
class ReverseGenericRelatedObjectsDescriptor(object):
"""
This class provides the functionality that makes the related-object
@@ -193,12 +193,12 @@ def create_generic_related_manager(superclass):
Factory function for a manager that subclasses 'superclass' (which is a
Manager) and adds behavior for generic related objects.
"""
-
+
class GenericRelatedObjectManager(superclass):
def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None,
join_table=None, source_col_name=None, target_col_name=None, content_type=None,
content_type_field_name=None, object_id_field_name=None):
-
+
super(GenericRelatedObjectManager, self).__init__()
self.core_filters = core_filters or {}
self.model = model
@@ -212,10 +212,10 @@ def create_generic_related_manager(superclass):
self.content_type_field_name = content_type_field_name
self.object_id_field_name = object_id_field_name
self.pk_val = self.instance._get_pk_val()
-
+
def get_query_set(self):
query = {
- '%s__pk' % self.content_type_field_name : self.content_type.id,
+ '%s__pk' % self.content_type_field_name : self.content_type.id,
'%s__exact' % self.object_id_field_name : self.pk_val,
}
return superclass.get_query_set(self).filter(**query)
diff --git a/django/contrib/localflavor/mx/__init__.py b/django/contrib/localflavor/mx/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/contrib/localflavor/mx/__init__.py
diff --git a/django/contrib/localflavor/mx/forms.py b/django/contrib/localflavor/mx/forms.py
new file mode 100644
index 0000000000..5e42cca7d7
--- /dev/null
+++ b/django/contrib/localflavor/mx/forms.py
@@ -0,0 +1,14 @@
+"""
+Mexican-specific form helpers.
+"""
+
+from django.newforms.fields import Select
+
+class MXStateSelect(Select):
+ """
+ A Select widget that uses a list of Mexican states as its choices.
+ """
+ def __init__(self, attrs=None):
+ from mx_states import STATE_CHOICES
+ super(MXStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
+
diff --git a/django/contrib/localflavor/mx/mx_states.py b/django/contrib/localflavor/mx/mx_states.py
new file mode 100644
index 0000000000..eed1700efb
--- /dev/null
+++ b/django/contrib/localflavor/mx/mx_states.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+"""
+A list of Mexican states for use as `choices` in a formfield.
+
+This exists in this standalone file so that it's only imported into memory
+when explicitly needed.
+"""
+
+from django.utils.translation import ugettext_lazy as _
+
+STATE_CHOICES = (
+ ('AGU', _(u'Aguascalientes')),
+ ('BCN', _(u'Baja California')),
+ ('BCS', _(u'Baja California Sur')),
+ ('CAM', _(u'Campeche')),
+ ('CHH', _(u'Chihuahua')),
+ ('CHP', _(u'Chiapas')),
+ ('COA', _(u'Coahuila')),
+ ('COL', _(u'Colima')),
+ ('DIF', _(u'Distrito Federal')),
+ ('DUR', _(u'Durango')),
+ ('GRO', _(u'Guerrero')),
+ ('GUA', _(u'Guanajuato')),
+ ('HID', _(u'Hidalgo')),
+ ('JAL', _(u'Jalisco')),
+ ('MEX', _(u'Estado de México')),
+ ('MIC', _(u'Michoacán')),
+ ('MOR', _(u'Morelos')),
+ ('NAY', _(u'Nayarit')),
+ ('NLE', _(u'Nuevo León')),
+ ('OAX', _(u'Oaxaca')),
+ ('PUE', _(u'Puebla')),
+ ('QUE', _(u'Querétaro')),
+ ('ROO', _(u'Quintana Roo')),
+ ('SIN', _(u'Sinaloa')),
+ ('SLP', _(u'San Luis Potosí')),
+ ('SON', _(u'Sonora')),
+ ('TAB', _(u'Tabasco')),
+ ('TAM', _(u'Tamaulipas')),
+ ('TLA', _(u'Tlaxcala')),
+ ('VER', _(u'Veracruz')),
+ ('YUC', _(u'Yucatán')),
+ ('ZAC', _(u'Zacatecas')),
+)
+
diff --git a/django/contrib/localflavor/uk/forms.py b/django/contrib/localflavor/uk/forms.py
index 84d6c0e157..2b162230de 100644
--- a/django/contrib/localflavor/uk/forms.py
+++ b/django/contrib/localflavor/uk/forms.py
@@ -2,7 +2,7 @@
UK-specific Form helpers
"""
-from django.newforms.fields import RegexField
+from django.newforms.fields import RegexField, Select
from django.utils.translation import ugettext
class UKPostcodeField(RegexField):
@@ -17,3 +17,19 @@ class UKPostcodeField(RegexField):
max_length=None, min_length=None,
error_message=ugettext(u'Enter a postcode. A space is required between the two postcode parts.'),
*args, **kwargs)
+
+class UKCountySelect(Select):
+ """
+ A Select widget that uses a list of UK Counties/Regions as its choices.
+ """
+ def __init__(self, attrs=None):
+ from uk_regions import UK_REGION_CHOICES
+ super(UKCountySelect, self).__init__(attrs, choices=UK_REGION_CHOICES)
+
+class UKNationSelect(Select):
+ """
+ A Select widget that uses a list of UK Nations as its choices.
+ """
+ def __init__(self, attrs=None):
+ from uk_regions import UK_NATIONS_CHOICES
+ super(UKNationSelect, self).__init__(attrs, choices=UK_NATIONS_CHOICES)
diff --git a/django/contrib/localflavor/uk/uk_regions.py b/django/contrib/localflavor/uk/uk_regions.py
new file mode 100644
index 0000000000..3e2c16ef5d
--- /dev/null
+++ b/django/contrib/localflavor/uk/uk_regions.py
@@ -0,0 +1,97 @@
+"""
+Sources:
+ English regions: http://www.statistics.gov.uk/geography/downloads/31_10_01_REGION_names_and_codes_12_00.xls
+ Northern Ireland regions: http://en.wikipedia.org/wiki/List_of_Irish_counties_by_area
+ Welsh regions: http://en.wikipedia.org/wiki/Preserved_counties_of_Wales
+ Scottish regions: http://en.wikipedia.org/wiki/Regions_and_districts_of_Scotland
+"""
+from django.utils.translation import ugettext as _
+
+ENGLAND_REGION_CHOICES = (
+ ("Bedfordshire", _("Bedfordshire")),
+ ("Buckinghamshire", _("Buckinghamshire")),
+ ("Cambridgeshire", ("Cambridgeshire")),
+ ("Cheshire", _("Cheshire")),
+ ("Cornwall and Isles of Scilly", _("Cornwall and Isles of Scilly")),
+ ("Cumbria", _("Cumbria")),
+ ("Derbyshire", _("Derbyshire")),
+ ("Devon", _("Devon")),
+ ("Dorset", _("Dorset")),
+ ("Durham", _("Durham")),
+ ("East Sussex", _("East Sussex")),
+ ("Essex", _("Essex")),
+ ("Gloucestershire", _("Gloucestershire")),
+ ("Greater London", _("Greater London")),
+ ("Greater Manchester", _("Greater Manchester")),
+ ("Hampshire", _("Hampshire")),
+ ("Hertfordshire", _("Hertfordshire")),
+ ("Kent", _("Kent")),
+ ("Lancashire", _("Lancashire")),
+ ("Leicestershire", _("Leicestershire")),
+ ("Lincolnshire", _("Lincolnshire")),
+ ("Merseyside", _("Merseyside")),
+ ("Norfolk", _("Norfolk")),
+ ("North Yorkshire", _("North Yorkshire")),
+ ("Northamptonshire", _("Northamptonshire")),
+ ("Northumberland", _("Northumberland")),
+ ("Nottinghamshire", _("Nottinghamshire")),
+ ("Oxfordshire", _("Oxfordshire")),
+ ("Shropshire", _("Shropshire")),
+ ("Somerset", _("Somerset")),
+ ("South Yorkshire", _("South Yorkshire")),
+ ("Staffordshire", _("Staffordshire")),
+ ("Suffolk", _("Suffolk")),
+ ("Surrey", _("Surrey")),
+ ("Tyne and Wear", _("Tyne and Wear")),
+ ("Warwickshire", _("Warwickshire")),
+ ("West Midlands", _("West Midlands")),
+ ("West Sussex", _("West Sussex")),
+ ("West Yorkshire", _("West Yorkshire")),
+ ("Wiltshire", _("Wiltshire")),
+ ("Worcestershire", _("Worcestershire")),
+)
+
+NORTHERN_IRELAND_REGION_CHOICES = (
+ ("County Antrim", _("County Antrim")),
+ ("County Armagh", _("County Armagh")),
+ ("County Down", _("County Down")),
+ ("County Fermanagh", _("County Down")),
+ ("County Londonderry", _("County Londonderry")),
+ ("County Tyrone", _("County Tyrone")),
+)
+
+WALES_REGION_CHOICES = (
+ ("Clwyd", _("Clwyd")),
+ ("Dyfed", _("Dyfed")),
+ ("Gwent", _("Gwent")),
+ ("Gwynedd", _("Gwynedd")),
+ ("Mid Glamorgan", _("Mid Glamorgan")),
+ ("Powys", _("Powys")),
+ ("South Glamorgan", _("South Glamorgan")),
+ ("West Glamorgan", _("West Glamorgan")),
+)
+
+SCOTTISH_REGION_CHOICES = (
+ ("Borders", _("Borders")),
+ ("Central Scotland", _("Central Scotland")),
+ ("Dumfries and Galloway", _("Dumfries and Galloway")),
+ ("Fife", _("Fife")),
+ ("Grampian", _("Grampian")),
+ ("Highland", _("Highland")),
+ ("Lothian", _("Lothian")),
+ ("Orkney Islands", _("Orkney Islands")),
+ ("Shetland Islands", _("Shetland Islands")),
+ ("Strathclyde", _("Strathclyde")),
+ ("Tayside", _("Tayside")),
+ ("Western Isles", _("Western Isles")),
+)
+
+UK_NATIONS_CHOICES = (
+ ("England", _("England")),
+ ("Northern Ireland", _("Northern Ireland")),
+ ("Scotland", _("Scotland")),
+ ("Wales", _("Wales")),
+)
+
+UK_REGION_CHOICES = ENGLAND_REGION_CHOICES + NORTHERN_IRELAND_REGION_CHOICES + WALES_REGION_CHOICES + SCOTTISH_REGION_CHOICES
+
diff --git a/django/contrib/localflavor/za/__init__.py b/django/contrib/localflavor/za/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/contrib/localflavor/za/__init__.py
diff --git a/django/contrib/localflavor/za/forms.py b/django/contrib/localflavor/za/forms.py
new file mode 100644
index 0000000000..b04b7cf6ab
--- /dev/null
+++ b/django/contrib/localflavor/za/forms.py
@@ -0,0 +1,57 @@
+"""
+South Africa-specific Form helpers
+"""
+
+from django.newforms import ValidationError
+from django.newforms.fields import Field, RegexField, EMPTY_VALUES
+from django.utils.checksums import luhn
+from django.utils.translation import gettext as _
+import re
+from datetime import date
+
+id_re = re.compile(r'^(?P<yy>\d\d)(?P<mm>\d\d)(?P<dd>\d\d)(?P<mid>\d{4})(?P<end>\d{3})')
+
+class ZAIDField(Field):
+ """A form field for South African ID numbers -- the checksum is validated
+ using the Luhn checksum, and uses a simlistic (read: not entirely accurate)
+ check for the birthdate
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(ZAIDField, self).__init__()
+ self.error_message = _(u'Enter a valid South African ID number')
+
+ def clean(self, value):
+ # strip spaces and dashes
+ value = value.strip().replace(' ', '').replace('-', '')
+
+ super(ZAIDField, self).clean(value)
+
+ if value in EMPTY_VALUES:
+ return u''
+
+ match = re.match(id_re, value)
+
+ if not match:
+ raise ValidationError(self.error_message)
+
+ g = match.groupdict()
+
+ try:
+ # The year 2000 is conveniently a leapyear.
+ # This algorithm will break in xx00 years which aren't leap years
+ # There is no way to guess the century of a ZA ID number
+ d = date(int(g['yy']) + 2000, int(g['mm']), int(g['dd']))
+ except ValueError:
+ raise ValidationError(self.error_message)
+
+ if not luhn(value):
+ raise ValidationError(self.error_message)
+
+ return value
+
+class ZAPostCodeField(RegexField):
+ def __init__(self, *args, **kwargs):
+ super(ZAPostCodeField, self).__init__(r'^\d{4}$',
+ max_length=None, min_length=None,
+ error_message=_(u'Enter a valid South African postal code'))
diff --git a/django/contrib/localflavor/za/za_provinces.py b/django/contrib/localflavor/za/za_provinces.py
new file mode 100644
index 0000000000..0bc6fe14b3
--- /dev/null
+++ b/django/contrib/localflavor/za/za_provinces.py
@@ -0,0 +1,13 @@
+from django.utils.translation import gettext_lazy as _
+
+PROVINCE_CHOICES = (
+ ('EC', _('Eastern Cape')),
+ ('FS', _('Free State')),
+ ('GP', _('Gauteng')),
+ ('KN', _('KwaZulu-Natal')),
+ ('LP', _('Limpopo')),
+ ('MP', _('Mpumalanga')),
+ ('NC', _('Northern Cape')),
+ ('NW', _('North West')),
+ ('WC', _('Western Cape')),
+)
diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py
index 13708fd26d..5d4f4786e1 100644
--- a/django/contrib/markup/templatetags/markup.py
+++ b/django/contrib/markup/templatetags/markup.py
@@ -32,7 +32,23 @@ def textile(value):
return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8')))
textile.is_safe = True
-def markdown(value):
+def markdown(value, arg=''):
+ """
+ Runs Markdown over a given value, optionally using various
+ extensions python-markdown supports.
+
+ Syntax::
+
+ {{ value|markdown:"extension1_name,extension2_name..." }}
+
+ To enable safe mode, which strips raw HTML and only returns HTML
+ generated by actual Markdown syntax, pass "safe" as the first
+ extension in the list.
+
+ If the version of Markdown in use does not support extensions,
+ they will be silently ignored.
+
+ """
try:
import markdown
except ImportError:
@@ -40,7 +56,18 @@ def markdown(value):
raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
return force_unicode(value)
else:
- return mark_safe(force_unicode(markdown.markdown(smart_str(value))))
+ # markdown.version was first added in 1.6b. The only version of markdown
+ # to fully support extensions before 1.6b was the shortlived 1.6a.
+ if hasattr(markdown, 'version'):
+ extensions = [e for e in arg.split(",") if e]
+ if len(extensions) > 0 and extensions[0] == "safe":
+ extensions = extensions[1:]
+ safe_mode = True
+ else:
+ safe_mode = False
+ return mark_safe(force_unicode(markdown.markdown(smart_str(value), extensions, safe_mode=safe_mode)))
+ else:
+ return mark_safe(force_unicode(markdown.markdown(smart_str(value))))
markdown.is_safe = True
def restructuredtext(value):
diff --git a/django/contrib/markup/tests.py b/django/contrib/markup/tests.py
index 6e7beaeb2f..9a96f8cda4 100644
--- a/django/contrib/markup/tests.py
+++ b/django/contrib/markup/tests.py
@@ -61,8 +61,15 @@ Paragraph 2 with a link_
t = Template("{{ rest_content|restructuredtext }}")
rendered = t.render(Context(locals())).strip()
if docutils:
- self.assertEqual(rendered, """<p>Paragraph 1</p>
+ # Different versions of docutils return slightly different HTML
+ try:
+ # Docutils v0.4 and earlier
+ self.assertEqual(rendered, """<p>Paragraph 1</p>
<p>Paragraph 2 with a <a class="reference" href="http://www.example.com/">link</a></p>""")
+ except AssertionError, e:
+ # Docutils from SVN (which will become 0.5)
+ self.assertEqual(rendered, """<p>Paragraph 1</p>
+<p>Paragraph 2 with a <a class="reference external" href="http://www.example.com/">link</a></p>""")
else:
self.assertEqual(rendered, rest_content)
diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py
index 192065a5f3..b8726fd2bd 100644
--- a/django/contrib/sessions/backends/base.py
+++ b/django/contrib/sessions/backends/base.py
@@ -51,6 +51,14 @@ class SessionBase(object):
self.modified = self.modified or key in self._session
return self._session.pop(key, *args)
+ def setdefault(self, key, value):
+ if key in self._session:
+ return self._session[key]
+ else:
+ self.modified = True
+ self._session[key] = value
+ return value
+
def set_test_cookie(self):
self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py
index 221db5cc60..a8c3c69b10 100644
--- a/django/contrib/sessions/backends/file.py
+++ b/django/contrib/sessions/backends/file.py
@@ -1,14 +1,23 @@
import os
+import tempfile
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase
-from django.core.exceptions import SuspiciousOperation
+from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
class SessionStore(SessionBase):
"""
Implements a file based session store.
"""
def __init__(self, session_key=None):
- self.storage_path = settings.SESSION_FILE_PATH
+ self.storage_path = getattr(settings, "SESSION_FILE_PATH", tempfile.gettempdir())
+
+ # Make sure the storage path is valid.
+ if not os.path.isdir(self.storage_path):
+ raise ImproperlyConfigured("The session storage path %r doesn't exist. "\
+ "Please set your SESSION_FILE_PATH setting "\
+ "to an existing directory in which Django "\
+ "can store session data." % self.storage_path)
+
self.file_prefix = settings.SESSION_COOKIE_NAME
super(SessionStore, self).__init__(session_key)
diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py
index 8fc5e17d69..dfa7bed226 100644
--- a/django/contrib/sessions/models.py
+++ b/django/contrib/sessions/models.py
@@ -18,40 +18,6 @@ class SessionManager(models.Manager):
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
return base64.encodestring(pickled + pickled_md5)
- def get_new_session_key(self):
- "Returns session key that isn't being used."
- # The random module is seeded when this Apache child is created.
- # Use SECRET_KEY as added salt.
- try:
- pid = os.getpid()
- except AttributeError:
- # No getpid() in Jython, for example
- pid = 1
- while 1:
- session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), pid, time.time(), settings.SECRET_KEY)).hexdigest()
- try:
- self.get(session_key=session_key)
- except self.model.DoesNotExist:
- break
- return session_key
-
- def get_new_session_object(self):
- """
- Returns a new session object.
- """
- # FIXME: There is a *small* chance of collision here, meaning we will
- # return an existing object. That can be fixed when we add a way to
- # validate (and guarantee) that non-auto primary keys are unique. For
- # now, we save immediately in order to reduce the "window of
- # misfortune" as much as possible.
- created = False
- while not created:
- obj, created = self.get_or_create(session_key=self.get_new_session_key(),
- expire_date = datetime.datetime.now())
- # Collision in key generation, so re-seed the generator
- random.seed()
- return obj
-
def save(self, session_key, session_dict, expire_date):
s = self.model(session_key, self.encode(session_dict), expire_date)
if session_dict:
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
index ab3034f858..b2c664ce7b 100644
--- a/django/contrib/sessions/tests.py
+++ b/django/contrib/sessions/tests.py
@@ -1,5 +1,6 @@
r"""
+>>> from django.conf import settings
>>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
>>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession
>>> from django.contrib.sessions.backends.file import SessionStore as FileSession
@@ -39,6 +40,13 @@ True
>>> file_session.exists(file_session.session_key)
False
+# Make sure the file backend checks for a good storage dir
+>>> settings.SESSION_FILE_PATH = "/if/this/directory/exists/you/have/a/weird/computer"
+>>> FileSession()
+Traceback (innermost last):
+ ...
+ImproperlyConfigured: The session storage path '/if/this/directory/exists/you/have/a/weird/computer' doesn't exist. Please set your SESSION_FILE_PATH setting to an existing directory in which Django can store session data.
+
>>> cache_session = CacheSession()
>>> cache_session.modified
False
@@ -66,6 +74,11 @@ False
>>> s.accessed, s.modified
(True, False)
+>>> s.setdefault('foo', 'bar')
+'bar'
+>>> s.setdefault('foo', 'baz')
+'bar'
+
>>> s.accessed = False # Reset the accessed flag
>>> s.pop('some key')
diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py
index 6da8e883b9..495cc92822 100644
--- a/django/core/cache/__init__.py
+++ b/django/core/cache/__init__.py
@@ -22,19 +22,28 @@ from django.core.cache.backends.base import InvalidCacheBackendError
BACKENDS = {
# name for use in settings file --> name of module in "backends" directory
'memcached': 'memcached',
- 'simple': 'simple',
'locmem': 'locmem',
'file': 'filebased',
'db': 'db',
'dummy': 'dummy',
}
+DEPRECATED_BACKENDS = {
+ # deprecated backend --> replacement module
+ 'simple': 'locmem',
+}
+
def get_cache(backend_uri):
if backend_uri.find(':') == -1:
raise InvalidCacheBackendError, "Backend URI must start with scheme://"
scheme, rest = backend_uri.split(':', 1)
if not rest.startswith('//'):
raise InvalidCacheBackendError, "Backend URI must start with scheme://"
+ if scheme in DEPRECATED_BACKENDS:
+ import warnings
+ warnings.warn("'%s' backend is deprecated. Use '%s' instead." %
+ (scheme, DEPRECATED_BACKENDS[scheme]), DeprecationWarning)
+ scheme = DEPRECATED_BACKENDS[scheme]
if scheme not in BACKENDS:
raise InvalidCacheBackendError, "%r is not a valid cache backend" % scheme
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index 690193ac81..c1277bf20c 100644
--- a/django/core/cache/backends/filebased.py
+++ b/django/core/cache/backends/filebased.py
@@ -1,41 +1,38 @@
"File-based cache backend"
-from django.core.cache.backends.simple import CacheClass as SimpleCacheClass
-from django.utils.http import urlquote_plus
+import md5
import os, time
try:
import cPickle as pickle
except ImportError:
import pickle
+from django.core.cache.backends.base import BaseCache
-class CacheClass(SimpleCacheClass):
+class CacheClass(BaseCache):
def __init__(self, dir, params):
+ BaseCache.__init__(self, params)
+
+ max_entries = params.get('max_entries', 300)
+ try:
+ self._max_entries = int(max_entries)
+ except (ValueError, TypeError):
+ self._max_entries = 300
+
+ cull_frequency = params.get('cull_frequency', 3)
+ try:
+ self._cull_frequency = int(cull_frequency)
+ except (ValueError, TypeError):
+ self._cull_frequency = 3
+
self._dir = dir
if not os.path.exists(self._dir):
self._createdir()
- SimpleCacheClass.__init__(self, dir, params)
- del self._cache
- del self._expire_info
def add(self, key, value, timeout=None):
- fname = self._key_to_file(key)
- if timeout is None:
- timeout = self.default_timeout
- try:
- filelist = os.listdir(self._dir)
- except (IOError, OSError):
- self._createdir()
- filelist = []
- if len(filelist) > self._max_entries:
- self._cull(filelist)
- if os.path.basename(fname) not in filelist:
- try:
- f = open(fname, 'wb')
- now = time.time()
- pickle.dump(now + timeout, f, 2)
- pickle.dump(value, f, 2)
- except (IOError, OSError):
- pass
+ if self.has_key(key):
+ return None
+
+ self.set(key, value, timeout)
def get(self, key, default=None):
fname = self._key_to_file(key)
@@ -45,7 +42,7 @@ class CacheClass(SimpleCacheClass):
now = time.time()
if exp < now:
f.close()
- os.remove(fname)
+ self._delete(fname)
else:
return pickle.load(f)
except (IOError, OSError, EOFError, pickle.PickleError):
@@ -54,40 +51,74 @@ class CacheClass(SimpleCacheClass):
def set(self, key, value, timeout=None):
fname = self._key_to_file(key)
+ dirname = os.path.dirname(fname)
+
if timeout is None:
timeout = self.default_timeout
+
+ self._cull()
+
try:
- filelist = os.listdir(self._dir)
- except (IOError, OSError):
- self._createdir()
- filelist = []
- if len(filelist) > self._max_entries:
- self._cull(filelist)
- try:
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
f = open(fname, 'wb')
now = time.time()
- pickle.dump(now + timeout, f, 2)
- pickle.dump(value, f, 2)
+ pickle.dump(now + timeout, f, pickle.HIGHEST_PROTOCOL)
+ pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
except (IOError, OSError):
pass
def delete(self, key):
try:
- os.remove(self._key_to_file(key))
+ self._delete(self._key_to_file(key))
+ except (IOError, OSError):
+ pass
+
+ def _delete(self, fname):
+ os.remove(fname)
+ try:
+ # Remove the 2 subdirs if they're empty
+ dirname = os.path.dirname(fname)
+ os.rmdir(dirname)
+ os.rmdir(os.path.dirname(dirname))
except (IOError, OSError):
pass
def has_key(self, key):
- return os.path.exists(self._key_to_file(key))
+ fname = self._key_to_file(key)
+ try:
+ f = open(fname, 'rb')
+ exp = pickle.load(f)
+ now = time.time()
+ if exp < now:
+ f.close()
+ self._delete(fname)
+ return False
+ else:
+ return True
+ except (IOError, OSError, EOFError, pickle.PickleError):
+ return False
- def _cull(self, filelist):
+ def _cull(self):
+ if int(self._num_entries) < self._max_entries:
+ return
+
+ try:
+ filelist = os.listdir(self._dir)
+ except (IOError, OSError):
+ return
+
if self._cull_frequency == 0:
doomed = filelist
else:
- doomed = [k for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0]
- for fname in doomed:
+ doomed = [os.path.join(self._dir, k) for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0]
+
+ for topdir in doomed:
try:
- os.remove(os.path.join(self._dir, fname))
+ for root, _, files in os.walk(topdir):
+ for f in files:
+ self._delete(os.path.join(root, f))
except (IOError, OSError):
pass
@@ -98,4 +129,22 @@ class CacheClass(SimpleCacheClass):
raise EnvironmentError, "Cache directory '%s' does not exist and could not be created'" % self._dir
def _key_to_file(self, key):
- return os.path.join(self._dir, urlquote_plus(key))
+ """
+ Convert the filename into an md5 string. We'll turn the first couple
+ bits of the path into directory prefixes to be nice to filesystems
+ that have problems with large numbers of files in a directory.
+
+ Thus, a cache key of "foo" gets turnned into a file named
+ ``{cache-dir}ac/bd/18db4cc2f85cedef654fccc4a4d8``.
+ """
+ path = md5.new(key.encode('utf-8')).hexdigest()
+ path = os.path.join(path[:2], path[2:4], path[4:])
+ return os.path.join(self._dir, path)
+
+ def _get_num_entries(self):
+ count = 0
+ for _,_,files in os.walk(self._dir):
+ count += len(files)
+ return count
+ _num_entries = property(_get_num_entries)
+
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
index 2d74e2b132..e8e1e0d450 100644
--- a/django/core/cache/backends/locmem.py
+++ b/django/core/cache/backends/locmem.py
@@ -6,65 +6,122 @@ try:
except ImportError:
import pickle
-from django.core.cache.backends.simple import CacheClass as SimpleCacheClass
+from django.core.cache.backends.base import BaseCache
from django.utils.synch import RWLock
-class CacheClass(SimpleCacheClass):
- def __init__(self, host, params):
- SimpleCacheClass.__init__(self, host, params)
+class CacheClass(BaseCache):
+ def __init__(self, _, params):
+ BaseCache.__init__(self, params)
+ self._cache = {}
+ self._expire_info = {}
+
+ max_entries = params.get('max_entries', 300)
+ try:
+ self._max_entries = int(max_entries)
+ except (ValueError, TypeError):
+ self._max_entries = 300
+
+ cull_frequency = params.get('cull_frequency', 3)
+ try:
+ self._cull_frequency = int(cull_frequency)
+ except (ValueError, TypeError):
+ self._cull_frequency = 3
+
self._lock = RWLock()
def add(self, key, value, timeout=None):
self._lock.writer_enters()
- # Python 2.3 and 2.4 don't allow combined try-except-finally blocks.
try:
- try:
- super(CacheClass, self).add(key, pickle.dumps(value), timeout)
- except pickle.PickleError:
- pass
+ exp = self._expire_info.get(key)
+ if exp is None or exp <= time.time():
+ try:
+ self._set(key, pickle.dumps(value), timeout)
+ except pickle.PickleError:
+ pass
finally:
self._lock.writer_leaves()
def get(self, key, default=None):
- should_delete = False
self._lock.reader_enters()
try:
- now = time.time()
exp = self._expire_info.get(key)
if exp is None:
return default
- elif exp < now:
- should_delete = True
- else:
+ elif exp > time.time():
try:
return pickle.loads(self._cache[key])
except pickle.PickleError:
return default
finally:
self._lock.reader_leaves()
- if should_delete:
- self._lock.writer_enters()
- try:
- del self._cache[key]
- del self._expire_info[key]
- return default
- finally:
- self._lock.writer_leaves()
+ self._lock.writer_enters()
+ try:
+ del self._cache[key]
+ del self._expire_info[key]
+ return default
+ finally:
+ self._lock.writer_leaves()
+
+ def _set(self, key, value, timeout=None):
+ if len(self._cache) >= self._max_entries:
+ self._cull()
+ if timeout is None:
+ timeout = self.default_timeout
+ self._cache[key] = value
+ self._expire_info[key] = time.time() + timeout
def set(self, key, value, timeout=None):
self._lock.writer_enters()
# Python 2.3 and 2.4 don't allow combined try-except-finally blocks.
try:
try:
- super(CacheClass, self).set(key, pickle.dumps(value), timeout)
+ self._set(key, pickle.dumps(value), timeout)
except pickle.PickleError:
pass
finally:
self._lock.writer_leaves()
+ def has_key(self, key):
+ self._lock.reader_enters()
+ try:
+ exp = self._expire_info.get(key)
+ if exp is None:
+ return False
+ elif exp > time.time():
+ return True
+ finally:
+ self._lock.reader_leaves()
+
+ self._lock.writer_enters()
+ try:
+ del self._cache[key]
+ del self._expire_info[key]
+ return False
+ finally:
+ self._lock.writer_leaves()
+
+ def _cull(self):
+ if self._cull_frequency == 0:
+ self._cache.clear()
+ self._expire_info.clear()
+ else:
+ doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
+ for k in doomed:
+ self.delete(k)
+
+ def _delete(self, key):
+ try:
+ del self._cache[key]
+ except KeyError:
+ pass
+ try:
+ del self._expire_info[key]
+ except KeyError:
+ pass
+
def delete(self, key):
self._lock.writer_enters()
try:
- SimpleCacheClass.delete(self, key)
+ self._delete(key)
finally:
self._lock.writer_leaves()
diff --git a/django/core/cache/backends/simple.py b/django/core/cache/backends/simple.py
deleted file mode 100644
index ff60d49066..0000000000
--- a/django/core/cache/backends/simple.py
+++ /dev/null
@@ -1,73 +0,0 @@
-"Single-process in-memory cache backend."
-
-from django.core.cache.backends.base import BaseCache
-import time
-
-class CacheClass(BaseCache):
- def __init__(self, host, params):
- BaseCache.__init__(self, params)
- self._cache = {}
- self._expire_info = {}
-
- max_entries = params.get('max_entries', 300)
- try:
- self._max_entries = int(max_entries)
- except (ValueError, TypeError):
- self._max_entries = 300
-
- cull_frequency = params.get('cull_frequency', 3)
- try:
- self._cull_frequency = int(cull_frequency)
- except (ValueError, TypeError):
- self._cull_frequency = 3
-
- def add(self, key, value, timeout=None):
- if len(self._cache) >= self._max_entries:
- self._cull()
- if timeout is None:
- timeout = self.default_timeout
- if key not in self._cache.keys():
- self._cache[key] = value
- self._expire_info[key] = time.time() + timeout
-
- def get(self, key, default=None):
- now = time.time()
- exp = self._expire_info.get(key)
- if exp is None:
- return default
- elif exp < now:
- del self._cache[key]
- del self._expire_info[key]
- return default
- else:
- return self._cache[key]
-
- def set(self, key, value, timeout=None):
- if len(self._cache) >= self._max_entries:
- self._cull()
- if timeout is None:
- timeout = self.default_timeout
- self._cache[key] = value
- self._expire_info[key] = time.time() + timeout
-
- def delete(self, key):
- try:
- del self._cache[key]
- except KeyError:
- pass
- try:
- del self._expire_info[key]
- except KeyError:
- pass
-
- def has_key(self, key):
- return key in self._cache
-
- def _cull(self):
- if self._cull_frequency == 0:
- self._cache.clear()
- self._expire_info.clear()
- else:
- doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
- for k in doomed:
- self.delete(k)
diff --git a/django/core/exceptions.py b/django/core/exceptions.py
index f22f67c261..d9fc326cf2 100644
--- a/django/core/exceptions.py
+++ b/django/core/exceptions.py
@@ -4,6 +4,10 @@ class ObjectDoesNotExist(Exception):
"The requested object does not exist"
silent_variable_failure = True
+class MultipleObjectsReturned(Exception):
+ "The query returned multiple objects when only one was expected."
+ pass
+
class SuspiciousOperation(Exception):
"The user did something suspicious"
pass
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index 1796cae8ea..17a24d6f60 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -1,7 +1,8 @@
+import sys
+
+from django import http
from django.core import signals
from django.dispatch import dispatcher
-from django import http
-import sys
class BaseHandler(object):
# Changes that are always applied to a response (in this order).
diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py
index e81a65be4d..ebf79295e0 100644
--- a/django/core/handlers/modpython.py
+++ b/django/core/handlers/modpython.py
@@ -1,11 +1,12 @@
-from django.core.handlers.base import BaseHandler
+import os
+from pprint import pformat
+
+from django import http
from django.core import signals
+from django.core.handlers.base import BaseHandler
from django.dispatch import dispatcher
from django.utils import datastructures
from django.utils.encoding import force_unicode
-from django import http
-from pprint import pformat
-import os
# NOTE: do *not* import settings (or any module which eventually imports
# settings) until after ModPythonHandler has been called; otherwise os.environ
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 94575ca369..df2ba19b65 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -5,12 +5,12 @@ try:
except ImportError:
from StringIO import StringIO
-from django.core.handlers.base import BaseHandler
+from django import http
from django.core import signals
+from django.core.handlers.base import BaseHandler
from django.dispatch import dispatcher
from django.utils import datastructures
from django.utils.encoding import force_unicode
-from django import http
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
STATUS_CODE_TEXT = {
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index fcbc9d1110..d78e2eda0b 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -10,7 +10,7 @@ from django.core.management.base import BaseCommand, CommandError, handle_defaul
get_version = django.get_version
# A cache of loaded commands, so that call_command
-# doesn't have to reload every time it is called
+# doesn't have to reload every time it's called.
_commands = None
def find_commands(management_dir):
@@ -29,8 +29,8 @@ def find_commands(management_dir):
def find_management_module(app_name):
"""
- Determines the path to the management module for the application named,
- without acutally importing the application or the management module.
+ Determines the path to the management module for the given app_name,
+ without actually importing the application or the management module.
Raises ImportError if the management module cannot be found for any reason.
"""
@@ -46,19 +46,19 @@ def find_management_module(app_name):
def load_command_class(app_name, name):
"""
Given a command name and an application name, returns the Command
- class instance. All errors raised by the importation process
+ class instance. All errors raised by the import process
(ImportError, AttributeError) are allowed to propagate.
"""
return getattr(__import__('%s.management.commands.%s' % (app_name, name),
{}, {}, ['Command']), 'Command')()
-def get_commands():
+def get_commands(load_user_commands=True, project_directory=None):
"""
- Returns a dictionary of commands against the application in which
- those commands can be found. This works by looking for a
- management.commands package in django.core, and in each installed
- application -- if a commands package exists, all commands in that
- package are registered.
+ Returns a dictionary mapping command names to their callback applications.
+
+ This works by looking for a management.commands package in django.core, and
+ in each installed application -- if a commands package exists, all commands
+ in that package are registered.
Core commands are always included. If a settings module has been
specified, user-defined commands will also be included, the
@@ -73,34 +73,22 @@ def get_commands():
startapp command), the instantiated module can be placed in the
dictionary in place of the application name.
- The dictionary is cached on the first call, and reused on subsequent
+ The dictionary is cached on the first call and reused on subsequent
calls.
"""
global _commands
if _commands is None:
- _commands = dict([(name, 'django.core')
- for name in find_commands(__path__[0])])
- # Get commands from all installed apps.
- try:
- from django.conf import settings
- apps = settings.INSTALLED_APPS
- except (AttributeError, EnvironmentError):
- apps = []
-
- for app_name in apps:
- try:
- path = find_management_module(app_name)
- _commands.update(dict([(name, app_name)
- for name in find_commands(path)]))
- except ImportError:
- pass # No management module - ignore this app
-
- # Try to determine the project directory
- try:
+ _commands = dict([(name, 'django.core') for name in find_commands(__path__[0])])
+
+ if load_user_commands:
+ # Get commands from all installed apps.
from django.conf import settings
- project_directory = setup_environ(__import__(settings.SETTINGS_MODULE))
- except (AttributeError, EnvironmentError, ImportError):
- project_directory = None
+ for app_name in settings.INSTALLED_APPS:
+ try:
+ path = find_management_module(app_name)
+ _commands.update(dict([(name, app_name) for name in find_commands(path)]))
+ except ImportError:
+ pass # No management module -- ignore this app.
if project_directory:
# Remove the "startproject" command from self.commands, because
@@ -157,18 +145,18 @@ class ManagementUtility(object):
def __init__(self, argv=None):
self.argv = argv or sys.argv[:]
self.prog_name = os.path.basename(self.argv[0])
+ self.project_directory = None
+ self.user_commands = False
def main_help_text(self):
"""
Returns the script's main help text, as a string.
"""
usage = ['%s <subcommand> [options] [args]' % self.prog_name]
- usage.append('Django command line tool,'
- ' version %s' % django.get_version())
- usage.append("Type '%s help <subcommand>' for help on a specific"
- " subcommand." % self.prog_name)
+ usage.append('Django command line tool, version %s' % django.get_version())
+ usage.append("Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name)
usage.append('Available subcommands:')
- commands = get_commands().keys()
+ commands = get_commands(self.user_commands, self.project_directory).keys()
commands.sort()
for cmd in commands:
usage.append(' %s' % cmd)
@@ -178,18 +166,18 @@ class ManagementUtility(object):
"""
Tries to fetch the given subcommand, printing a message with the
appropriate command called from the command line (usually
- django-admin.py or manage.py) if it can't be found.
+ "django-admin.py" or "manage.py") if it can't be found.
"""
try:
- app_name = get_commands()[subcommand]
+ app_name = get_commands(self.user_commands, self.project_directory)[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.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \
+ (subcommand, self.prog_name))
sys.exit(1)
return klass
@@ -201,8 +189,7 @@ class ManagementUtility(object):
# Preprocess options to extract --settings and --pythonpath.
# These options could affect the commands that are available, so they
# must be processed early.
- parser = LaxOptionParser(version=get_version(),
- option_list=BaseCommand.option_list)
+ parser = LaxOptionParser(version=get_version(), option_list=BaseCommand.option_list)
try:
options, args = parser.parse_args(self.argv)
handle_default_options(options)
@@ -242,6 +229,8 @@ class ProjectManagementUtility(ManagementUtility):
"""
def __init__(self, argv, project_directory):
super(ProjectManagementUtility, self).__init__(argv)
+ self.project_directory = project_directory
+ self.user_commands = True
def setup_environ(settings_mod):
"""
@@ -254,6 +243,8 @@ def setup_environ(settings_mod):
# way. For example, if this file (manage.py) lives in a directory
# "myproject", this code would add "/path/to/myproject" to sys.path.
project_directory, settings_filename = os.path.split(settings_mod.__file__)
+ if not project_directory:
+ project_directory = os.getcwd()
project_name = os.path.basename(project_directory)
settings_name = os.path.splitext(settings_filename)[0]
sys.path.append(os.path.join(project_directory, os.pardir))
@@ -261,8 +252,7 @@ def setup_environ(settings_mod):
sys.path.pop()
# Set DJANGO_SETTINGS_MODULE appropriately.
- os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name,
- settings_name)
+ os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name)
return project_directory
def execute_from_command_line(argv=None):
diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
index 83fc9b8ac9..216db09141 100644
--- a/django/core/management/commands/loaddata.py
+++ b/django/core/management/commands/loaddata.py
@@ -58,7 +58,7 @@ class Command(BaseCommand):
else:
formats = []
- if verbosity > 0:
+ if verbosity > 2:
if formats:
print "Loading '%s' fixtures..." % fixture_name
else:
@@ -106,7 +106,7 @@ class Command(BaseCommand):
return
fixture.close()
except:
- if verbosity > 1:
+ if verbosity > 2:
print "No %s fixture '%s' in %s." % \
(format, fixture_name, humanize(fixture_dir))
@@ -122,7 +122,7 @@ class Command(BaseCommand):
transaction.leave_transaction_management()
if count[0] == 0:
- if verbosity > 0:
+ if verbosity > 2:
print "No fixtures found."
else:
if verbosity > 0:
diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
index 8b4bb68005..e6e84ec78c 100644
--- a/django/core/management/commands/syncdb.py
+++ b/django/core/management/commands/syncdb.py
@@ -33,8 +33,9 @@ class Command(NoArgsCommand):
for app_name in settings.INSTALLED_APPS:
try:
__import__(app_name + '.management', {}, {}, [''])
- except ImportError:
- pass
+ except ImportError, exc:
+ if not exc.args[0].startswith('No module named management'):
+ raise
cursor = connection.cursor()
diff --git a/django/core/management/validation.py b/django/core/management/validation.py
index c433b7fa96..bc9faae056 100644
--- a/django/core/management/validation.py
+++ b/django/core/management/validation.py
@@ -72,10 +72,14 @@ def get_validation_errors(outfile, app=None):
# Check to see if the related field will clash with any
# existing fields, m2m fields, m2m related objects or related objects
if f.rel:
- rel_opts = f.rel.to._meta
if f.rel.to not in models.get_models():
- e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, rel_opts.object_name))
+ e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, f.rel.to))
+ # it is a string and we could not find the model it refers to
+ # so skip the next section
+ if isinstance(f.rel.to, (str, unicode)):
+ continue
+ rel_opts = f.rel.to._meta
rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
rel_query_name = f.related_query_name()
for r in rel_opts.fields:
@@ -103,10 +107,14 @@ def get_validation_errors(outfile, app=None):
for i, f in enumerate(opts.many_to_many):
# Check to see if the related m2m field will clash with any
# existing fields, m2m fields, m2m related objects or related objects
- rel_opts = f.rel.to._meta
if f.rel.to not in models.get_models():
- e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, rel_opts.object_name))
+ e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, f.rel.to))
+ # it is a string and we could not find the model it refers to
+ # so skip the next section
+ if isinstance(f.rel.to, (str, unicode)):
+ continue
+ rel_opts = f.rel.to._meta
rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
rel_query_name = f.related_query_name()
# If rel_name is none, there is no reverse accessor.
diff --git a/django/core/serializers/pyyaml.py b/django/core/serializers/pyyaml.py
index 92159dbbe3..772f34a2e3 100644
--- a/django/core/serializers/pyyaml.py
+++ b/django/core/serializers/pyyaml.py
@@ -5,6 +5,7 @@ Requires PyYaml (http://pyyaml.org/), but that's checked for in __init__.
"""
import datetime
+from django.db import models
from django.core.serializers.python import Serializer as PythonSerializer
from django.core.serializers.python import Deserializer as PythonDeserializer
try:
@@ -17,10 +18,23 @@ class Serializer(PythonSerializer):
"""
Convert a queryset to YAML.
"""
+
+ def handle_field(self, obj, field):
+ # A nasty special case: base YAML doesn't support serialization of time
+ # types (as opposed to dates or datetimes, which it does support). Since
+ # we want to use the "safe" serializer for better interoperability, we
+ # need to do something with those pesky times. Converting 'em to strings
+ # isn't perfect, but it's better than a "!!python/time" type which would
+ # halt deserialization under any other language.
+ if isinstance(field, models.TimeField) and getattr(obj, field.name) is not None:
+ self._current[field.name] = str(getattr(obj, field.name))
+ else:
+ super(Serializer, self).handle_field(obj, field)
+
def end_serialization(self):
self.options.pop('stream', None)
self.options.pop('fields', None)
- yaml.dump(self.objects, self.stream, **self.options)
+ yaml.safe_dump(self.objects, self.stream, **self.options)
def getvalue(self):
return self.stream.getvalue()
diff --git a/django/db/__init__.py b/django/db/__init__.py
index d4ea1403bd..8f75e0d7b8 100644
--- a/django/db/__init__.py
+++ b/django/db/__init__.py
@@ -34,8 +34,8 @@ except ImportError, e:
raise # If there's some other error, this must be an error in Django itself.
def _import_database_module(import_path='', module_name=''):
- """Lazyily import a database module when requested."""
- return __import__('%s%s.%s' % (_import_path, settings.DATABASE_ENGINE, module_name), {}, {}, [''])
+ """Lazily import a database module when requested."""
+ return __import__('%s%s.%s' % (import_path, settings.DATABASE_ENGINE, module_name), {}, {}, [''])
# We don't want to import the introspect/creation modules unless
# someone asks for 'em, so lazily load them on demmand.
diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
index fb842950b1..cd830413fc 100644
--- a/django/db/backends/oracle/base.py
+++ b/django/db/backends/oracle/base.py
@@ -47,7 +47,8 @@ class DatabaseOperations(BaseDatabaseOperations):
BEGIN
SELECT %(sq_name)s.nextval
INTO :new.%(col_name)s FROM dual;
- END;/""" % locals()
+ END;
+ /""" % locals()
return sequence_sql, trigger_sql
def date_extract_sql(self, lookup_type, field_name):
@@ -445,24 +446,31 @@ class FormatStylePlaceholderCursor(Database.Cursor):
charset = 'utf-8'
def _format_params(self, params):
- sz_kwargs = {}
if isinstance(params, dict):
result = {}
charset = self.charset
for key, value in params.items():
result[smart_str(key, charset)] = smart_str(value, charset)
- if hasattr(value, 'oracle_type'): sz_kwargs[key] = value.oracle_type()
+ return result
else:
- result = {}
- for i in xrange(len(params)):
- key = 'arg%d' % i
- result[key] = smart_str(params[i], self.charset, True)
- if hasattr(params[i], 'oracle_type'): sz_kwargs[key] = params[i].oracle_type()
+ return tuple([smart_str(p, self.charset, True) for p in params])
- # If any of the parameters had an `oracle_type` method, then we set
- # the inputsizes for those parameters using the returned type
- if sz_kwargs: self.setinputsizes(**sz_kwargs)
- return result
+ def _guess_input_sizes(self, params_list):
+ # Mark any string parameter greater than 4000 characters as an NCLOB.
+ if isinstance(params_list[0], dict):
+ sizes = {}
+ iterators = [params.iteritems() for params in params_list]
+ else:
+ sizes = [None] * len(params_list[0])
+ iterators = [enumerate(params) for params in params_list]
+ for iterator in iterators:
+ for key, value in iterator:
+ if isinstance(value, basestring) and len(value) > 4000:
+ sizes[key] = Database.NCLOB
+ if isinstance(sizes, dict):
+ self.setinputsizes(**sizes)
+ else:
+ self.setinputsizes(*sizes)
def execute(self, query, params=None):
if params is None:
@@ -477,11 +485,12 @@ class FormatStylePlaceholderCursor(Database.Cursor):
if query.endswith(';') or query.endswith('/'):
query = query[:-1]
query = smart_str(query, self.charset) % tuple(args)
+ self._guess_input_sizes([params])
return Database.Cursor.execute(self, query, params)
def executemany(self, query, params=None):
try:
- args = [(':arg%d' % i) for i in range(len(params[0]))]
+ args = [(':arg%d' % i) for i in range(len(params[0]))]
except (IndexError, TypeError):
# No params given, nothing to do
return None
@@ -493,6 +502,7 @@ class FormatStylePlaceholderCursor(Database.Cursor):
query = query[:-1]
query = smart_str(query, self.charset) % tuple(args)
new_param_list = [self._format_params(i) for i in params]
+ self._guess_input_sizes(new_param_list)
return Database.Cursor.executemany(self, query, new_param_list)
def fetchone(self):
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index d7b3558344..6b5233e8de 100644
--- a/django/db/backends/postgresql_psycopg2/base.py
+++ b/django/db/backends/postgresql_psycopg2/base.py
@@ -6,6 +6,7 @@ Requires psycopg 2: http://initd.org/projects/psycopg2
from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures
from django.db.backends.postgresql.operations import DatabaseOperations as PostgresqlDatabaseOperations
+from django.utils.safestring import SafeUnicode
try:
import psycopg2 as Database
import psycopg2.extensions
@@ -17,6 +18,7 @@ DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
+psycopg2.extensions.register_adapter(SafeUnicode, psycopg2.extensions.QuotedString)
class DatabaseFeatures(BaseDatabaseFeatures):
needs_datetime_string_cast = False
diff --git a/django/db/models/base.py b/django/db/models/base.py
index bc90e8d543..e19223051d 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -1,7 +1,7 @@
import django.db.models.manipulators
import django.db.models.manager
from django.core import validators
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist
from django.db.models.fields.related import OneToOneRel, ManyToOneRel
from django.db.models.query import delete_objects
@@ -35,6 +35,8 @@ class ModelBase(type):
new_class = type.__new__(cls, name, bases, {'__module__': attrs.pop('__module__')})
new_class.add_to_class('_meta', Options(attrs.pop('Meta', None)))
new_class.add_to_class('DoesNotExist', types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {}))
+ new_class.add_to_class('MultipleObjectsReturned',
+ types.ClassType('MultipleObjectsReturned', (MultipleObjectsReturned, ), {}))
# Build complete list of parents
for base in bases:
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 20aa56d408..61ccc2d85d 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -104,7 +104,7 @@ class Field(object):
self.radio_admin = radio_admin
self.help_text = help_text
self.db_column = db_column
- self.db_tablespace = db_tablespace
+ self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
self.db_index = db_index
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 788d1c80de..37ace0a7c1 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -29,7 +29,7 @@ class Options(object):
self.object_name, self.app_label = None, None
self.get_latest_by = None
self.order_with_respect_to = None
- self.db_tablespace = None
+ self.db_tablespace = settings.DEFAULT_TABLESPACE
self.admin = None
self.meta = meta
self.pk = None
@@ -152,7 +152,7 @@ class Options(object):
rel_objs = []
for klass in get_models():
for f in klass._meta.fields:
- if f.rel and self == f.rel.to._meta:
+ if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
rel_objs.append(RelatedObject(f.rel.to, klass, f))
self._all_related_objects = rel_objs
return rel_objs
@@ -186,7 +186,7 @@ class Options(object):
rel_objs = []
for klass in get_models():
for f in klass._meta.many_to_many:
- if f.rel and self == f.rel.to._meta:
+ if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
rel_objs.append(RelatedObject(f.rel.to, klass, f))
if app_cache_ready():
self._all_related_many_to_many_objects = rel_objs
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 4d0d295e97..0bc36f425a 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -261,7 +261,8 @@ class _QuerySet(object):
obj_list = list(clone)
if len(obj_list) < 1:
raise self.model.DoesNotExist, "%s matching query does not exist." % self.model._meta.object_name
- assert len(obj_list) == 1, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs)
+ elif len(obj_list) > 1:
+ raise self.model.MultipleObjectsReturned, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs)
return obj_list[0]
def create(self, **kwargs):
diff --git a/django/http/__init__.py b/django/http/__init__.py
index 47f9736ce2..13cc8cea0d 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -331,7 +331,7 @@ class HttpResponse(object):
chunk = self._iterator.next()
if isinstance(chunk, unicode):
chunk = chunk.encode(self._charset)
- return chunk
+ return str(chunk)
def close(self):
if hasattr(self._container, 'close'):
diff --git a/django/http/utils.py b/django/http/utils.py
index d08a9e0237..f98ca93a37 100644
--- a/django/http/utils.py
+++ b/django/http/utils.py
@@ -5,7 +5,7 @@ Functions that modify an HTTP request or response in some way.
# This group of functions are run as part of the response handling, after
# everything else, including all response middleware. Think of them as
# "compulsory response middleware". Be careful about what goes here, because
-# it's a little fiddly to override this behaviour, so they should be truly
+# it's a little fiddly to override this behavior, so they should be truly
# universally applicable.
def fix_location_header(request, response):
@@ -13,7 +13,7 @@ def fix_location_header(request, response):
Ensures that we always use an absolute URI in any location header in the
response. This is required by RFC 2616, section 14.30.
- Code constructing response objects is free to insert relative paths and
+ Code constructing response objects is free to insert relative paths, as
this function converts them to absolute paths.
"""
if 'Location' in response and request.get_host():
@@ -31,4 +31,3 @@ def conditional_content_removal(request, response):
if request.method == 'HEAD':
response.content = ''
return response
-
diff --git a/django/middleware/common.py b/django/middleware/common.py
index 10a3a71b8d..3d57fa4367 100644
--- a/django/middleware/common.py
+++ b/django/middleware/common.py
@@ -5,6 +5,7 @@ from django.conf import settings
from django import http
from django.core.mail import mail_managers
from django.utils.http import urlquote
+from django.core import urlresolvers
class CommonMiddleware(object):
"""
@@ -16,6 +17,12 @@ class CommonMiddleware(object):
this middleware appends missing slashes and/or prepends missing
"www."s.
+ - If APPEND_SLASH is set and the initial URL doesn't end with a
+ slash, and it is not found in urlpatterns, a new URL is formed by
+ appending a slash at the end. If this new URL is found in
+ urlpatterns, then an HTTP-redirect is returned to this new URL;
+ otherwise the initial URL is processed as usual.
+
- ETags: If the USE_ETAGS setting is set, ETags will be calculated from
the entire page content and Not Modified responses will be returned
appropriately.
@@ -33,27 +40,48 @@ class CommonMiddleware(object):
if user_agent_regex.search(request.META['HTTP_USER_AGENT']):
return http.HttpResponseForbidden('<h1>Forbidden</h1>')
- # Check for a redirect based on settings.APPEND_SLASH and settings.PREPEND_WWW
+ # Check for a redirect based on settings.APPEND_SLASH
+ # and settings.PREPEND_WWW
host = request.get_host()
old_url = [host, request.path]
new_url = old_url[:]
- if settings.PREPEND_WWW and old_url[0] and not old_url[0].startswith('www.'):
+
+ if (settings.PREPEND_WWW and old_url[0] and
+ not old_url[0].startswith('www.')):
new_url[0] = 'www.' + old_url[0]
- # Append a slash if append_slash is set and the URL doesn't have a
- # trailing slash or a file extension.
- if settings.APPEND_SLASH and (not old_url[1].endswith('/')) and ('.' not in old_url[1].split('/')[-1]):
- new_url[1] = new_url[1] + '/'
- if settings.DEBUG and request.method == 'POST':
- raise RuntimeError, "You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SLASH set. Django can't redirect to the slash URL while maintaining POST data. Change your form to point to %s%s (note the trailing slash), or set APPEND_SLASH=False in your Django settings." % (new_url[0], new_url[1])
+
+ # Append a slash if APPEND_SLASH is set and the URL doesn't have a
+ # trailing slash and there is no pattern for the current path
+ if settings.APPEND_SLASH and (not old_url[1].endswith('/')):
+ try:
+ urlresolvers.resolve(request.path)
+ except urlresolvers.Resolver404:
+ new_url[1] = new_url[1] + '/'
+ if settings.DEBUG and request.method == 'POST':
+ raise RuntimeError, (""
+ "You called this URL via POST, but the URL doesn't end "
+ "in a slash and you have APPEND_SLASH set. Django can't "
+ "redirect to the slash URL while maintaining POST data. "
+ "Change your form to point to %s%s (note the trailing "
+ "slash), or set APPEND_SLASH=False in your Django "
+ "settings.") % (new_url[0], new_url[1])
+
if new_url != old_url:
- # Redirect
- if new_url[0]:
- newurl = "%s://%s%s" % (request.is_secure() and 'https' or 'http', new_url[0], urlquote(new_url[1]))
+ # Redirect if the target url exists
+ try:
+ urlresolvers.resolve(new_url[1])
+ except urlresolvers.Resolver404:
+ pass
else:
- newurl = urlquote(new_url[1])
- if request.GET:
- newurl += '?' + request.GET.urlencode()
- return http.HttpResponsePermanentRedirect(newurl)
+ if new_url[0]:
+ newurl = "%s://%s%s" % (
+ request.is_secure() and 'https' or 'http',
+ new_url[0], urlquote(new_url[1]))
+ else:
+ newurl = urlquote(new_url[1])
+ if request.GET:
+ newurl += '?' + request.GET.urlencode()
+ return http.HttpResponsePermanentRedirect(newurl)
return None
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index 4a54a98d61..3b8f4195b0 100644
--- a/django/newforms/fields.py
+++ b/django/newforms/fields.py
@@ -83,21 +83,15 @@ class Field(object):
self.creation_counter = Field.creation_counter
Field.creation_counter += 1
- self.error_messages = self._build_error_messages(error_messages)
-
- def _build_error_messages(self, extra_error_messages):
- error_messages = {}
-
- def get_default_error_messages(klass):
+ def set_class_error_messages(messages, klass):
for base_class in klass.__bases__:
- get_default_error_messages(base_class)
- if hasattr(klass, 'default_error_messages'):
- error_messages.update(klass.default_error_messages)
+ set_class_error_messages(messages, base_class)
+ messages.update(getattr(klass, 'default_error_messages', {}))
- get_default_error_messages(self.__class__)
- if extra_error_messages:
- error_messages.update(extra_error_messages)
- return error_messages
+ messages = {}
+ set_class_error_messages(messages, self.__class__)
+ messages.update(error_messages or {})
+ self.error_messages = messages
def clean(self, value):
"""
@@ -415,7 +409,7 @@ class EmailField(RegexField):
try:
from django.conf import settings
URL_VALIDATOR_USER_AGENT = settings.URL_VALIDATOR_USER_AGENT
-except (ImportError, EnvironmentError):
+except ImportError:
# It's OK if Django settings aren't configured.
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)'
@@ -539,8 +533,8 @@ class BooleanField(Field):
"""Returns a Python boolean object."""
super(BooleanField, self).clean(value)
# Explicitly check for the string 'False', which is what a hidden field
- # will submit for False (since bool("True") == True we don't need to
- # handle that explicitly).
+ # will submit for False. Because bool("True") == True, we don't need to
+ # handle that explicitly.
if value == 'False':
return False
return bool(value)
diff --git a/django/newforms/models.py b/django/newforms/models.py
index 51ed16ff7f..17fd1cf2e2 100644
--- a/django/newforms/models.py
+++ b/django/newforms/models.py
@@ -3,16 +3,20 @@ Helper functions for creating Form classes from Django models
and database field objects.
"""
+from warnings import warn
+
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode
from django.utils.datastructures import SortedDict
+from django.core.exceptions import ImproperlyConfigured
-from util import ValidationError
+from util import ValidationError, ErrorList
from forms import BaseForm
from fields import Field, ChoiceField, EMPTY_VALUES
from widgets import Select, SelectMultiple, MultipleHiddenInput
__all__ = (
+ 'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
'ModelChoiceField', 'ModelMultipleChoiceField'
)
@@ -80,6 +84,9 @@ def form_for_model(model, form=BaseForm, fields=None,
determining the formfield for a given database field. It's a callable that
takes a database Field instance and returns a form Field instance.
"""
+ warn("form_for_model is deprecated, use ModelForm instead.",
+ PendingDeprecationWarning,
+ stacklevel=3)
opts = model._meta
field_list = []
for f in opts.fields + opts.many_to_many:
@@ -107,6 +114,9 @@ def form_for_instance(instance, form=BaseForm, fields=None,
takes a database Field instance, plus **kwargs, and returns a form Field
instance with the given kwargs (i.e. 'initial').
"""
+ warn("form_for_instance is deprecated, use ModelForm instead.",
+ PendingDeprecationWarning,
+ stacklevel=3)
model = instance.__class__
opts = model._meta
field_list = []
@@ -132,6 +142,160 @@ def form_for_fields(field_list):
for f in field_list if f.editable])
return type('FormForFields', (BaseForm,), {'base_fields': fields})
+
+# ModelForms #################################################################
+
+def model_to_dict(instance, fields=None, exclude=None):
+ """
+ Returns a dict containing the data in ``instance`` suitable for passing as
+ a Form's ``initial`` keyword argument.
+
+ ``fields`` is an optional list of field names. If provided, only the named
+ fields will be included in the returned dict.
+
+ ``exclude`` is an optional list of field names. If provided, the named
+ fields will be excluded from the returned dict, even if they are listed in
+ the ``fields`` argument.
+ """
+ # avoid a circular import
+ from django.db.models.fields.related import ManyToManyField
+ opts = instance._meta
+ data = {}
+ for f in opts.fields + opts.many_to_many:
+ if not f.editable:
+ continue
+ if fields and not f.name in fields:
+ continue
+ if exclude and f.name in exclude:
+ continue
+ if isinstance(f, ManyToManyField):
+ # If the object doesn't have a primry key yet, just use an empty
+ # list for its m2m fields. Calling f.value_from_object will raise
+ # an exception.
+ if instance.pk is None:
+ data[f.name] = []
+ else:
+ # MultipleChoiceWidget needs a list of pks, not object instances.
+ data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
+ else:
+ data[f.name] = f.value_from_object(instance)
+ return data
+
+def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()):
+ """
+ Returns a ``SortedDict`` containing form fields for the given model.
+
+ ``fields`` is an optional list of field names. If provided, only the named
+ fields will be included in the returned fields.
+
+ ``exclude`` is an optional list of field names. If provided, the named
+ fields will be excluded from the returned fields, even if they are listed
+ in the ``fields`` argument.
+ """
+ # TODO: if fields is provided, it would be nice to return fields in that order
+ field_list = []
+ opts = model._meta
+ for f in opts.fields + opts.many_to_many:
+ if not f.editable:
+ continue
+ if fields and not f.name in fields:
+ continue
+ if exclude and f.name in exclude:
+ continue
+ formfield = formfield_callback(f)
+ if formfield:
+ field_list.append((f.name, formfield))
+ return SortedDict(field_list)
+
+class ModelFormOptions(object):
+ def __init__(self, options=None):
+ self.model = getattr(options, 'model', None)
+ self.fields = getattr(options, 'fields', None)
+ self.exclude = getattr(options, 'exclude', None)
+
+class ModelFormMetaclass(type):
+ def __new__(cls, name, bases, attrs):
+ # TODO: no way to specify formfield_callback yet, do we need one, or
+ # should it be a special case for the admin?
+ fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
+ fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
+
+ # If this class is subclassing another Form, add that Form's fields.
+ # Note that we loop over the bases in *reverse*. This is necessary in
+ # order to preserve the correct order of fields.
+ for base in bases[::-1]:
+ if hasattr(base, 'base_fields'):
+ fields = base.base_fields.items() + fields
+ declared_fields = SortedDict(fields)
+
+ opts = ModelFormOptions(attrs.get('Meta', None))
+ attrs['_meta'] = opts
+
+ # Don't allow more than one Meta model defenition in bases. The fields
+ # would be generated correctly, but the save method won't deal with
+ # more than one object.
+ base_models = []
+ for base in bases:
+ base_opts = getattr(base, '_meta', None)
+ base_model = getattr(base_opts, 'model', None)
+ if base_model is not None:
+ base_models.append(base_model)
+ if len(base_models) > 1:
+ raise ImproperlyConfigured("%s's base classes define more than one model." % name)
+
+ # If a model is defined, extract form fields from it and add them to base_fields
+ if attrs['_meta'].model is not None:
+ # Don't allow a subclass to define a different Meta model than a
+ # parent class has. Technically the right fields would be generated,
+ # but the save method will not deal with more than one model.
+ for base in bases:
+ base_opts = getattr(base, '_meta', None)
+ base_model = getattr(base_opts, 'model', None)
+ if base_model and base_model is not opts.model:
+ raise ImproperlyConfigured('%s defines a different model than its parent.' % name)
+ model_fields = fields_for_model(opts.model, opts.fields, opts.exclude)
+ # fields declared in base classes override fields from the model
+ model_fields.update(declared_fields)
+ attrs['base_fields'] = model_fields
+ else:
+ attrs['base_fields'] = declared_fields
+ return type.__new__(cls, name, bases, attrs)
+
+class BaseModelForm(BaseForm):
+ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
+ initial=None, error_class=ErrorList, label_suffix=':', instance=None):
+ opts = self._meta
+ if instance is None:
+ # if we didn't get an instance, instantiate a new one
+ self.instance = opts.model()
+ object_data = {}
+ else:
+ self.instance = instance
+ object_data = model_to_dict(instance, opts.fields, opts.exclude)
+ # if initial was provided, it should override the values from instance
+ if initial is not None:
+ object_data.update(initial)
+ BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
+
+ def save(self, commit=True):
+ """
+ Saves this ``form``'s cleaned_data into model instance ``self.instance``.
+
+ If commit=True, then the changes to ``instance`` will be saved to the
+ database. Returns ``instance``.
+ """
+ if self.instance.pk is None:
+ fail_message = 'created'
+ else:
+ fail_message = 'changed'
+ return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
+
+class ModelForm(BaseModelForm):
+ __metaclass__ = ModelFormMetaclass
+
+
+# Fields #####################################################################
+
class QuerySetIterator(object):
def __init__(self, queryset, empty_label, cache_choices):
self.queryset = queryset
@@ -142,7 +306,7 @@ class QuerySetIterator(object):
if self.empty_label is not None:
yield (u"", self.empty_label)
for obj in self.queryset:
- yield (obj._get_pk_val(), smart_unicode(obj))
+ yield (obj.pk, smart_unicode(obj))
# Clear the QuerySet cache if required.
if not self.cache_choices:
self.queryset._result_cache = None
diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py
index 611b997736..1cece7c7c0 100644
--- a/django/shortcuts/__init__.py
+++ b/django/shortcuts/__init__.py
@@ -38,7 +38,7 @@ def get_object_or_404(klass, *args, **kwargs):
klass may be a Model, Manager, or QuerySet object. All other passed
arguments and keyword arguments are used in the get() query.
- Note: Like with get(), an AssertionError will be raised if more than one
+ Note: Like with get(), an MultipleObjectsReturned will be raised if more than one
object is found.
"""
queryset = _get_queryset(klass)
diff --git a/django/template/context.py b/django/template/context.py
index 017d2d84b1..0e41a26618 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -23,12 +23,14 @@ class Context(object):
yield d
def push(self):
- self.dicts = [{}] + self.dicts
+ d = {}
+ self.dicts = [d] + self.dicts
+ return d
def pop(self):
if len(self.dicts) == 1:
raise ContextPopException
- del self.dicts[0]
+ return self.dicts.pop(0)
def __setitem__(self, key, value):
"Set a variable in the current context"
@@ -62,6 +64,7 @@ class Context(object):
def update(self, other_dict):
"Like dict.update(). Pushes an entire dictionary's keys and values onto the context."
self.dicts = [other_dict] + self.dicts
+ return other_dict
# This is a function rather than module-level procedural code because we only
# want it to execute if somebody uses RequestContext.
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index f2be64ef1d..2f211dadb5 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -43,7 +43,11 @@ def stringfilter(func):
def addslashes(value):
- """Adds slashes - useful for passing strings to JavaScript, for example."""
+ """
+ Adds slashes before quotes. Useful for escaping strings in CSV, for
+ example. Less useful for escaping JavaScript; use the ``escapejs``
+ filter instead.
+ """
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
addslashes.is_safe = True
addslashes = stringfilter(addslashes)
@@ -54,6 +58,25 @@ def capfirst(value):
capfirst.is_safe=True
capfirst = stringfilter(capfirst)
+_js_escapes = (
+ ('\\', '\\\\'),
+ ('"', '\\"'),
+ ("'", "\\'"),
+ ('\n', '\\n'),
+ ('\r', '\\r'),
+ ('\b', '\\b'),
+ ('\f', '\\f'),
+ ('\t', '\\t'),
+ ('\v', '\\v'),
+ ('</', '<\\/'),
+)
+def escapejs(value):
+ """Backslash-escapes characters for use in JavaScript strings."""
+ for bad, good in _js_escapes:
+ value = value.replace(bad, good)
+ return value
+escapejs = stringfilter(escapejs)
+
def fix_ampersands(value):
"""Replaces ampersands with ``&amp;`` entities."""
from django.utils.html import fix_ampersands
@@ -696,7 +719,7 @@ filesizeformat.is_safe = True
def pluralize(value, arg=u's'):
"""
- Returns a plural suffix if the value is not 1. By default, 's' is used as
+ Returns a plural suffix if the value is not 1. By default, 's' is used as
the suffix:
* If value is 0, vote{{ value|pluralize }} displays "0 votes".
@@ -766,6 +789,7 @@ register.filter(dictsort)
register.filter(dictsortreversed)
register.filter(divisibleby)
register.filter(escape)
+register.filter(escapejs)
register.filter(filesizeformat)
register.filter(first)
register.filter(fix_ampersands)
diff --git a/django/test/_doctest.py b/django/test/_doctest.py
index 3589e16225..d1308b855e 100644
--- a/django/test/_doctest.py
+++ b/django/test/_doctest.py
@@ -354,8 +354,19 @@ class _OutputRedirectingPdb(pdb.Pdb):
"""
def __init__(self, out):
self.__out = out
+ self.__debugger_used = False
pdb.Pdb.__init__(self)
+ def set_trace(self):
+ self.__debugger_used = True
+ pdb.Pdb.set_trace(self)
+
+ def set_continue(self):
+ # Calling set_continue unconditionally would break unit test coverage
+ # reporting, as Bdb.set_continue calls sys.settrace(None).
+ if self.__debugger_used:
+ pdb.Pdb.set_continue(self)
+
def trace_dispatch(self, *args):
# Redirect stdout to the given stream.
save_stdout = sys.stdout
diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py
index ee156b11d0..25e9421575 100644
--- a/django/utils/datastructures.py
+++ b/django/utils/datastructures.py
@@ -1,6 +1,6 @@
class MergeDict(object):
"""
- A simple class for creating new "virtual" dictionaries that actualy look
+ A simple class for creating new "virtual" dictionaries that actually look
up values in more than one dictionary, passed in the constructor.
"""
def __init__(self, *dicts):
@@ -215,7 +215,7 @@ class MultiValueDict(dict):
def get(self, key, default=None):
"""
- Returns the last data value for the passed key. If key doesn't exist
+ Returns the last data value for the passed key. If key doesn't exist
or value is an empty list, then default is returned.
"""
try:
@@ -228,7 +228,7 @@ class MultiValueDict(dict):
def getlist(self, key):
"""
- Returns the list of values for the passed key. If key doesn't exist,
+ Returns the list of values for the passed key. If key doesn't exist,
then an empty list is returned.
"""
try:
diff --git a/django/utils/html.py b/django/utils/html.py
index cb786db1e4..33e2ee3856 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -1,4 +1,4 @@
-"HTML utilities suitable for global use."
+"""HTML utilities suitable for global use."""
import re
import string
@@ -8,11 +8,11 @@ from django.utils.encoding import force_unicode
from django.utils.functional import allow_lazy
from django.utils.http import urlquote
-# Configuration for urlize() function
+# Configuration for urlize() function.
LEADING_PUNCTUATION = ['(', '<', '&lt;']
TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '&gt;']
-# list of possible strings used for bullets in bulleted lists
+# List of possible strings used for bullets in bulleted lists.
DOTS = ['&middot;', '*', '\xe2\x80\xa2', '&#149;', '&bull;', '&#8226;']
unencoded_ampersands_re = re.compile(r'&(?!(\w+|#\d+);)')
@@ -28,7 +28,7 @@ trailing_empty_content_re = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\
del x # Temporary variable
def escape(html):
- "Return the given HTML with ampersands, quotes and carets encoded."
+ """Returns the given HTML with ampersands, quotes and carets encoded."""
return mark_safe(force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;'))
escape = allow_lazy(escape, unicode)
@@ -42,7 +42,7 @@ def conditional_escape(html):
return escape(html)
def linebreaks(value, autoescape=False):
- "Converts newlines into <p> and <br />s"
+ """Converts newlines into <p> and <br />s."""
value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
paras = re.split('\n{2,}', value)
if autoescape:
@@ -50,31 +50,31 @@ def linebreaks(value, autoescape=False):
else:
paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
return u'\n\n'.join(paras)
-linebreaks = allow_lazy(linebreaks, unicode)
+linebreaks = allow_lazy(linebreaks, unicode)
def strip_tags(value):
- "Return the given HTML with all tags stripped."
+ """Returns the given HTML with all tags stripped."""
return re.sub(r'<[^>]*?>', '', force_unicode(value))
strip_tags = allow_lazy(strip_tags)
def strip_spaces_between_tags(value):
- "Return the given HTML with spaces between tags removed."
+ """Returns the given HTML with spaces between tags removed."""
return re.sub(r'>\s+<', '><', force_unicode(value))
strip_spaces_between_tags = allow_lazy(strip_spaces_between_tags, unicode)
def strip_entities(value):
- "Return the given HTML with all entities (&something;) stripped."
+ """Returns the given HTML with all entities (&something;) stripped."""
return re.sub(r'&(?:\w+|#\d+);', '', force_unicode(value))
strip_entities = allow_lazy(strip_entities, unicode)
def fix_ampersands(value):
- "Return the given HTML with all unencoded ampersands encoded correctly."
+ """Returns the given HTML with all unencoded ampersands encoded correctly."""
return unencoded_ampersands_re.sub('&amp;', force_unicode(value))
fix_ampersands = allow_lazy(fix_ampersands, unicode)
def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
"""
- Convert any URLs in text into clickable links.
+ Converts any URLs in text into clickable links.
Works on http://, https://, and www. links. Links can have trailing
punctuation (periods, commas, close-parens) and leading punctuation
@@ -100,7 +100,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
if safe_input:
middle = mark_safe(middle)
if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \
- len(middle) > 0 and middle[0] in string.letters + string.digits and \
+ len(middle) > 0 and middle[0] in string.ascii_letters + string.digits and \
(middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
middle = '<a href="http://%s"%s>%s</a>' % (
urlquote(middle, safe='/&=:;#?+'), nofollow_attr,
diff --git a/django/utils/maxlength.py b/django/utils/maxlength.py
index 9216fe1c3a..a616541f85 100644
--- a/django/utils/maxlength.py
+++ b/django/utils/maxlength.py
@@ -1,6 +1,6 @@
"""
Utilities for providing backwards compatibility for the maxlength argument,
-which has been replaced by max_length, see ticket #2101.
+which has been replaced by max_length. See ticket #2101.
"""
from warnings import warn
@@ -15,17 +15,15 @@ def legacy_maxlength(max_length, maxlength):
"""
Consolidates max_length and maxlength, providing backwards compatibilty
for the legacy "maxlength" argument.
+
If one of max_length or maxlength is given, then that value is returned.
- If both are given, a TypeError is raised.
- If maxlength is used at all, a deprecation warning is issued.
+ If both are given, a TypeError is raised. If maxlength is used at all, a
+ deprecation warning is issued.
"""
if maxlength is not None:
- warn("maxlength is deprecated, use max_length instead.",
- PendingDeprecationWarning,
- stacklevel=3)
+ warn("maxlength is deprecated. Use max_length instead.", DeprecationWarning, stacklevel=3)
if max_length is not None:
- raise TypeError("field can not take both the max_length"
- " argument and the legacy maxlength argument.")
+ raise TypeError("Field cannot take both the max_length argument and the legacy maxlength argument.")
max_length = maxlength
return max_length
@@ -33,7 +31,8 @@ def remove_maxlength(func):
"""
A decorator to be used on a class's __init__ that provides backwards
compatibilty for the legacy "maxlength" keyword argument, i.e.
- name = models.CharField(maxlength=20)
+ name = models.CharField(maxlength=20)
+
It does this by changing the passed "maxlength" keyword argument
(if it exists) into a "max_length" keyword argument.
"""
@@ -58,7 +57,6 @@ class LegacyMaxlength(type):
Metaclass for providing backwards compatibility support for the
"maxlength" keyword argument.
"""
-
def __init__(cls, name, bases, attrs):
super(LegacyMaxlength, cls).__init__(name, bases, attrs)
# Decorate the class's __init__ to remove any maxlength keyword.
diff --git a/django/utils/safestring.py b/django/utils/safestring.py
index f6f4c1eb2f..2e31c23676 100644
--- a/django/utils/safestring.py
+++ b/django/utils/safestring.py
@@ -34,16 +34,13 @@ class SafeString(str, SafeData):
Concatenating a safe string with another safe string or safe unicode
object is safe. Otherwise, the result is no longer safe.
"""
+ t = super(SafeString, self).__add__(rhs)
if isinstance(rhs, SafeUnicode):
- return SafeUnicode(self + rhs)
+ return SafeUnicode(t)
elif isinstance(rhs, SafeString):
- return SafeString(self + rhs)
- else:
- return super(SafeString, self).__add__(rhs)
-
- def __str__(self):
- return self
-
+ return SafeString(t)
+ return t
+
def _proxy_method(self, *args, **kwargs):
"""
Wrap a call to a normal unicode method up so that we return safe
@@ -69,11 +66,11 @@ class SafeUnicode(unicode, SafeData):
Concatenating a safe unicode object with another safe string or safe
unicode object is safe. Otherwise, the result is no longer safe.
"""
+ t = super(SafeUnicode, self).__add__(rhs)
if isinstance(rhs, SafeData):
- return SafeUnicode(self + rhs)
- else:
- return super(SafeUnicode, self).__add__(rhs)
-
+ return SafeUnicode(t)
+ return t
+
def _proxy_method(self, *args, **kwargs):
"""
Wrap a call to a normal unicode method up so that we return safe
diff --git a/django/views/debug.py b/django/views/debug.py
index 3358d2f08e..18a396d3a6 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -1,9 +1,12 @@
+import os
+import re
+import sys
+
from django.conf import settings
from django.template import Template, Context, TemplateDoesNotExist
from django.utils.html import escape
from django.http import HttpResponseServerError, HttpResponseNotFound
from django.utils.encoding import smart_unicode
-import os, re, sys
HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
@@ -131,7 +134,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
if start is not None and end is not None:
unicode_str = exc_value.args[1]
unicode_hint = smart_unicode(unicode_str[max(start-5, 0):min(end+5, len(unicode_str))], 'ascii', errors='replace')
-
+ from django import get_version
t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
c = Context({
'exception_type': exc_type.__name__,
@@ -142,8 +145,10 @@ def technical_500_response(request, exc_type, exc_value, tb):
'request': request,
'request_protocol': request.is_secure() and "https" or "http",
'settings': get_safe_settings(),
- 'sys_executable' : sys.executable,
- 'sys_version_info' : '%d.%d.%d' % sys.version_info[0:3],
+ 'sys_executable': sys.executable,
+ 'sys_version_info': '%d.%d.%d' % sys.version_info[0:3],
+ 'django_version_info': get_version(),
+ 'sys_path' : sys.path,
'template_info': template_info,
'template_does_not_exist': template_does_not_exist,
'loader_debug_info': loader_debug_info,
@@ -229,8 +234,8 @@ TECHNICAL_500_TEMPLATE = """
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
- <meta name="robots" content="NONE,NOARCHIVE" />
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <meta name="robots" content="NONE,NOARCHIVE">
<title>{{ exception_type }} at {{ request.path|escape }}</title>
<style type="text/css">
html * { padding:0; margin:0; }
@@ -275,6 +280,8 @@ TECHNICAL_500_TEMPLATE = """
#requestinfo h3 { margin-bottom:-1em; }
.error { background: #ffc; }
.specific { color:#cc3300; font-weight:bold; }
+ h2 span.commands { font-size:.7em;}
+ span.commands a:link {color:#5E5694;}
</style>
<script type="text/javascript">
//<!--
@@ -365,6 +372,10 @@ TECHNICAL_500_TEMPLATE = """
<th>Python Version:</th>
<td>{{ sys_version_info }}</td>
</tr>
+ <tr>
+ <th>Python Path:</th>
+ <td>{{ sys_path }}</td>
+ </tr>
</table>
</div>
{% if unicode_hint %}
@@ -409,9 +420,7 @@ TECHNICAL_500_TEMPLATE = """
</div>
{% endif %}
<div id="traceback">
- <h2>Traceback <span>(innermost last)</span></h2>
- <div class="commands"><a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></div>
- <br/>
+ <h2>Traceback <span class="commands"><a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></span></h2>
{% autoescape off %}
<div id="browserTraceback">
<ul class="traceback">
@@ -456,27 +465,51 @@ TECHNICAL_500_TEMPLATE = """
{% endfor %}
</ul>
</div>
+ {% endautoescape %}
+ <form action="http://dpaste.com/" name="pasteform" id="pasteform" method="post">
<div id="pastebinTraceback" class="pastebin">
- <table>
- <tbody>
- <tr>
- <td>
- <code>
-Traceback (most recent call last):<br/>
-{% for frame in frames %}
- File "{{ frame.filename }}" in {{ frame.function }}<br/>
- {% if frame.context_line %}
- &nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
- {% endif %}
-{% endfor %}<br/>
-&nbsp;&nbsp;{{ exception_type }} at {{ request.path|escape }}<br/>
-&nbsp;&nbsp;{{ exception_value|escape }}</code>
- </td>
- </tr>
- </tbody>
- </table>
+ <input type="hidden" name="language" value="PythonConsole">
+ <input type="hidden" name="title" value="{{ exception_type|escape }} at {{ request.path|escape }}">
+ <input type="hidden" name="source" value="Django Dpaste Agent">
+ <input type="hidden" name="poster" value="Django">
+ <textarea name="content" id="traceback_area" cols="140" rows="25">
+Environment:
+
+Request Method: {{ request.META.REQUEST_METHOD }}
+Request URL: {{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path|escape }}
+Django Version: {{ django_version_info }}
+Python Version: {{ sys_version_info }}
+Installed Applications:
+{{ settings.INSTALLED_APPS|pprint }}
+Installed Middleware:
+{{ settings.MIDDLEWARE_CLASSES|pprint }}
+
+{% if template_does_not_exist %}Template Loader Error:
+{% if loader_debug_info %}Django tried loading these templates, in this order:
+{% for loader in loader_debug_info %}Using loader {{ loader.loader }}:
+{% for t in loader.templates %}{{ t.name }} (File {% if t.exists %}exists{% else %}does not exist{% endif %})
+{% endfor %}{% endfor %}
+{% else %}Django couldn't find any templates because your TEMPLATE_LOADERS setting is empty!
+{% endif %}
+{% endif %}{% if template_info %}
+Template error:
+In template {{ template_info.name }}, error at line {{ template_info.line }}
+ {{ template_info.message }}{% for source_line in template_info.source_lines %}{% ifequal source_line.0 template_info.line %}
+ {{ source_line.0 }} : {{ template_info.before }} {{ template_info.during }} {{ template_info.after }}
+{% else %}
+ {{ source_line.0 }} : {{ source_line.1 }}
+{% endifequal %}{% endfor %}{% endif %}
+Traceback:
+{% for frame in frames %}File "{{ frame.filename|escape }}" in {{ frame.function|escape }}
+{% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line|escape }}{% endif %}
+{% endfor %}
+Exception Type: {{ exception_type|escape }} at {{ request.path|escape }}
+Exception Value: {{ exception_value|escape }}
+</textarea>
+ <br><br>
+ <input type="submit" value="Share this traceback on public Web site">
</div>
- {% endautoescape %}
+</form>
</div>
<div id="requestinfo">
@@ -602,9 +635,9 @@ TECHNICAL_404_TEMPLATE = """
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Page not found at {{ request.path|escape }}</title>
- <meta name="robots" content="NONE,NOARCHIVE" />
+ <meta name="robots" content="NONE,NOARCHIVE">
<style type="text/css">
html * { padding:0; margin:0; }
body * { padding:10px 20px; }
diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py
index e6a75e63aa..a745559819 100644
--- a/django/views/generic/date_based.py
+++ b/django/views/generic/date_based.py
@@ -9,7 +9,7 @@ from django.http import Http404, HttpResponse
def archive_index(request, queryset, date_field, num_latest=15,
template_name=None, template_loader=loader,
- extra_context=None, allow_empty=False, context_processors=None,
+ extra_context=None, allow_empty=True, context_processors=None,
mimetype=None, allow_future=False, template_object_name='latest'):
"""
Generic top-level archive of date-based objects.
diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py
index b72173bd76..617a6443a2 100644
--- a/django/views/generic/list_detail.py
+++ b/django/views/generic/list_detail.py
@@ -5,7 +5,7 @@ from django.core.paginator import ObjectPaginator, InvalidPage
from django.core.exceptions import ObjectDoesNotExist
def object_list(request, queryset, paginate_by=None, page=None,
- allow_empty=False, template_name=None, template_loader=loader,
+ allow_empty=True, template_name=None, template_loader=loader,
extra_context=None, context_processors=None, template_object_name='object',
mimetype=None):
"""
diff --git a/docs/add_ons.txt b/docs/add_ons.txt
index 00c6e0dcf4..c6fdb4a3a2 100644
--- a/docs/add_ons.txt
+++ b/docs/add_ons.txt
@@ -58,6 +58,17 @@ See the `csrf documentation`_.
.. _csrf documentation: ../csrf/
+flatpages
+=========
+
+A framework for managing simple "flat" HTML content in a database.
+
+See the `flatpages documentation`_.
+
+.. _flatpages documentation: ../flatpages/
+
+Requires the sites_ contrib package to be installed as well.
+
formtools
=========
@@ -162,17 +173,6 @@ Examples (when 'today' is 17 Feb 2007):
.. _DATE_FORMAT: ../settings/#date_format
-flatpages
-=========
-
-A framework for managing simple "flat" HTML content in a database.
-
-See the `flatpages documentation`_.
-
-.. _flatpages documentation: ../flatpages/
-
-Requires the sites_ contrib package to be installed as well.
-
localflavor
===========
@@ -211,6 +211,15 @@ See the `redirects documentation`_.
.. _redirects documentation: ../redirects/
+sessions
+========
+
+A framework for storing data in anonymous sessions.
+
+See the `sessions documentation`_.
+
+.. _sessions documentation: ../sessions/
+
sites
=====
@@ -240,6 +249,16 @@ See the `syndication documentation`_.
.. _syndication documentation: ../syndication_feeds/
+webdesign
+=========
+
+Helpers and utilties targeted primarily at Web *designers* rather than
+Web *developers*.
+
+See the `Web design helpers documentation`_.
+
+.. _Web design helpers documentation: ../webdesign/
+
Other add-ons
=============
diff --git a/docs/authentication.txt b/docs/authentication.txt
index 2c34c6663d..8f305ba4d1 100644
--- a/docs/authentication.txt
+++ b/docs/authentication.txt
@@ -154,10 +154,13 @@ custom methods:
* ``get_profile()`` -- Returns a site-specific profile for this user.
Raises ``django.contrib.auth.models.SiteProfileNotAvailable`` if the current site
- doesn't allow profiles.
+ doesn't allow profiles. For information on how to define a
+ site-specific user profile, see the section on `storing additional
+ user information`_ below.
.. _Django model: ../model-api/
.. _DEFAULT_FROM_EMAIL: ../settings/#default-from-email
+.. _storing additional user information: #storing-additional-information-about-users
Manager functions
~~~~~~~~~~~~~~~~~
@@ -269,6 +272,45 @@ you need to create a superuser after that via the command line, you can use the
Make sure to substitute ``/path/to/`` with the path to the Django codebase on
your filesystem.
+Storing additional information about users
+------------------------------------------
+
+If you'd like to store additional information related to your users,
+Django provides a method to specify a site-specific related model --
+termed a "user profile" -- for this purpose.
+
+To make use of this feature, define a model with fields for the
+additional information you'd like to store, or additional methods
+you'd like to have available, and also add a ``ForeignKey`` from your
+model to the ``User`` model, specified with ``unique=True`` to ensure
+only one instance of your model can be created for each ``User``.
+
+To indicate that this model is the user profile model for a given
+site, fill in the setting ``AUTH_PROFILE_MODULE`` with a string
+consisting of the following items, separated by a dot:
+
+1. The (normalized to lower-case) name of the application in which the
+ user profile model is defined (in other words, an all-lowercase
+ version of the name which was passed to ``manage.py startapp`` to
+ create the application).
+
+2. The (normalized to lower-case) name of the model class.
+
+For example, if the profile model was a class named ``UserProfile``
+and was defined inside an application named ``accounts``, the
+appropriate setting would be::
+
+ AUTH_PROFILE_MODULE = 'accounts.userprofile'
+
+When a user profile model has been defined and specified in this
+manner, each ``User`` object will have a method -- ``get_profile()``
+-- which returns the instance of the user profile model associated
+with that ``User``.
+
+For more information, see `Chapter 12 of the Django book`_.
+
+.. _Chapter 12 of the Django book: http://www.djangobook.com/en/beta/chapter12/#cn226
+
Authentication in Web requests
==============================
@@ -337,6 +379,17 @@ This example shows how you might use both ``authenticate()`` and ``login()``::
else:
# Return an 'invalid login' error message.
+.. admonition:: Calling ``authenticate()`` first
+
+ When you're manually logging a user in, you *must* call
+ ``authenticate()`` before you call ``login()``. ``authenticate()``
+ sets an attribute on the ``User`` noting which authentication
+ backend successfully authenticated that user (see the `backends
+ documentation`_ for details), and this information is needed later
+ during the login process.
+
+.. _backends documentation: #other-authentication-sources
+
Manually checking a user's password
-----------------------------------
diff --git a/docs/cache.txt b/docs/cache.txt
index af6cb35c42..e7e1cdd791 100644
--- a/docs/cache.txt
+++ b/docs/cache.txt
@@ -168,6 +168,10 @@ development or testing environments. For example::
CACHE_BACKEND = 'simple:///'
+**New in Django development version:** This cache backend is deprecated and
+will be removed in a future release. New code should use the ``locmem`` backend
+instead.
+
Dummy caching (for development)
-------------------------------
diff --git a/docs/contributing.txt b/docs/contributing.txt
index a842f4477a..37c9196467 100644
--- a/docs/contributing.txt
+++ b/docs/contributing.txt
@@ -242,7 +242,7 @@ We've got two roles here:
* Ticket triagers: community members who keep track of tickets, making
sure the tickets are always categorized correctly.
-Second, note the four triage stages:
+Second, note the five triage stages:
1. A ticket starts as "Unreviewed", meaning that a triager has yet to
examine the ticket and move it along.
@@ -254,9 +254,15 @@ Second, note the four triage stages:
3. Once a ticket is ruled to be approved for fixing, it's moved into the
"Accepted" stage. This stage is where all the real work gets done.
- 4. If a ticket has an associated patch (see below), a triager will review the
- patch. If the patch is complete, it'll be marked as "ready for checkin" so
- that a core developer knows to review and check in the patches.
+ 4. In some cases, a ticket might get moved to the "Someday/Maybe" state.
+ This means the ticket is an enhancement request that we might consider
+ adding to the framework if an excellent patch is submitted. These
+ tickets are not a high priority.
+
+ 5. If a ticket has an associated patch (see below), a triager will review
+ the patch. If the patch is complete, it'll be marked as "ready for
+ checkin" so that a core developer knows to review and check in the
+ patches.
The second part of this workflow involves a set of flags the describe what the
ticket has or needs in order to be "ready for checkin":
@@ -654,9 +660,8 @@ To run the tests, ``cd`` to the ``tests/`` directory and type::
./runtests.py --settings=path.to.django.settings
Yes, the unit tests need a settings module, but only for database connection
-info, with the ``DATABASE_ENGINE`` setting. You will also need a ``ROOT_URLCONF``
-setting (its value is ignored; it just needs to be present) and a ``SITE_ID``
-setting (any non-zero integer value will do) in order for all the tests to pass.
+info, with the ``DATABASE_ENGINE`` setting. You'll also need a ``ROOT_URLCONF``
+setting (its value is ignored; it just needs to be present).
If you're using the ``sqlite3`` database backend, no further settings are
needed. A temporary database will be created in memory when running the tests.
diff --git a/docs/custom_model_fields.txt b/docs/custom_model_fields.txt
index 74eb10aa82..80c9de7d16 100644
--- a/docs/custom_model_fields.txt
+++ b/docs/custom_model_fields.txt
@@ -1,5 +1,5 @@
===================
-Custom Model Fields
+Custom model fields
===================
**New in Django development version**
@@ -8,9 +8,10 @@ Introduction
============
The `model reference`_ documentation explains how to use Django's standard
-field classes. For many purposes, those classes are all you'll need. Sometimes,
-though, the Django version won't meet your precise requirements, or you'll want
-to use a field that is entirely different from those shipped with Django.
+field classes -- ``CharField``, ``DateField``, etc. For many purposes, those
+classes are all you'll need. Sometimes, though, the Django version won't meet
+your precise requirements, or you'll want to use a field that is entirely
+different from those shipped with Django.
Django's built-in field types don't cover every possible database column type --
only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure
@@ -27,10 +28,10 @@ Our example object
Creating custom fields requires a bit of attention to detail. To make things
easier to follow, we'll use a consistent example throughout this document.
Suppose you have a Python object representing the deal of cards in a hand of
-Bridge_. It doesn't matter if you don't know how to play Bridge. You only need
-to know that 52 cards are dealt out equally to four players, who are
-traditionally called *north*, *east*, *south* and *west*. Our class looks
-something like this::
+Bridge_. (Don't worry, you don't know how to play Bridge to follow this
+example. You only need to know that 52 cards are dealt out equally to four
+players, who are traditionally called *north*, *east*, *south* and *west*.)
+Our class looks something like this::
class Hand(object):
def __init__(self, north, east, south, west):
@@ -42,10 +43,9 @@ something like this::
# ... (other possibly useful methods omitted) ...
-This is just an ordinary Python class, nothing Django-specific about it. We
-would like to be able to things like this in our models (we assume the
-``hand`` attribute on the model is an instance of ``Hand``)::
-
+This is just an ordinary Python class, with nothing Django-specific about it.
+We'd like to be able to things like this in our models (we assume the ``hand``
+attribute on the model is an instance of ``Hand``)::
example = MyModel.objects.get(pk=1)
print example.hand.north
@@ -72,7 +72,7 @@ model support for existing classes where you cannot change the source code.
.. _PostgreSQL custom types: http://www.postgresql.org/docs/8.2/interactive/sql-createtype.html
.. _Bridge: http://en.wikipedia.org/wiki/Contract_bridge
-Background Theory
+Background theory
=================
Database storage
@@ -87,7 +87,7 @@ that falls out fairly naturally once you have the database side under control).
Fields in a model must somehow be converted to fit into an existing database
column type. Different databases provide different sets of valid column types,
but the rule is still the same: those are the only types you have to work
-with. Anything you want to store in the database must fit into one of
+with. Anything you want to store in the database must fit into one of
those types.
Normally, you're either writing a Django field to match a particular database
@@ -95,10 +95,9 @@ column type, or there's a fairly straightforward way to convert your data to,
say, a string.
For our ``Hand`` example, we could convert the card data to a string of 104
-characters by concatenating all the cards together in a pre-determined order.
-Say, all the *north* cards first, then the *east*, *south* and *west* cards, in
-that order. So ``Hand`` objects can be saved to text or character columns in
-the database.
+characters by concatenating all the cards together in a pre-determined order --
+say, all the *north* cards first, then the *east*, *south* and *west* cards. So
+``Hand`` objects can be saved to text or character columns in the database.
What does a field class do?
---------------------------
@@ -109,12 +108,12 @@ mean model fields and not `form fields`_) are subclasses of
field is common to all fields -- name, help text, validator lists, uniqueness
and so forth. Storing all that information is handled by ``Field``. We'll get
into the precise details of what ``Field`` can do later on; for now, suffice it
-to say that everything descends from ``Field`` and then customises key pieces
-of the class behaviour.
+to say that everything descends from ``Field`` and then customizes key pieces
+of the class behavior.
.. _form fields: ../newforms/#fields
-It's important to realise that a Django field class is not what is stored in
+It's important to realize that a Django field class is not what is stored in
your model attributes. The model attributes contain normal Python objects. The
field classes you define in a model are actually stored in the ``Meta`` class
when the model class is created (the precise details of how this is done are
@@ -127,31 +126,35 @@ Keep this in mind when creating your own custom fields. The Django ``Field``
subclass you write provides the machinery for converting between your Python
instances and the database/serializer values in various ways (there are
differences between storing a value and using a value for lookups, for
-example). If this sounds a bit tricky, don't worry. It will hopefully become
-clearer in the examples below. Just remember that you will often end up
-creating two classes when you want a custom field. The first class is the
-Python object that your users will manipulate. They will assign it to the model
-attribute, they will read from it for displaying purposes, things like that.
-This is the ``Hand`` class in our example. The second class is the ``Field``
-subclass. This is the class that knows how to convert your first class back and
-forth between its permanent storage form and the Python form.
+example). If this sounds a bit tricky, don't worry -- it will become clearer in
+the examples below. Just remember that you will often end up creating two
+classes when you want a custom field:
+
+ * The first class is the Python object that your users will manipulate.
+ They will assign it to the model attribute, they will read from it for
+ displaying purposes, things like that. This is the ``Hand`` class in our
+ example.
+
+ * The second class is the ``Field`` subclass. This is the class that knows
+ how to convert your first class back and forth between its permanent
+ storage form and the Python form.
Writing a ``Field`` subclass
=============================
-When you are planning your ``Field`` subclass, first give some thought to
-which existing field your new field is most similar to. Can you subclass an
-existing Django field and save yourself some work? If not, you should subclass the ``Field`` class, from which everything is descended.
+When planning your ``Field`` subclass, first give some thought to which
+existing ``Field`` class your new field is most similar to. Can you subclass an
+existing Django field and save yourself some work? If not, you should subclass
+the ``Field`` class, from which everything is descended.
-Initialising your new field is a matter of separating out any arguments that
+Initializing your new field is a matter of separating out any arguments that
are specific to your case from the common arguments and passing the latter to
the ``__init__()`` method of ``Field`` (or your parent class).
-In our example, the Django field we create is going to be called
-``HandField``. It's not a bad idea to use a similar naming scheme to Django's
-fields so that our new class is identifiable and yet clearly related to the
-``Hand`` class it is wrapping. It doesn't behave like any existing field, so
-we'll subclass directly from ``Field``::
+In our example, we'll call our field ``HandField``. (It's a good idea to call
+your ``Field`` subclass ``(Something)Field``, so it's easily identifiable as a
+``Field`` subclass.) It doesn't behave like any existing field, so we'll
+subclass directly from ``Field``::
from django.db import models
@@ -160,7 +163,7 @@ we'll subclass directly from ``Field``::
kwargs['max_length'] = 104
super(HandField, self).__init__(*args, **kwargs)
-Our ``HandField`` will accept most of the standard field options (see the list
+Our ``HandField`` accept most of the standard field options (see the list
below), but we ensure it has a fixed length, since it only needs to hold 52
card values plus their suits; 104 characters in total.
@@ -171,40 +174,40 @@ card values plus their suits; 104 characters in total.
(``auto_now`` being set implies ``editable=False``). No error is raised in
this case.
- This behaviour simplifies the field classes, because they don't need to
+ This behavior simplifies the field classes, because they don't need to
check for options that aren't necessary. They just pass all the options to
- the parent class and then don't use them later on. It is up to you whether
+ the parent class and then don't use them later on. It's up to you whether
you want your fields to be more strict about the options they select, or
- to use the simpler, more permissive behaviour of the current fields.
+ to use the simpler, more permissive behavior of the current fields.
The ``Field.__init__()`` method takes the following parameters, in this
order:
- - ``verbose_name``
- - ``name``
- - ``primary_key``
- - ``max_length``
- - ``unique``
- - ``blank``
- - ``null``
- - ``db_index``
- - ``core``
- - ``rel``: Used for related fields (like ``ForeignKey``). For advanced use
+ * ``verbose_name``
+ * ``name``
+ * ``primary_key``
+ * ``max_length``
+ * ``unique``
+ * ``blank``
+ * ``null``
+ * ``db_index``
+ * ``core``
+ * ``rel``: Used for related fields (like ``ForeignKey``). For advanced use
only.
- - ``default``
- - ``editable``
- - ``serialize``: If ``False``, the field will not be serialized when the
+ * ``default``
+ * ``editable``
+ * ``serialize``: If ``False``, the field will not be serialized when the
model is passed to Django's serializers_. Defaults to ``True``.
- - ``prepopulate_from``
- - ``unique_for_date``
- - ``unique_for_month``
- - ``unique_for_year``
- - ``validator_list``
- - ``choices``
- - ``radio_admin``
- - ``help_text``
- - ``db_column``
- - ``db_tablespace``: Currently only used with the Oracle backend and only
+ * ``prepopulate_from``
+ * ``unique_for_date``
+ * ``unique_for_month``
+ * ``unique_for_year``
+ * ``validator_list``
+ * ``choices``
+ * ``radio_admin``
+ * ``help_text``
+ * ``db_column``
+ * ``db_tablespace``: Currently only used with the Oracle backend and only
for index creation. You can usually ignore this option.
All of the options without an explanation in the above list have the same
@@ -218,22 +221,19 @@ The ``SubfieldBase`` metaclass
------------------------------
As we indicated in the introduction_, field subclasses are often needed for
-two reasons. Either to take advantage of a custom database column type, or to
-handle complex Python types. A combination of the two is obviously also
-possible. If you are only working with custom database column types and your
+two reasons: either to take advantage of a custom database column type, or to
+handle complex Python types. Obviously, a combination of the two is also
+possible. If you're only working with custom database column types and your
model fields appear in Python as standard Python types direct from the
database backend, you don't need to worry about this section.
-If you are handling custom Python types, such as our ``Hand`` class, we need
-to make sure that when Django initialises an instance of our model and assigns
-a database value to our custom field attribute we convert that value into the
+If you're handling custom Python types, such as our ``Hand`` class, we need
+to make sure that when Django initializes an instance of our model and assigns
+a database value to our custom field attribute, we convert that value into the
appropriate Python object. The details of how this happens internally are a
-little complex. For the field writer, though, things are fairly simple. Make
-sure your field subclass uses ``django.db.models.SubfieldBase`` as its
-metaclass. This ensures that the ``to_python()`` method, documented below_,
-will always be called when the attribute is initialised.
-
-Our ``HandField`` class now looks like this::
+little complex, but the code you need to write in your ``Field`` class is
+simple: make sure your field subclass uses ``django.db.models.SubfieldBase`` as
+its metaclass::
class HandField(models.Field):
__metaclass__ = models.SubfieldBase
@@ -241,16 +241,18 @@ Our ``HandField`` class now looks like this::
def __init__(self, *args, **kwargs):
# ...
+This ensures that the ``to_python()`` method, documented below_, will always be
+called when the attribute is initialized.
+
.. _below: #to-python-self-value
Useful methods
--------------
-Once you've created your ``Field`` subclass and setup up the
-``__metaclass__``, if necessary, there are a few standard methods you need to
-consider overriding. Which of these you need to implement will depend on you
-particular field behaviour. The list below is in approximately decreasing
-order of importance, so start from the top.
+Once you've created your ``Field`` subclass and set up up the
+``__metaclass__``, you might consider overriding a few standard methods,
+depending on your field's behavior. The list of methods below is in
+approximately decreasing order of importance, so start from the top.
``db_type(self)``
~~~~~~~~~~~~~~~~~
@@ -337,23 +339,32 @@ field. You are then responsible for creating the column in the right table in
some other way, of course, but this gives you a way to tell Django to get out
of the way.
-
``to_python(self, value)``
~~~~~~~~~~~~~~~~~~~~~~~~~~
-Converts between all the ways your field can receive its initial value and the
-Python object you want to end up with. The default version just returns
-``value``, so is useful is the database backend returns the data already in
-the correct form (a Python string, for example).
+Converts a value as returned by your database (or a serializer) to a Python
+object.
+
+The default implementation simply returns ``value``, for the common case in
+which the database backend already returns data in the correct format (as a
+Python string, for example).
+
+If your custom ``Field`` class deals with data structures that are more complex
+than strings, dates, integers or floats, then you'll need to override this
+method. As a general rule, the method should deal gracefully with any of the
+following arguments:
-Normally, you will need to override this method. As a general rule, be
-prepared to accept an instance of the right type (e.g. ``Hand`` in our ongoing
-example), a string (from a deserializer, for example), and whatever the
-database wrapper returns for the column type you are using.
+ * An instance of the correct type (e.g., ``Hand`` in our ongoing example).
-In our ``HandField`` class, we are storing the data in a character field in
-the database, so we need to be able to process strings and ``Hand`` instances
-in ``to_python()``::
+ * A string (e.g., from a deserializer).
+
+ * Whatever the database returns for the column type you're using.
+
+In our ``HandField`` class, we're storing the data as a VARCHAR field in the
+database, so we need to be able to process strings and ``Hand`` instances in
+``to_python()``::
+
+ import re
class HandField(models.Field):
# ...
@@ -362,14 +373,18 @@ in ``to_python()``::
if isinstance(value, Hand):
return value
- # The string case
+ # The string case.
p1 = re.compile('.{26}')
p2 = re.compile('..')
args = [p2.findall(x) for x in p1.findall(value)]
return Hand(*args)
-Notice that we always return a ``Hand`` instance from this method. That is the
-Python object we want to store in the model's attribute.
+Notice that we always return a ``Hand`` instance from this method. That's the
+Python object type we want to store in the model's attribute.
+
+**Remember:** If your custom field needs the ``to_python()`` method to be
+called when it is created, you should be using `The SubfieldBase metaclass`_
+mentioned earlier. Otherwise ``to_python()`` won't be called automatically.
``get_db_prep_save(self, value)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -377,7 +392,7 @@ Python object we want to store in the model's attribute.
This is the reverse of ``to_python()`` when working with the database backends
(as opposed to serialization). The ``value`` parameter is the current value of
the model's attribute (a field has no reference to its containing model, so it
-cannot retrieve the value itself) and the method should return data in a
+cannot retrieve the value itself), and the method should return data in a
format that can be used as a parameter in a query for the database backend.
For example::
@@ -389,7 +404,6 @@ For example::
return ''.join([''.join(l) for l in (self.north,
self.east, self.south, self.west)])
-
``pre_save(self, model_instance, add)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -399,10 +413,10 @@ The attribute name is in ``self.attname`` (this is set up by ``Field``). If
the model is being saved to the database for the first time, the ``add``
parameter will be ``True``, otherwise it will be ``False``.
-Often you won't need to override this method. However, at times it can be very
-useful. For example, the Django ``DateTimeField`` uses this method to set the
-attribute to the correct value before returning it in the cases when
-``auto_now`` or ``auto_now_add`` are set on the field.
+You only need to override this method if you want to preprocess the value
+somehow, just before saving. For example, Django's ``DateTimeField`` uses this
+method to set the attribute correctly in the case of ``auto_now`` or
+``auto_now_add``.
If you do override this method, you must return the value of the attribute at
the end. You should also update the model's attribute if you make any changes
@@ -460,9 +474,9 @@ All of the ``kwargs`` dictionary is passed directly to the form field's
``__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 `forms documentation`_ for information about this. Also
-have a look at ``django.contrib.localflavor`` for some examples of custom
-widgets.
+form widget). See the `forms documentation`_ for information about this, and
+take a look at the code in ``django.contrib.localflavor`` for some examples of
+custom widgets.
Continuing our ongoing example, we can write the ``formfield()`` method as::
@@ -471,14 +485,14 @@ Continuing our ongoing example, we can write the ``formfield()`` method as::
def formfield(self, **kwargs):
# This is a fairly standard way to set up some defaults
- # whilst letting the caller override them.
+ # while letting the caller override them.
defaults = {'form_class': MyFormField}
defaults.update(kwargs)
return super(HandField, self).formfield(**defaults)
-This assumes we have some ``MyFormField`` field class (which has its own
-default widget) imported. This document doesn't cover the details of writing
-custom form fields.
+This assumes we're imported a ``MyFormField`` field class (which has its own
+default widget). This document doesn't cover the details of writing custom form
+fields.
.. _helper functions: ../newforms/#generating-forms-for-models
.. _forms documentation: ../newforms/
@@ -490,7 +504,7 @@ Returns a string giving the name of the ``Field`` subclass we are emulating at
the database level. This is used to determine the type of database column for
simple cases.
-If you have created a ``db_type()`` method, you do not need to worry about
+If you have created a ``db_type()`` method, you don't need to worry about
``get_internal_type()`` -- it won't be used much. Sometimes, though, your
database storage is similar in type to some other field, so you can use that
other field's logic to create the right column.
@@ -512,7 +526,7 @@ the database backend you are using -- that is, it doesn't appear in
be used by the serializer, but the default ``db_type()`` method will return
``None``. See the documentation of ``db_type()`` above_ for reasons why this
might be useful. Putting a descriptive string in as the type of the field for
-the serializer is a useful idea if you are ever going to be using the
+the serializer is a useful idea if you're ever going to be using the
serializer output in some other place, outside of Django.
.. _above: #db-type-self
@@ -528,7 +542,7 @@ serializer output in some other place, outside of Django.
Returns a dictionary, mapping the field's attribute name to a flattened string
version of the data. This method has some internal uses that aren't of
interest to use here (mostly having to do with manipulators). For our
-purposes, it is sufficient to return a one item dictionary that maps the
+purposes, it's sufficient to return a one item dictionary that maps the
attribute name to a string.
This method is used by the serializers to convert the field into a string for
@@ -549,19 +563,20 @@ we can reuse some existing conversion code::
Some general advice
--------------------
-Writing a custom field can be a tricky process sometimes, particularly if you
-are doing complex conversions between your Python types and your database and
-serialization formats. A couple of tips to make things go more smoothly:
-
- 1. Look at the existing Django fields (in
- ``django/db/models/fields/__init__.py``) for inspiration. Try to find a field
- that is already close to what you want and extend it a little bit, in
- preference to creating an entirely new field from scratch.
-
- 2. Put a ``__str__()`` or ``__unicode__()`` method on the class you are
- wrapping up as a field. There are a lot of places where the default behaviour
- of the field code is to call ``force_unicode()`` on the value (in our
- examples in this document, ``value`` would be a ``Hand`` instance, not a
- ``HandField``). So if your ``__unicode__()`` method automatically converts to
- the string form of your Python object, you can save yourself a lot of work.
-
+Writing a custom field can be a tricky process, particularly if you're doing
+complex conversions between your Python types and your database and
+serialization formats. Here are a couple of tips to make things go more
+smoothly:
+
+ 1. Look at the existing Django fields (in
+ ``django/db/models/fields/__init__.py``) for inspiration. Try to find a
+ field that's similar to what you want and extend it a little bit,
+ instead of creating an entirely new field from scratch.
+
+ 2. Put a ``__str__()`` or ``__unicode__()`` method on the class you're
+ wrapping up as a field. There are a lot of places where the default
+ behavior of the field code is to call ``force_unicode()`` on the value.
+ (In our examples in this document, ``value`` would be a ``Hand``
+ instance, not a ``HandField``). So if your ``__unicode__()`` method
+ automatically converts to the string form of your Python object, you can
+ save yourself a lot of work.
diff --git a/docs/databases.txt b/docs/databases.txt
index d4853f75f8..6832c2b361 100644
--- a/docs/databases.txt
+++ b/docs/databases.txt
@@ -258,6 +258,11 @@ many-to-many table would be stored in the ``indexes`` tablespace. The ``data``
field would also generate an index, but no tablespace for it is specified, so
it would be stored in the model tablespace ``tables`` by default.
+**New in the Django development version:** Use the ``DEFAULT_TABLESPACE`` and
+``DEFAULT_INDEX_TABLESPACE`` settings to specify default values for the
+db_tablespace options. These are useful for setting a tablespace for the
+built-in Django apps and other applications whose code you cannot control.
+
Django does not create the tablespaces for you. Please refer to `Oracle's
documentation`_ for details on creating and managing tablespaces.
diff --git a/docs/email.txt b/docs/email.txt
index 55b91b1935..7f2eef6fc7 100644
--- a/docs/email.txt
+++ b/docs/email.txt
@@ -253,9 +253,11 @@ For example::
The class has the following methods:
- * ``send()`` sends the message, using either the connection that is
- specified in the ``connection`` attribute, or creating a new connection
- if none already exists.
+ * ``send(fail_silently=False)`` sends the message, using either
+ the connection that is specified in the ``connection``
+ attribute, or creating a new connection if none already
+ exists. If the keyword argument ``fail_silently`` is ``True``,
+ exceptions raised while sending the message will be quashed.
* ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a
subclass of Python's ``email.MIMEText.MIMEText`` class) or a
diff --git a/docs/form_for_model.txt b/docs/form_for_model.txt
new file mode 100644
index 0000000000..6761c15331
--- /dev/null
+++ b/docs/form_for_model.txt
@@ -0,0 +1,418 @@
+Generating forms for models
+===========================
+
+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``
+model, and you want to create a form that lets people submit comments. In this
+case, it would be redundant to define the field types in your form, because
+you've already defined the fields in your model.
+
+For this reason, Django provides a few helper functions that let you create a
+``Form`` class from a Django model.
+
+``form_for_model()``
+--------------------
+
+The method ``django.newforms.form_for_model()`` creates a form based on the
+definition of a specific model. Pass it the model class, and it will return a
+``Form`` class that contains a form field for each model field.
+
+For example::
+
+ >>> from django.newforms import form_for_model
+
+ # Create the form class.
+ >>> ArticleForm = form_for_model(Article)
+
+ # Create an empty form instance.
+ >>> f = ArticleForm()
+
+It bears repeating that ``form_for_model()`` takes the model *class*, not a
+model instance, and it returns a ``Form`` *class*, not a ``Form`` instance.
+
+Field types
+~~~~~~~~~~~
+
+The generated ``Form`` class will have a form field for every model field. Each
+model field has a corresponding default form field. For example, a
+``CharField`` on a model is represented as a ``CharField`` on a form. A
+model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is
+the full list of conversions:
+
+ =============================== ========================================
+ Model field Form field
+ =============================== ========================================
+ ``AutoField`` Not represented in the form
+ ``BooleanField`` ``BooleanField``
+ ``CharField`` ``CharField`` with ``max_length`` set to
+ the model field's ``max_length``
+ ``CommaSeparatedIntegerField`` ``CharField``
+ ``DateField`` ``DateField``
+ ``DateTimeField`` ``DateTimeField``
+ ``DecimalField`` ``DecimalField``
+ ``EmailField`` ``EmailField``
+ ``FileField`` ``FileField``
+ ``FilePathField`` ``CharField``
+ ``FloatField`` ``FloatField``
+ ``ForeignKey`` ``ModelChoiceField`` (see below)
+ ``ImageField`` ``ImageField``
+ ``IntegerField`` ``IntegerField``
+ ``IPAddressField`` ``IPAddressField``
+ ``ManyToManyField`` ``ModelMultipleChoiceField`` (see
+ below)
+ ``NullBooleanField`` ``CharField``
+ ``PhoneNumberField`` ``USPhoneNumberField``
+ (from ``django.contrib.localflavor.us``)
+ ``PositiveIntegerField`` ``IntegerField``
+ ``PositiveSmallIntegerField`` ``IntegerField``
+ ``SlugField`` ``CharField``
+ ``SmallIntegerField`` ``IntegerField``
+ ``TextField`` ``CharField`` with ``widget=Textarea``
+ ``TimeField`` ``TimeField``
+ ``URLField`` ``URLField`` with ``verify_exists`` set
+ to the model field's ``verify_exists``
+ ``USStateField`` ``CharField`` with
+ ``widget=USStateSelect``
+ (``USStateSelect`` is from
+ ``django.contrib.localflavor.us``)
+ ``XMLField`` ``CharField`` with ``widget=Textarea``
+ =============================== ========================================
+
+
+.. note::
+ The ``FloatField`` form field and ``DecimalField`` model and form fields
+ are new in the development version.
+
+As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
+types are special cases:
+
+ * ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``,
+ which is a ``ChoiceField`` whose choices are a model ``QuerySet``.
+
+ * ``ManyToManyField`` is represented by
+ ``django.newforms.ModelMultipleChoiceField``, which is a
+ ``MultipleChoiceField`` whose choices are a model ``QuerySet``.
+
+In addition, each generated form field has attributes set as follows:
+
+ * If the model field has ``blank=True``, then ``required`` is set to
+ ``False`` on the form field. Otherwise, ``required=True``.
+
+ * The form field's ``label`` is set to the ``verbose_name`` of the model
+ field, with the first character capitalized.
+
+ * The form field's ``help_text`` is set to the ``help_text`` of the model
+ field.
+
+ * If the model field has ``choices`` set, then the form field's ``widget``
+ will be set to ``Select``, with choices coming from the model field's
+ ``choices``. The choices will normally include the blank choice which is
+ selected by default. If the field is required, this forces the user to
+ make a selection. The blank choice will not be included if the model
+ field has ``blank=False`` and an explicit ``default`` value (the
+ ``default`` value will be initially selected instead).
+
+Finally, note that you can override the form field used for a given model
+field. See "Overriding the default field types" below.
+
+A full example
+~~~~~~~~~~~~~~
+
+Consider this set of models::
+
+ from django.db import models
+
+ TITLE_CHOICES = (
+ ('MR', 'Mr.'),
+ ('MRS', 'Mrs.'),
+ ('MS', 'Ms.'),
+ )
+
+ class Author(models.Model):
+ name = models.CharField(max_length=100)
+ title = models.CharField(max_length=3, choices=TITLE_CHOICES)
+ birth_date = models.DateField(blank=True, null=True)
+
+ def __unicode__(self):
+ return self.name
+
+ class Book(models.Model):
+ name = models.CharField(max_length=100)
+ authors = models.ManyToManyField(Author)
+
+With these models, a call to ``form_for_model(Author)`` would return a ``Form``
+class equivalent to this::
+
+ class AuthorForm(forms.Form):
+ name = forms.CharField(max_length=100)
+ title = forms.CharField(max_length=3,
+ widget=forms.Select(choices=TITLE_CHOICES))
+ birth_date = forms.DateField(required=False)
+
+A call to ``form_for_model(Book)`` would return a ``Form`` class equivalent to
+this::
+
+ class BookForm(forms.Form):
+ name = forms.CharField(max_length=100)
+ authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
+
+The ``save()`` method
+~~~~~~~~~~~~~~~~~~~~~
+
+Every form produced by ``form_for_model()`` also has a ``save()`` method. This
+method creates and saves a database object from the data bound to the form. For
+example::
+
+ # Create a form instance from POST data.
+ >>> f = ArticleForm(request.POST)
+
+ # Save a new Article object from the form's data.
+ >>> new_article = f.save()
+
+Note that ``save()`` will raise a ``ValueError`` if the data in the form
+doesn't validate -- i.e., ``if form.errors``.
+
+This ``save()`` method accepts an optional ``commit`` keyword argument, which
+accepts either ``True`` or ``False``. If you call ``save()`` with
+``commit=False``, then it will return an object that hasn't yet been saved to
+the database. In this case, it's up to you to call ``save()`` on the resulting
+model instance. This is useful if you want to do custom processing on the
+object before saving it. ``commit`` is ``True`` by default.
+
+Another side effect of using ``commit=False`` is seen when your model has
+a many-to-many relation with another model. If your model has a many-to-many
+relation and you specify ``commit=False`` when you save a form, Django cannot
+immediately save the form data for the many-to-many relation. This is because
+it isn't possible to save many-to-many data for an instance until the instance
+exists in the database.
+
+To work around this problem, every time you save a form using ``commit=False``,
+Django adds a ``save_m2m()`` method to the form created by ``form_for_model``.
+After you've manually saved the instance produced by the form, you can invoke
+``save_m2m()`` to save the many-to-many form data. For example::
+
+ # Create a form instance with POST data.
+ >>> f = AuthorForm(request.POST)
+
+ # Create, but don't save the new author instance.
+ >>> new_author = f.save(commit=False)
+
+ # Modify the author in some way.
+ >>> new_author.some_field = 'some_value'
+
+ # Save the new instance.
+ >>> new_author.save()
+
+ # Now, save the many-to-many data for the form.
+ >>> f.save_m2m()
+
+Calling ``save_m2m()`` is only required if you use ``save(commit=False)``.
+When you use a simple ``save()`` on a form, all data -- including
+many-to-many data -- is saved without the need for any additional method calls.
+For example::
+
+ # Create a form instance with POST data.
+ >>> f = AuthorForm(request.POST)
+
+ # Create and save the new author instance. There's no need to do anything else.
+ >>> new_author = f.save()
+
+Using an alternate base class
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you want to add custom methods to the form generated by
+``form_for_model()``, write a class that extends ``django.newforms.BaseForm``
+and contains your custom methods. Then, use the ``form`` argument to
+``form_for_model()`` to tell it to use your custom form as its base class.
+For example::
+
+ # Create the new base class.
+ >>> class MyBase(BaseForm):
+ ... def my_method(self):
+ ... # Do whatever the method does
+
+ # Create the form class with a different base class.
+ >>> ArticleForm = form_for_model(Article, form=MyBase)
+
+ # Instantiate the form.
+ >>> f = ArticleForm()
+
+ # Use the base class method.
+ >>> f.my_method()
+
+Using a subset of fields on the form
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**New in Django development version**
+
+In some cases, you may not want all the model fields to appear on the generated
+form. There are two ways of telling ``form_for_model()`` to use only a subset
+of the model fields:
+
+ 1. Set ``editable=False`` on the model field. As a result, *any* form
+ created from the model via ``form_for_model()`` will not include that
+ field.
+
+ 2. Use the ``fields`` argument to ``form_for_model()``. This argument, if
+ given, should be a list of field names to include in the form.
+
+ For example, if you want a form for the ``Author`` model (defined above)
+ that includes only the ``name`` and ``title`` fields, you would specify
+ ``fields`` like this::
+
+ PartialArticleForm = form_for_model(Author, fields=('name', 'title'))
+
+.. note::
+
+ If you specify ``fields`` when creating a form with ``form_for_model()``,
+ then the fields that are *not* specified will not be set by the form's
+ ``save()`` method. Django will prevent any attempt to save an incomplete
+ model, so if the model does not allow the missing fields to be empty, and
+ does not provide a default value for the missing fields, any attempt to
+ ``save()`` a ``form_for_model`` with missing fields will fail. To avoid
+ this failure, you must use ``save(commit=False)`` and manually set any
+ extra required fields::
+
+ instance = form.save(commit=False)
+ instance.required_field = 'new value'
+ instance.save()
+
+ See the `section on saving forms`_ for more details on using
+ ``save(commit=False)``.
+
+.. _section on saving forms: `The save() method`_
+
+Overriding the default field types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The default field types, as described in the "Field types" table above, are
+sensible defaults; if you have a ``DateField`` in your model, chances are you'd
+want that to be represented as a ``DateField`` in your form. But
+``form_for_model()`` gives you the flexibility of changing the form field type
+for a given model field. You do this by specifying a **formfield callback**.
+
+A formfield callback is a function that, when provided with a model field,
+returns a form field instance. When constructing a form, ``form_for_model()``
+asks the formfield callback to provide form field types.
+
+By default, ``form_for_model()`` calls the ``formfield()`` method on the model
+field::
+
+ def default_callback(field, **kwargs):
+ return field.formfield(**kwargs)
+
+The ``kwargs`` are any keyword arguments that might be passed to the form
+field, such as ``required=True`` or ``label='Foo'``.
+
+For example, if you wanted to use ``MyDateFormField`` for any ``DateField``
+field on the model, you could define the callback::
+
+ >>> def my_callback(field, **kwargs):
+ ... if isinstance(field, models.DateField):
+ ... return MyDateFormField(**kwargs)
+ ... else:
+ ... return field.formfield(**kwargs)
+
+ >>> ArticleForm = form_for_model(Article, formfield_callback=my_callback)
+
+Note that your callback needs to handle *all* possible model field types, not
+just the ones that you want to behave differently to the default. That's why
+this example has an ``else`` clause that implements the default behavior.
+
+.. warning::
+ The field that is passed into the ``formfield_callback`` function in
+ ``form_for_model()`` and ``form_for_instance`` is the field instance from
+ your model's class. You **must not** alter that object at all; treat it
+ as read-only!
+
+ If you make any alterations to that object, it will affect any future
+ users of the model class, because you will have changed the field object
+ used to construct the class. This is almost certainly what you don't want
+ to have happen.
+
+Finding the model associated with a form
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The model class that was used to construct the form is available
+using the ``_model`` property of the generated form::
+
+ >>> ArticleForm = form_for_model(Article)
+ >>> ArticleForm._model
+ <class 'myapp.models.Article'>
+
+``form_for_instance()``
+-----------------------
+
+``form_for_instance()`` is like ``form_for_model()``, but it takes a model
+instance instead of a model class::
+
+ # Create an Author.
+ >>> a = Author(name='Joe Smith', title='MR', birth_date=None)
+ >>> a.save()
+
+ # Create a form for this particular Author.
+ >>> AuthorForm = form_for_instance(a)
+
+ # Instantiate the form.
+ >>> f = AuthorForm()
+
+When a form created by ``form_for_instance()`` is created, the initial data
+values for the form fields are drawn from the instance. However, this data is
+not bound to the form. You will need to bind data to the form before the form
+can be saved.
+
+Unlike ``form_for_model()``, a choice field in form created by
+``form_for_instance()`` will not include the blank choice if the respective
+model field has ``blank=False``. The initial choice is drawn from the instance.
+
+When you call ``save()`` on a form created by ``form_for_instance()``,
+the database instance will be updated. As in ``form_for_model()``, ``save()``
+will raise ``ValueError`` if the data doesn't validate.
+
+``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback``
+arguments that behave the same way as they do for ``form_for_model()``.
+
+Let's modify the earlier `contact form`_ view example a little bit. Suppose we
+have a ``Message`` model that holds each contact submission. Something like::
+
+ class Message(models.Model):
+ subject = models.CharField(max_length=100)
+ message = models.TextField()
+ sender = models.EmailField()
+ cc_myself = models.BooleanField(required=False)
+
+You could use this model to create a form (using ``form_for_model()``). You
+could also use existing ``Message`` instances to create a form for editing
+messages. The `simple example view`_ can be changed slightly to accept the ``id`` value
+of an existing ``Message`` and present it for editing::
+
+ def contact_edit(request, msg_id):
+ # Create the form from the message id.
+ message = get_object_or_404(Message, id=msg_id)
+ ContactForm = form_for_instance(message)
+
+ if request.method == 'POST':
+ form = ContactForm(request.POST)
+ if form.is_valid():
+ form.save()
+ return HttpResponseRedirect('/url/on_success/')
+ else:
+ form = ContactForm()
+ return render_to_response('contact.html', {'form': form})
+
+Aside from how we create the ``ContactForm`` class here, the main point to
+note is that the form display in the ``GET`` branch of the function
+will use the values from the ``message`` instance as initial values for the
+form field.
+
+.. _contact form: ../newforms/#simple-view-example
+.. _`simple example view`: ../newforms/#simple-view-example
+
+When should you use ``form_for_model()`` and ``form_for_instance()``?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``form_for_model()`` and ``form_for_instance()`` functions are meant to be
+shortcuts for the common case. If you want to create a form whose fields map to
+more than one model, or a form that contains fields that *aren't* on a model,
+you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way
+isn't that difficult, after all.
diff --git a/docs/generic_views.txt b/docs/generic_views.txt
index 08ff01c372..17187894c0 100644
--- a/docs/generic_views.txt
+++ b/docs/generic_views.txt
@@ -188,7 +188,7 @@ a date in the *future* are not included unless you set ``allow_future`` to
* ``allow_empty``: A boolean specifying whether to display the page if no
objects are available. If this is ``False`` and no objects are available,
the view will raise a 404 instead of displaying an empty page. By
- default, this is ``False``.
+ default, this is ``True``.
* ``context_processors``: A list of template-context processors to apply to
the view's template. See the `RequestContext docs`_.
@@ -718,7 +718,7 @@ A page representing a list of objects.
* ``allow_empty``: A boolean specifying whether to display the page if no
objects are available. If this is ``False`` and no objects are available,
the view will raise a 404 instead of displaying an empty page. By
- default, this is ``False``.
+ default, this is ``True``.
* ``context_processors``: A list of template-context processors to apply to
the view's template. See the `RequestContext docs`_.
diff --git a/docs/install.txt b/docs/install.txt
index 08444d3a1a..331a5ea8e3 100644
--- a/docs/install.txt
+++ b/docs/install.txt
@@ -73,13 +73,15 @@ installed.
If you plan to use Django's ``manage.py syncdb`` command to
automatically create database tables for your models, you'll need to
-ensure that Django has permission to create tables in the database
-you're using; if you plan to manually create the tables, you can
-simply grant Django ``SELECT``, ``INSERT``, ``UPDATE`` and ``DELETE``
-permissions. Django does not issue ``ALTER TABLE`` statements, and so
-will not require permission to do so. If you will be using Django's
-`testing framework`_ with data fixtures, Django will need permission
-to create a temporary test database.
+ensure that Django has permission to create and alter tables in the
+database you're using; if you plan to manually create the tables, you
+can simply grant Django ``SELECT``, ``INSERT``, ``UPDATE`` and
+``DELETE`` permissions. On some databases, Django will need
+``ALTER TABLE`` privileges during ``syncdb`` but won't issue
+``ALTER TABLE`` statements on a table once ``syncdb`` has created it.
+
+If you're using Django's `testing framework`_ to test database queries,
+Django will need permission to create a test database.
.. _PostgreSQL: http://www.postgresql.org/
.. _MySQL: http://www.mysql.com/
diff --git a/docs/localflavor.txt b/docs/localflavor.txt
new file mode 100644
index 0000000000..3eecba57e7
--- /dev/null
+++ b/docs/localflavor.txt
@@ -0,0 +1,654 @@
+==========================
+The "local flavor" add-ons
+==========================
+
+Django comes with assorted pieces of code that are useful only for a particular
+country or culture. These pieces of code are organized as a set of
+subpackages, named using `ISO 3166 country codes`_.
+
+.. _ISO 3166 country codes: http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm
+
+Most of the ``localflavor`` add-ons are localized form components deriving from
+the newforms_ framework. To use one of these localized components, just import
+the relevant subpackage. For example, a form with a field for French telephone
+numbers is created like so::
+
+ from django import newforms as forms
+ from django.contrib.localflavor import fr
+
+ class MyForm(forms.Form):
+ my_french_phone_no = fr.forms.FRPhoneNumberField()
+
+Countries currently supported by ``localflavor`` are:
+
+* Argentina_
+* Australia_
+* Brazil_
+* Canada_
+* Chile_
+* Finland_
+* France_
+* Germany_
+* Holland_
+* Iceland_
+* India_
+* Italy_
+* Japan_
+* Mexico_
+* Norway_
+* Peru_
+* Poland_
+* Slovakia_
+* `South Africa`_
+* Spain_
+* Switzerland_
+* `United Kingdom`_
+* `United States of America`_
+
+.. _Argentina: `Argentina (django.contrib.localflavor.ar)`_
+.. _Australia: `Australia (django.contrib.localflavor.au)`_
+.. _Brazil: `Brazil (django.contrib.localflavor.br)`_
+.. _Canada: `Canada (django.contrib.localflavor.ca)`_
+.. _Chile: `Chile (django.contrib.localflavor.cl)`_
+.. _Finland: `Finland (django.contrib.localflavor.fi)`_
+.. _France: `France (django.contrib.localflavor.fr)`_
+.. _Germany: `Germany (django.contrib.localflavor.de)`_
+.. _Holland: `Holland (django.contrib.localflavor.nl)`_
+.. _Iceland: `Iceland (django.contrib.localflavor.is\_)`_
+.. _India: `India (django.contrib.localflavor.in\_)`_
+.. _Italy: `Italy (django.contrib.localflavor.it)`_
+.. _Japan: `Japan (django.contrib.localflavor.jp)`_
+.. _Mexico: `Mexico (django.contrib.localflavor.mx)`_
+.. _Norway: `Norway (django.contrib.localflavor.no)`_
+.. _Peru: `Peru (django.contrib.localflavor.pe)`_
+.. _Poland: `Poland (django.contrib.localflavor.pl)`_
+.. _Slovakia: `Slovakia (django.contrib.localflavor.sk)`_
+.. _South Africa: `South Africa (django.contrib.localflavor.za)`_
+.. _Spain: `Spain (django.contrib.localflavor.es)`_
+.. _Switzerland: `Switzerland (django.contrib.localflavor.ch)`_
+.. _United Kingdom: `United Kingdom (django.contrib.localflavor.uk)`_
+.. _United States of America: `United States of America (django.contrib.localflavor.us)`_
+
+The ``localflavor`` add-on also includes the ``generic`` subpackage, containing
+useful code that is not specific to one particular country or culture.
+Currently, it defines date and date & time input fields based on those from
+newforms_, but with non-US default formats. Here's an example of how to use
+them::
+
+ from django import newforms as forms
+ from django.contrib.localflavor import generic
+
+ class MyForm(forms.Form):
+ my_date_field = generic.forms.DateField()
+
+.. _newforms: ../newforms/
+
+
+.. admonition:: Adding a Flavor
+
+ We'd love to add more of these to Django, so please create a ticket for
+ anything that you've found useful. Please use unicode objects
+ (``u'mystring'``) for strings, rather than setting the encoding in the file
+ (see any of the existing flavors for examples).
+
+
+Argentina (``django.contrib.localflavor.ar``)
+=============================================
+
+ARPostalCodeField
+-----------------
+
+A form field that validates input as either a classic four-digit Argentinian
+postal code or a CPA_.
+
+.. _CPA: http://www.correoargentino.com.ar/consulta_cpa/home.php
+
+ARProvinceSelect
+----------------
+
+A ``Select`` widget that uses a list of Argentina's provinces as its choices.
+
+
+Australia (``django.contrib.localflavor.au``)
+=============================================
+
+AUPostCodeField
+---------------
+
+A form field that validates input as an Australian postcode.
+
+AUPhoneNumberField
+------------------
+
+A form field that validates input as an Australian phone number. Valid numbers
+have ten digits.
+
+AUStateSelect
+-------------
+
+A ``Select`` widget that uses a list of Australian states/territories as its
+choices.
+
+
+Brazil (``django.contrib.localflavor.br``)
+==========================================
+
+BRPhoneNumberField
+------------------
+
+A form field that validates input as a Brazilian phone number, with the format
+XX-XXXX-XXXX.
+
+BRZipCodeField
+--------------
+
+A form field that validates input as a Brazilian zip code, with the format
+XXXXX-XXX.
+
+BRStateSelect
+-------------
+
+A ``Select`` widget that uses a list of Brazilian states/territories as its
+choices.
+
+
+Canada (``django.contrib.localflavor.ca``)
+==========================================
+
+CAPhoneNumberField
+------------------
+
+A form field that validates input as a Canadian phone number, with the format
+XXX-XXX-XXXX.
+
+CAPostalCodeField
+-----------------
+
+A form field that validates input as a Canadian postal code, with the format
+XXX XXX.
+
+CAProvinceField
+---------------
+
+A form field that validates input as a Canadian province name or abbreviation.
+
+CASocialInsuranceNumberField
+----------------------------
+
+A form field that validates input as a Canadian Social Insurance Number (SIN).
+A valid number must have the format XXX-XXX-XXXX and pass a `Luhn mod-10
+checksum`_.
+
+.. _Luhn mod-10 checksum: http://en.wikipedia.org/wiki/Luhn_algorithm
+
+CAProvinceSelect
+----------------
+
+A ``Select`` widget that uses a list of Canadian provinces and territories as
+its choices.
+
+
+Chile (``django.contrib.localflavor.cl``)
+=========================================
+
+CLRutField
+----------
+
+A form field that validates input as a Chilean national identification number
+('Rol Unico Tributario' or RUT). The valid format is XX.XXX.XXX-X.
+
+CLRegionSelect
+--------------
+
+A ``Select`` widget that uses a list of Chilean regions (Regiones) as its
+choices.
+
+
+Finland (``django.contrib.localflavor.fi``)
+===========================================
+
+FISocialSecurityNumber
+----------------------
+
+A form field that validates input as a Finnish social security number.
+
+FIZipCodeField
+--------------
+
+A form field that validates input as a Finnish zip code. Valid codes
+consist of five digits.
+
+FIMunicipalitySelect
+--------------------
+
+A ``Select`` widget that uses a list of Finnish municipalities as its
+choices.
+
+
+France (``django.contrib.localflavor.fr``)
+==========================================
+
+FRPhoneNumberField
+------------------
+
+A form field that validates input as a French local phone number. The
+correct format is 0X XX XX XX XX. 0X.XX.XX.XX.XX and 0XXXXXXXXX validate
+but are corrected to 0X XX XX XX XX.
+
+FRZipCodeField
+--------------
+
+A form field that validates input as a French zip code. Valid codes
+consist of five digits.
+
+FRDepartmentSelect
+------------------
+
+A ``Select`` widget that uses a list of French departments as its choices.
+
+
+Germany (``django.contrib.localflavor.de``)
+===========================================
+
+DEIdentityCardNumberField
+-------------------------
+
+A form field that validates input as a German identity card number
+(Personalausweis_). Valid numbers have the format
+XXXXXXXXXXX-XXXXXXX-XXXXXXX-X, with no group consisting entirely of zeroes.
+
+.. _Personalausweis: http://de.wikipedia.org/wiki/Personalausweis
+
+DEZipCodeField
+--------------
+
+A form field that validates input as a German zip code. Valid codes
+consist of five digits.
+
+DEStateSelect
+-------------
+
+A ``Select`` widget that uses a list of German states as its choices.
+
+
+Holland (``django.contrib.localflavor.nl``)
+===========================================
+
+NLPhoneNumberField
+------------------
+
+A form field that validates input as a Dutch telephone number.
+
+NLSofiNumberField
+-----------------
+
+A form field that validates input as a Dutch social security number
+(SoFI/BSN).
+
+NLZipCodeField
+--------------
+
+A form field that validates input as a Dutch zip code.
+
+NLProvinceSelect
+----------------
+
+A ``Select`` widget that uses a list of Dutch provinces as its list of
+choices.
+
+
+Iceland (``django.contrib.localflavor.is_``)
+============================================
+
+ISIdNumberField
+---------------
+
+A form field that validates input as an Icelandic identification number
+(kennitala). The format is XXXXXX-XXXX.
+
+ISPhoneNumberField
+------------------
+
+A form field that validates input as an Icelandtic phone number (seven
+digits with an optional hyphen or space after the first three digits).
+
+ISPostalCodeSelect
+------------------
+
+A ``Select`` widget that uses a list of Icelandic postal codes as its
+choices.
+
+
+India (``django.contrib.localflavor.in_``)
+==========================================
+
+INStateField
+------------
+
+A form field that validates input as an Indian state/territory name or
+abbreviation. Input is normalized to the standard two-letter vehicle
+registration abbreviation for the given state or territory.
+
+INZipCodeField
+--------------
+
+A form field that validates input as an Indian zip code, with the
+format XXXXXXX.
+
+INStateSelect
+-------------
+
+A ``Select`` widget that uses a list of Indian states/territories as its
+choices.
+
+
+Italy (``django.contrib.localflavor.it``)
+=========================================
+
+ITSocialSecurityNumberField
+---------------------------
+
+A form field that validates input as an Italian social security number
+(`codice fiscale`_).
+
+.. _codice fiscale: http://www.agenziaentrate.it/ilwwcm/connect/Nsi/Servizi/Codice+fiscale+-+tessera+sanitaria/Codice+fiscale/NSI+Informazioni+sulla+codificazione+delle+persone+fisiche
+
+ITVatNumberField
+----------------
+
+A form field that validates Italian VAT numbers (partita IVA).
+
+ITZipCodeField
+--------------
+
+A form field that validates input as an Italian zip code. Valid codes
+must have five digits.
+
+ITProvinceSelect
+----------------
+
+A ``Select`` widget that uses a list of Italian provinces as its choices.
+
+ITRegionSelect
+--------------
+
+A ``Select`` widget that uses a list of Italian regions as its choices.
+
+
+Japan (``django.contrib.localflavor.jp``)
+=========================================
+
+JPPostalCodeField
+-----------------
+
+A form field that validates input as a Japanese postcode.
+It accepts seven digits, with or without a hyphen.
+
+JPPrefectureSelect
+------------------
+
+A ``Select`` widget that uses a list of Japanese prefectures as its choices.
+
+
+Mexico (``django.contrib.localflavor.mx``)
+==========================================
+
+MXStateSelect
+-------------
+
+A ``Select`` widget that uses a list of Mexican states as its choices.
+
+
+Norway (``django.contrib.localflavor.no``)
+==========================================
+
+NOSocialSecurityNumber
+----------------------
+
+A form field that validates input as a Norwegian social security number
+(personnummer_).
+
+.. _personnummer: http://no.wikipedia.org/wiki/Personnummer
+
+NOZipCodeField
+--------------
+
+A form field that validates input as a Norwegian zip code. Valid codes
+have four digits.
+
+NOMunicipalitySelect
+--------------------
+
+A ``Select`` widget that uses a list of Norwegian municipalities (fylker) as
+its choices.
+
+
+Peru (``django.contrib.localflavor.pe``)
+========================================
+
+PEDNIField
+----------
+
+A form field that validates input as a DNI (Peruvian national identity)
+number.
+
+PERUCField
+----------
+
+A form field that validates input as an RUC (Registro Unico de
+Contribuyentes) number. Valid RUC numbers have eleven digits.
+
+PEDepartmentSelect
+------------------
+
+A ``Select`` widget that uses a list of Peruvian Departments as its choices.
+
+
+Poland (``django.contrib.localflavor.pl``)
+==========================================
+
+PLNationalIdentificationNumberField
+-----------------------------------
+
+A form field that validates input as a Polish national identification number
+(PESEL_).
+
+.. _PESEL: http://en.wikipedia.org/wiki/PESEL
+
+PLNationalBusinessRegisterField
+-------------------------------
+
+A form field that validates input as a Polish National Official Business
+Register Number (REGON_), having either seven or nine digits. The checksum
+algorithm used for REGONs is documented at
+http://wipos.p.lodz.pl/zylla/ut/nip-rego.html.
+
+.. _REGON: http://www.stat.gov.pl/bip/regon_ENG_HTML.htm
+
+PLPostalCodeField
+-----------------
+
+A form field that validates input as a Polish postal code. The valid format
+is XX-XXX, where X is a digit.
+
+PLTaxNumberField
+----------------
+
+A form field that validates input as a Polish Tax Number (NIP). Valid
+formats are XXX-XXX-XX-XX or XX-XX-XXX-XXX. The checksum algorithm used
+for NIPs is documented at http://wipos.p.lodz.pl/zylla/ut/nip-rego.html.
+
+PLAdministrativeUnitSelect
+--------------------------
+
+A ``Select`` widget that uses a list of Polish administrative units as its
+choices.
+
+PLVoivodeshipSelect
+-------------------
+
+A ``Select`` widget that uses a list of Polish voivodeships (administrative
+provinces) as its choices.
+
+
+Slovakia (``django.contrib.localflavor.sk``)
+============================================
+
+SKPostalCodeField
+-----------------
+
+A form field that validates input as a Slovak postal code. Valid formats
+are XXXXX or XXX XX, where X is a digit.
+
+SKDistrictSelect
+----------------
+
+A ``Select`` widget that uses a list of Slovak districts as its choices.
+
+SKRegionSelect
+--------------
+
+A ``Select`` widget that uses a list of Slovak regions as its choices.
+
+
+South Africa (``django.contrib.localflavor.za``)
+================================================
+
+ZAIDField
+---------
+
+A form field that validates input as a South African ID number. Validation
+uses the Luhn checksum and a simplistic (i.e., not entirely accurate) check
+for birth date.
+
+ZAPostCodeField
+---------------
+
+A form field that validates input as a South African postcode. Valid
+postcodes must have four digits.
+
+
+Spain (``django.contrib.localflavor.es``)
+=========================================
+
+ESIdentityCardNumberField
+-------------------------
+
+A form field that validates input as a Spanish NIF/NIE/CIF (Fiscal
+Identification Number) code.
+
+ESCCCField
+----------
+
+A form field that validates input as a Spanish bank account number (Codigo
+Cuenta Cliente or CCC). A valid CCC number has the format
+EEEE-OOOO-CC-AAAAAAAAAA, where the E, O, C and A digits denote the entity,
+office, checksum and account, respectively. The first checksum digit
+validates the entity and office. The second checksum digit validates the
+account. It is also valid to use a space as a delimiter, or to use no
+delimiter.
+
+ESPhoneNumberField
+------------------
+
+A form field that validates input as a Spanish phone number. Valid numbers
+have nine digits, the first of which is 6, 8 or 9.
+
+ESPostalCodeField
+-----------------
+
+A form field that validates input as a Spanish postal code. Valid codes
+have five digits, the first two being in the range 01 to 52, representing
+the province.
+
+ESProvinceSelect
+----------------
+
+A ``Select`` widget that uses a list of Spanish provinces as its choices.
+
+ESRegionSelect
+--------------
+
+A ``Select`` widget that uses a list of Spanish regions as its choices.
+
+
+Switzerland (``django.contrib.localflavor.ch``)
+===============================================
+
+CHIdentityCardNumberField
+-------------------------
+
+A form field that validates input as a Swiss identity card number.
+A valid number must confirm to the X1234567<0 or 1234567890 format and
+have the correct checksums -- see http://adi.kousz.ch/artikel/IDCHE.htm.
+
+CHPhoneNumberField
+------------------
+
+A form field that validates input as a Swiss phone number. The correct
+format is 0XX XXX XX XX. 0XX.XXX.XX.XX and 0XXXXXXXXX validate but are
+corrected to 0XX XXX XX XX.
+
+CHZipCodeField
+--------------
+
+A form field that validates input as a Swiss zip code. Valid codes
+consist of four digits.
+
+CHStateSelect
+-------------
+
+A ``Select`` widget that uses a list of Swiss states as its choices.
+
+
+United Kingdom (``django.contrib.localflavor.uk``)
+==================================================
+
+UKPostcodeField
+---------------
+
+A form field that validates input as a UK postcode. The regular
+expression used is sourced from the schema for British Standard BS7666
+address types at http://www.govtalk.gov.uk/gdsc/schemas/bs7666-v2-0.xsd.
+
+
+United States of America (``django.contrib.localflavor.us``)
+============================================================
+
+USPhoneNumberField
+------------------
+
+A form field that validates input as a U.S. phone number.
+
+USSocialSecurityNumberField
+---------------------------
+
+A form field that validates input as a U.S. Social Security Number (SSN).
+A valid SSN must obey the following rules:
+
+* Format of XXX-XX-XXXX
+* No group of digits consisting entirely of zeroes
+* Leading group of digits cannot be 666
+* Number not in promotional block 987-65-4320 through 987-65-4329
+* Number not one known to be invalid due to widespread promotional
+ use or distribution (e.g., the Woolworth's number or the 1962
+ promotional number)
+
+USStateField
+------------
+
+A form field that validates input as a U.S. state name or abbreviation. It
+normalizes the input to the standard two-letter postal service abbreviation
+for the given state.
+
+USZipCodeField
+--------------
+
+A form field that validates input as a U.S. zip code. Valid formats are
+XXXXX or XXXXX-XXXX.
+
+USStateSelect
+-------------
+
+A form Select widget that uses a list of U.S. states/territories as its
+choices.
diff --git a/docs/middleware.txt b/docs/middleware.txt
index 41b1a96b89..39019caf1e 100644
--- a/docs/middleware.txt
+++ b/docs/middleware.txt
@@ -58,11 +58,20 @@ Adds a few conveniences for perfectionists:
which should be a list of strings.
* Performs URL rewriting based on the ``APPEND_SLASH`` and ``PREPEND_WWW``
- settings. If ``APPEND_SLASH`` is ``True``, URLs that lack a trailing
- slash will be redirected to the same URL with a trailing slash, unless the
- last component in the path contains a period. So ``foo.com/bar`` is
- redirected to ``foo.com/bar/``, but ``foo.com/bar/file.txt`` is passed
- through unchanged.
+ settings.
+
+ If ``APPEND_SLASH`` is ``True`` and the initial URL doesn't end with a slash,
+ and it is not found in urlpatterns, a new URL is formed by appending a slash
+ at the end. If this new URL is found in urlpatterns, then an HTTP-redirect is
+ returned to this new URL; otherwise the initial URL is processed as usual.
+
+ So ``foo.com/bar`` will be redirected to ``foo.com/bar/`` if you do not
+ have a valid urlpattern for ``foo.com/bar``, and do have a valid urlpattern
+ for ``foo.com/bar/``.
+
+ **New in Django development version:** The behavior of ``APPEND_SLASH`` has
+ changed slightly in the development version. It didn't used to check to see
+ whether the pattern was matched in the URLconf.
If ``PREPEND_WWW`` is ``True``, URLs that lack a leading "www." will be
redirected to the same URL with a leading "www."
diff --git a/docs/model-api.txt b/docs/model-api.txt
index f39b711e84..3f908ec158 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -618,8 +618,9 @@ statement for this field.
**New in Django development version**
The name of the database tablespace to use for this field's index, if
-indeed this field is indexed. The default is the ``db_tablespace`` of
-the model, if any. If the backend doesn't support tablespaces, this
+this field is indexed. The default is the project's
+``DEFAULT_INDEX_TABLESPACE`` setting, if set, or the ``db_tablespace``
+of the model, if any. If the backend doesn't support tablespaces, this
option is ignored.
``default``
diff --git a/docs/modelforms.txt b/docs/modelforms.txt
new file mode 100644
index 0000000000..372abf9811
--- /dev/null
+++ b/docs/modelforms.txt
@@ -0,0 +1,313 @@
+==========================
+Using newforms with models
+==========================
+
+``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``
+model, and you want to create a form that lets people submit comments. In this
+case, it would be redundant to define the field types in your form, because
+you've already defined the fields in your model.
+
+For this reason, Django provides a helper class that let you create a ``Form``
+class from a Django model.
+
+For example::
+
+ >>> from django.newforms import ModelForm
+
+ # Create the form class.
+ >>> class ArticleForm(ModelForm):
+ ... class Meta:
+ ... model = Article
+
+ # Creating a form to add an article.
+ >>> form = ArticleForm()
+
+ # Creating a form to change an existing article.
+ >>> article = Article.objects.get(pk=1)
+ >>> form = ArticleForm(instance=article)
+
+Field types
+-----------
+
+The generated ``Form`` class will have a form field for every model field. Each
+model field has a corresponding default form field. For example, a
+``CharField`` on a model is represented as a ``CharField`` on a form. A
+model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is
+the full list of conversions:
+
+ =============================== ========================================
+ Model field Form field
+ =============================== ========================================
+ ``AutoField`` Not represented in the form
+ ``BooleanField`` ``BooleanField``
+ ``CharField`` ``CharField`` with ``max_length`` set to
+ the model field's ``max_length``
+ ``CommaSeparatedIntegerField`` ``CharField``
+ ``DateField`` ``DateField``
+ ``DateTimeField`` ``DateTimeField``
+ ``DecimalField`` ``DecimalField``
+ ``EmailField`` ``EmailField``
+ ``FileField`` ``FileField``
+ ``FilePathField`` ``CharField``
+ ``FloatField`` ``FloatField``
+ ``ForeignKey`` ``ModelChoiceField`` (see below)
+ ``ImageField`` ``ImageField``
+ ``IntegerField`` ``IntegerField``
+ ``IPAddressField`` ``IPAddressField``
+ ``ManyToManyField`` ``ModelMultipleChoiceField`` (see
+ below)
+ ``NullBooleanField`` ``CharField``
+ ``PhoneNumberField`` ``USPhoneNumberField``
+ (from ``django.contrib.localflavor.us``)
+ ``PositiveIntegerField`` ``IntegerField``
+ ``PositiveSmallIntegerField`` ``IntegerField``
+ ``SlugField`` ``CharField``
+ ``SmallIntegerField`` ``IntegerField``
+ ``TextField`` ``CharField`` with ``widget=Textarea``
+ ``TimeField`` ``TimeField``
+ ``URLField`` ``URLField`` with ``verify_exists`` set
+ to the model field's ``verify_exists``
+ ``USStateField`` ``CharField`` with
+ ``widget=USStateSelect``
+ (``USStateSelect`` is from
+ ``django.contrib.localflavor.us``)
+ ``XMLField`` ``CharField`` with ``widget=Textarea``
+ =============================== ========================================
+
+
+.. note::
+ The ``FloatField`` form field and ``DecimalField`` model and form fields
+ are new in the development version.
+
+As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
+types are special cases:
+
+ * ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``,
+ which is a ``ChoiceField`` whose choices are a model ``QuerySet``.
+
+ * ``ManyToManyField`` is represented by
+ ``django.newforms.ModelMultipleChoiceField``, which is a
+ ``MultipleChoiceField`` whose choices are a model ``QuerySet``.
+
+In addition, each generated form field has attributes set as follows:
+
+ * If the model field has ``blank=True``, then ``required`` is set to
+ ``False`` on the form field. Otherwise, ``required=True``.
+
+ * The form field's ``label`` is set to the ``verbose_name`` of the model
+ field, with the first character capitalized.
+
+ * The form field's ``help_text`` is set to the ``help_text`` of the model
+ field.
+
+ * If the model field has ``choices`` set, then the form field's ``widget``
+ will be set to ``Select``, with choices coming from the model field's
+ ``choices``. The choices will normally include the blank choice which is
+ selected by default. If the field is required, this forces the user to
+ make a selection. The blank choice will not be included if the model
+ field has ``blank=False`` and an explicit ``default`` value (the
+ ``default`` value will be initially selected instead).
+
+Finally, note that you can override the form field used for a given model
+field. See "Overriding the default field types" below.
+
+A full example
+--------------
+
+Consider this set of models::
+
+ from django.db import models
+
+ TITLE_CHOICES = (
+ ('MR', 'Mr.'),
+ ('MRS', 'Mrs.'),
+ ('MS', 'Ms.'),
+ )
+
+ class Author(models.Model):
+ name = models.CharField(max_length=100)
+ title = models.CharField(max_length=3, choices=TITLE_CHOICES)
+ birth_date = models.DateField(blank=True, null=True)
+
+ def __unicode__(self):
+ return self.name
+
+ class Book(models.Model):
+ name = models.CharField(max_length=100)
+ authors = models.ManyToManyField(Author)
+
+ class AuthorForm(ModelForm):
+ class Meta:
+ model = Author
+
+ class BookForm(ModelForm):
+ class Meta:
+ model = Book
+
+With these models, the ``ModelForm`` subclasses above would be roughly
+equivalent to this (the only difference being the ``save()`` method, which
+we'll discuss in a moment.)::
+
+ class AuthorForm(forms.Form):
+ name = forms.CharField(max_length=100)
+ title = forms.CharField(max_length=3,
+ widget=forms.Select(choices=TITLE_CHOICES))
+ birth_date = forms.DateField(required=False)
+
+ class BookForm(forms.Form):
+ name = forms.CharField(max_length=100)
+ authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
+
+The ``save()`` method
+---------------------
+
+Every form produced by ``ModelForm`` also has a ``save()``
+method. This method creates and saves a database object from the data
+bound to the form. A subclass of ``ModelForm`` can accept an existing
+model instance as the keyword argument ``instance``; if this is
+supplied, ``save()`` will update that instance. If it's not supplied,
+``save()`` will create a new instance of the specified model::
+
+ # Create a form instance from POST data.
+ >>> f = ArticleForm(request.POST)
+
+ # Save a new Article object from the form's data.
+ >>> new_article = f.save()
+
+ # Create a form to edit an existing Article.
+ >>> a = Article.objects.get(pk=1)
+ >>> f = ArticleForm(instance=a)
+
+Note that ``save()`` will raise a ``ValueError`` if the data in the form
+doesn't validate -- i.e., ``if form.errors``.
+
+This ``save()`` method accepts an optional ``commit`` keyword argument, which
+accepts either ``True`` or ``False``. If you call ``save()`` with
+``commit=False``, then it will return an object that hasn't yet been saved to
+the database. In this case, it's up to you to call ``save()`` on the resulting
+model instance. This is useful if you want to do custom processing on the
+object before saving it. ``commit`` is ``True`` by default.
+
+Another side effect of using ``commit=False`` is seen when your model has
+a many-to-many relation with another model. If your model has a many-to-many
+relation and you specify ``commit=False`` when you save a form, Django cannot
+immediately save the form data for the many-to-many relation. This is because
+it isn't possible to save many-to-many data for an instance until the instance
+exists in the database.
+
+To work around this problem, every time you save a form using ``commit=False``,
+Django adds a ``save_m2m()`` method to your ``ModelForm`` subclass. After
+you've manually saved the instance produced by the form, you can invoke
+``save_m2m()`` to save the many-to-many form data. For example::
+
+ # Create a form instance with POST data.
+ >>> f = AuthorForm(request.POST)
+
+ # Create, but don't save the new author instance.
+ >>> new_author = f.save(commit=False)
+
+ # Modify the author in some way.
+ >>> new_author.some_field = 'some_value'
+
+ # Save the new instance.
+ >>> new_author.save()
+
+ # Now, save the many-to-many data for the form.
+ >>> f.save_m2m()
+
+Calling ``save_m2m()`` is only required if you use ``save(commit=False)``.
+When you use a simple ``save()`` on a form, all data -- including
+many-to-many data -- is saved without the need for any additional method calls.
+For example::
+
+ # Create a form instance with POST data.
+ >>> a = Author()
+ >>> f = AuthorForm(a, request.POST)
+
+ # Create and save the new author instance. There's no need to do anything else.
+ >>> new_author = f.save()
+
+Using a subset of fields on the form
+------------------------------------
+
+In some cases, you may not want all the model fields to appear on the generated
+form. There are three ways of telling ``ModelForm`` to use only a subset of the
+model fields:
+
+ 1. Set ``editable=False`` on the model field. As a result, *any* form
+ created from the model via ``ModelForm`` will not include that
+ field.
+
+ 2. Use the ``fields`` attribute of the ``ModelForm``'s inner ``Meta`` class.
+ This attribute, if given, should be a list of field names to include in
+ the form.
+
+ 3. Use the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` class.
+ This attribute, if given, should be a list of field names to exclude
+ the form.
+
+ For example, if you want a form for the ``Author`` model (defined above)
+ that includes only the ``name`` and ``title`` fields, you would specify
+ ``fields`` or ``exclude`` like this::
+
+ class PartialAuthorForm(ModelForm):
+ class Meta:
+ model = Author
+ fields = ('name', 'title')
+
+ class PartialAuthorForm(ModelForm):
+ class Meta:
+ model = Author
+ exclude = ('birth_date',)
+
+ Since the Author model has only 3 fields, 'name', 'title', and
+ 'birth_date', the forms above will contain exactly the same fields.
+
+.. note::
+
+ If you specify ``fields`` or ``exclude`` when creating a form with
+ ``ModelForm``, then the fields that are not in the resulting form will not
+ be set by the form's ``save()`` method. Django will prevent any attempt to
+ save an incomplete model, so if the model does not allow the missing fields
+ to be empty, and does not provide a default value for the missing fields,
+ any attempt to ``save()`` a ``ModelForm`` with missing fields will fail.
+ To avoid this failure, you must instantiate your model with initial values
+ for the missing, but required fields, or use ``save(commit=False)`` and
+ manually set anyextra required fields::
+
+ instance = Instance(required_field='value')
+ form = InstanceForm(request.POST, instance=instance)
+ new_instance = form.save()
+
+ instance = form.save(commit=False)
+ instance.required_field = 'new value'
+ new_instance = instance.save()
+
+ See the `section on saving forms`_ for more details on using
+ ``save(commit=False)``.
+
+.. _section on saving forms: `The save() method`_
+
+Overriding the default field types
+----------------------------------
+
+The default field types, as described in the "Field types" table above, are
+sensible defaults; if you have a ``DateField`` in your model, chances are you'd
+want that to be represented as a ``DateField`` in your form. But
+``ModelForm`` gives you the flexibility of changing the form field type
+for a given model field. You do this by declaratively specifying fields like
+you would in a regular ``Form``. Declared fields will override the default
+ones generated by using the ``model`` attribute.
+
+For example, if you wanted to use ``MyDateFormField`` for the ``pub_date``
+field, you could do the following::
+
+ >>> class ArticleForm(ModelForm):
+ ... pub_date = MyDateFormField()
+ ...
+ ... class Meta:
+ ... model = Article
diff --git a/docs/newforms.txt b/docs/newforms.txt
index e27514cdba..19f42cb2ee 100644
--- a/docs/newforms.txt
+++ b/docs/newforms.txt
@@ -756,13 +756,14 @@ For example::
Highlighting required fields in templates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-You may wish to show a visitor which fields are required. Here is the above
-example modified to insert an asterix after the label of each required field::
+It's common to show a user which fields are required. Here's an example of how
+to do that, using the above example modified to insert an asterisk after the
+label of each required field::
<form method="post" action="">
<dl>
{% for field in form %}
- <dt>{{ field.label_tag }}{{ field.label }}{% if field.field.required %}*{% endif %}</dt>
+ <dt>{{ field.label_tag }}{% if field.field.required %}*{% endif %}</dt>
<dd>{{ field }}</dd>
{% if field.help_text %}<dd>{{ field.help_text }}</dd>{% endif %}
{% if field.errors %}<dd class="myerrors">{{ field.errors }}</dd>{% endif %}
@@ -772,10 +773,11 @@ example modified to insert an asterix after the label of each required field::
</form>
The ``{% if field.field.required %}*{% endif %}`` fragment is the relevant
-addition here. It adds the asterix only if the field is required. Note that we
-check ``field.field.required`` and not ``field.required``. In the template,
-``field`` is a ``newforms.forms.BoundField`` instance, which holds the actual
-``Field`` instance in its ``field`` attribute.
+addition here. It adds the asterisk only if the field is required.
+
+Note that we check ``field.field.required`` and not ``field.required``. In the
+template, ``field`` is a ``newforms.forms.BoundField`` instance, which holds
+the actual ``Field`` instance in its ``field`` attribute.
Binding uploaded files to a form
--------------------------------
@@ -1105,9 +1107,9 @@ fields. We've specified ``auto_id=False`` to simplify the output::
**New in Django development version**
-The ``error_messages`` argument lets you override the default messages which the
+The ``error_messages`` argument lets you override the default messages that the
field will raise. Pass in a dictionary with keys matching the error messages you
-want to override. For example::
+want to override. For example, here is the default error message::
>>> generic = forms.CharField()
>>> generic.clean('')
@@ -1115,14 +1117,16 @@ want to override. For example::
...
ValidationError: [u'This field is required.']
+And here is a custom error message::
+
>>> name = forms.CharField(error_messages={'required': 'Please enter your name'})
>>> name.clean('')
Traceback (most recent call last):
...
ValidationError: [u'Please enter your name']
-In the `built-in Field classes`_ section below, each Field defines the error
-message keys it uses.
+In the `built-in Field classes`_ section below, each ``Field`` defines the
+error message keys it uses.
Dynamic initial values
----------------------
@@ -1432,7 +1436,7 @@ Also takes the following optional arguments:
The optional argument ``error_message`` is also accepted for backwards
compatibility. The preferred way to provide an error message is to use the
``error_messages`` argument, passing a dictionary with ``'invalid'`` as a key
-and the error message as the value.
+and the error message as the value.
``TimeField``
~~~~~~~~~~~~~
@@ -1766,421 +1770,14 @@ You can then use this field whenever you have a form that requires a comment::
Generating forms for models
===========================
-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``
-model, and you want to create a form that lets people submit comments. In this
-case, it would be redundant to define the field types in your form, because
-you've already defined the fields in your model.
-
-For this reason, Django provides a few helper functions that let you create a
-``Form`` class from a Django model.
-
-``form_for_model()``
---------------------
-
-The method ``django.newforms.form_for_model()`` creates a form based on the
-definition of a specific model. Pass it the model class, and it will return a
-``Form`` class that contains a form field for each model field.
-
-For example::
-
- >>> from django.newforms import form_for_model
-
- # Create the form class.
- >>> ArticleForm = form_for_model(Article)
-
- # Create an empty form instance.
- >>> f = ArticleForm()
-
-It bears repeating that ``form_for_model()`` takes the model *class*, not a
-model instance, and it returns a ``Form`` *class*, not a ``Form`` instance.
-
-Field types
-~~~~~~~~~~~
-
-The generated ``Form`` class will have a form field for every model field. Each
-model field has a corresponding default form field. For example, a
-``CharField`` on a model is represented as a ``CharField`` on a form. A
-model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is
-the full list of conversions:
-
- =============================== ========================================
- Model field Form field
- =============================== ========================================
- ``AutoField`` Not represented in the form
- ``BooleanField`` ``BooleanField``
- ``CharField`` ``CharField`` with ``max_length`` set to
- the model field's ``max_length``
- ``CommaSeparatedIntegerField`` ``CharField``
- ``DateField`` ``DateField``
- ``DateTimeField`` ``DateTimeField``
- ``DecimalField`` ``DecimalField``
- ``EmailField`` ``EmailField``
- ``FileField`` ``FileField``
- ``FilePathField`` ``CharField``
- ``FloatField`` ``FloatField``
- ``ForeignKey`` ``ModelChoiceField`` (see below)
- ``ImageField`` ``ImageField``
- ``IntegerField`` ``IntegerField``
- ``IPAddressField`` ``IPAddressField``
- ``ManyToManyField`` ``ModelMultipleChoiceField`` (see
- below)
- ``NullBooleanField`` ``CharField``
- ``PhoneNumberField`` ``USPhoneNumberField``
- (from ``django.contrib.localflavor.us``)
- ``PositiveIntegerField`` ``IntegerField``
- ``PositiveSmallIntegerField`` ``IntegerField``
- ``SlugField`` ``CharField``
- ``SmallIntegerField`` ``IntegerField``
- ``TextField`` ``CharField`` with ``widget=Textarea``
- ``TimeField`` ``TimeField``
- ``URLField`` ``URLField`` with ``verify_exists`` set
- to the model field's ``verify_exists``
- ``USStateField`` ``CharField`` with
- ``widget=USStateSelect``
- (``USStateSelect`` is from
- ``django.contrib.localflavor.us``)
- ``XMLField`` ``CharField`` with ``widget=Textarea``
- =============================== ========================================
-
-
-.. note::
- The ``FloatField`` form field and ``DecimalField`` model and form fields
- are new in the development version.
-
-As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
-types are special cases:
-
- * ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``,
- which is a ``ChoiceField`` whose choices are a model ``QuerySet``.
-
- * ``ManyToManyField`` is represented by
- ``django.newforms.ModelMultipleChoiceField``, which is a
- ``MultipleChoiceField`` whose choices are a model ``QuerySet``.
-
-In addition, each generated form field has attributes set as follows:
-
- * If the model field has ``blank=True``, then ``required`` is set to
- ``False`` on the form field. Otherwise, ``required=True``.
-
- * The form field's ``label`` is set to the ``verbose_name`` of the model
- field, with the first character capitalized.
-
- * The form field's ``help_text`` is set to the ``help_text`` of the model
- field.
-
- * If the model field has ``choices`` set, then the form field's ``widget``
- will be set to ``Select``, with choices coming from the model field's
- ``choices``. The choices will normally include the blank choice which is
- selected by default. If the field is required, this forces the user to
- make a selection. The blank choice will not be included if the model
- field has ``blank=False`` and an explicit ``default`` value (the
- ``default`` value will be initially selected instead).
-
-Finally, note that you can override the form field used for a given model
-field. See "Overriding the default field types" below.
-
-A full example
-~~~~~~~~~~~~~~
-
-Consider this set of models::
-
- from django.db import models
-
- TITLE_CHOICES = (
- ('MR', 'Mr.'),
- ('MRS', 'Mrs.'),
- ('MS', 'Ms.'),
- )
-
- class Author(models.Model):
- name = models.CharField(max_length=100)
- title = models.CharField(max_length=3, choices=TITLE_CHOICES)
- birth_date = models.DateField(blank=True, null=True)
-
- def __unicode__(self):
- return self.name
-
- class Book(models.Model):
- name = models.CharField(max_length=100)
- authors = models.ManyToManyField(Author)
-
-With these models, a call to ``form_for_model(Author)`` would return a ``Form``
-class equivalent to this::
-
- class AuthorForm(forms.Form):
- name = forms.CharField(max_length=100)
- title = forms.CharField(max_length=3,
- widget=forms.Select(choices=TITLE_CHOICES))
- birth_date = forms.DateField(required=False)
-
-A call to ``form_for_model(Book)`` would return a ``Form`` class equivalent to
-this::
-
- class BookForm(forms.Form):
- name = forms.CharField(max_length=100)
- authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
-
-The ``save()`` method
-~~~~~~~~~~~~~~~~~~~~~
-
-Every form produced by ``form_for_model()`` also has a ``save()`` method. This
-method creates and saves a database object from the data bound to the form. For
-example::
-
- # Create a form instance from POST data.
- >>> f = ArticleForm(request.POST)
-
- # Save a new Article object from the form's data.
- >>> new_article = f.save()
-
-Note that ``save()`` will raise a ``ValueError`` if the data in the form
-doesn't validate -- i.e., ``if form.errors``.
-
-This ``save()`` method accepts an optional ``commit`` keyword argument, which
-accepts either ``True`` or ``False``. If you call ``save()`` with
-``commit=False``, then it will return an object that hasn't yet been saved to
-the database. In this case, it's up to you to call ``save()`` on the resulting
-model instance. This is useful if you want to do custom processing on the
-object before saving it. ``commit`` is ``True`` by default.
-
-Another side effect of using ``commit=False`` is seen when your model has
-a many-to-many relation with another model. If your model has a many-to-many
-relation and you specify ``commit=False`` when you save a form, Django cannot
-immediately save the form data for the many-to-many relation. This is because
-it isn't possible to save many-to-many data for an instance until the instance
-exists in the database.
-
-To work around this problem, every time you save a form using ``commit=False``,
-Django adds a ``save_m2m()`` method to the form created by ``form_for_model``.
-After you've manually saved the instance produced by the form, you can invoke
-``save_m2m()`` to save the many-to-many form data. For example::
-
- # Create a form instance with POST data.
- >>> f = AuthorForm(request.POST)
-
- # Create, but don't save the new author instance.
- >>> new_author = f.save(commit=False)
-
- # Modify the author in some way.
- >>> new_author.some_field = 'some_value'
-
- # Save the new instance.
- >>> new_author.save()
-
- # Now, save the many-to-many data for the form.
- >>> f.save_m2m()
-
-Calling ``save_m2m()`` is only required if you use ``save(commit=False)``.
-When you use a simple ``save()`` on a form, all data -- including
-many-to-many data -- is saved without the need for any additional method calls.
-For example::
-
- # Create a form instance with POST data.
- >>> f = AuthorForm(request.POST)
-
- # Create and save the new author instance. There's no need to do anything else.
- >>> new_author = f.save()
-
-Using an alternate base class
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you want to add custom methods to the form generated by
-``form_for_model()``, write a class that extends ``django.newforms.BaseForm``
-and contains your custom methods. Then, use the ``form`` argument to
-``form_for_model()`` to tell it to use your custom form as its base class.
-For example::
-
- # Create the new base class.
- >>> class MyBase(BaseForm):
- ... def my_method(self):
- ... # Do whatever the method does
-
- # Create the form class with a different base class.
- >>> ArticleForm = form_for_model(Article, form=MyBase)
-
- # Instantiate the form.
- >>> f = ArticleForm()
-
- # Use the base class method.
- >>> f.my_method()
-
-Using a subset of fields on the form
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-**New in Django development version**
-
-In some cases, you may not want all the model fields to appear on the generated
-form. There are two ways of telling ``form_for_model()`` to use only a subset
-of the model fields:
-
- 1. Set ``editable=False`` on the model field. As a result, *any* form
- created from the model via ``form_for_model()`` will not include that
- field.
-
- 2. Use the ``fields`` argument to ``form_for_model()``. This argument, if
- given, should be a list of field names to include in the form.
-
- For example, if you want a form for the ``Author`` model (defined above)
- that includes only the ``name`` and ``title`` fields, you would specify
- ``fields`` like this::
-
- PartialArticleForm = form_for_model(Author, fields=('name', 'title'))
-
-.. note::
-
- If you specify ``fields`` when creating a form with ``form_for_model()``,
- then the fields that are *not* specified will not be set by the form's
- ``save()`` method. Django will prevent any attempt to save an incomplete
- model, so if the model does not allow the missing fields to be empty, and
- does not provide a default value for the missing fields, any attempt to
- ``save()`` a ``form_for_model`` with missing fields will fail. To avoid
- this failure, you must use ``save(commit=False)`` and manually set any
- extra required fields::
-
- instance = form.save(commit=False)
- instance.required_field = 'new value'
- instance.save()
-
- See the `section on saving forms`_ for more details on using
- ``save(commit=False)``.
-
-.. _section on saving forms: `The save() method`_
-
-Overriding the default field types
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The default field types, as described in the "Field types" table above, are
-sensible defaults; if you have a ``DateField`` in your model, chances are you'd
-want that to be represented as a ``DateField`` in your form. But
-``form_for_model()`` gives you the flexibility of changing the form field type
-for a given model field. You do this by specifying a **formfield callback**.
-
-A formfield callback is a function that, when provided with a model field,
-returns a form field instance. When constructing a form, ``form_for_model()``
-asks the formfield callback to provide form field types.
-
-By default, ``form_for_model()`` calls the ``formfield()`` method on the model
-field::
-
- def default_callback(field, **kwargs):
- return field.formfield(**kwargs)
-
-The ``kwargs`` are any keyword arguments that might be passed to the form
-field, such as ``required=True`` or ``label='Foo'``.
-
-For example, if you wanted to use ``MyDateFormField`` for any ``DateField``
-field on the model, you could define the callback::
-
- >>> def my_callback(field, **kwargs):
- ... if isinstance(field, models.DateField):
- ... return MyDateFormField(**kwargs)
- ... else:
- ... return field.formfield(**kwargs)
-
- >>> ArticleForm = form_for_model(Article, formfield_callback=my_callback)
-
-Note that your callback needs to handle *all* possible model field types, not
-just the ones that you want to behave differently to the default. That's why
-this example has an ``else`` clause that implements the default behavior.
-
-.. warning::
- The field that is passed into the ``formfield_callback`` function in
- ``form_for_model()`` and ``form_for_instance`` is the field instance from
- your model's class. You **must not** alter that object at all; treat it
- as read-only!
-
- If you make any alterations to that object, it will affect any future
- users of the model class, because you will have changed the field object
- used to construct the class. This is almost certainly what you don't want
- to have happen.
-
-Finding the model associated with a form
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The model class that was used to construct the form is available
-using the ``_model`` property of the generated form::
-
- >>> ArticleForm = form_for_model(Article)
- >>> ArticleForm._model
- <class 'myapp.models.Article'>
-
-``form_for_instance()``
------------------------
-
-``form_for_instance()`` is like ``form_for_model()``, but it takes a model
-instance instead of a model class::
-
- # Create an Author.
- >>> a = Author(name='Joe Smith', title='MR', birth_date=None)
- >>> a.save()
-
- # Create a form for this particular Author.
- >>> AuthorForm = form_for_instance(a)
-
- # Instantiate the form.
- >>> f = AuthorForm()
-
-When a form created by ``form_for_instance()`` is created, the initial data
-values for the form fields are drawn from the instance. However, this data is
-not bound to the form. You will need to bind data to the form before the form
-can be saved.
-
-Unlike ``form_for_model()``, a choice field in form created by
-``form_for_instance()`` will not include the blank choice if the respective
-model field has ``blank=False``. The initial choice is drawn from the instance.
-
-When you call ``save()`` on a form created by ``form_for_instance()``,
-the database instance will be updated. As in ``form_for_model()``, ``save()``
-will raise ``ValueError`` if the data doesn't validate.
-
-``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback``
-arguments that behave the same way as they do for ``form_for_model()``.
-
-Let's modify the earlier `contact form`_ view example a little bit. Suppose we
-have a ``Message`` model that holds each contact submission. Something like::
-
- class Message(models.Model):
- subject = models.CharField(max_length=100)
- message = models.TextField()
- sender = models.EmailField()
- cc_myself = models.BooleanField(required=False)
-
-You could use this model to create a form (using ``form_for_model()``). You
-could also use existing ``Message`` instances to create a form for editing
-messages. The earlier_ view can be changed slightly to accept the ``id`` value
-of an existing ``Message`` and present it for editing::
-
- def contact_edit(request, msg_id):
- # Create the form from the message id.
- message = get_object_or_404(Message, id=msg_id)
- ContactForm = form_for_instance(message)
-
- if request.method == 'POST':
- form = ContactForm(request.POST)
- if form.is_valid():
- form.save()
- return HttpResponseRedirect('/url/on_success/')
- else:
- form = ContactForm()
- return render_to_response('contact.html', {'form': form})
-
-Aside from how we create the ``ContactForm`` class here, the main point to
-note is that the form display in the ``GET`` branch of the function
-will use the values from the ``message`` instance as initial values for the
-form field.
-
-.. _contact form: `Simple view example`_
-.. _earlier: `Simple view example`_
+The prefered way of generating forms that work with models is explained in the
+`ModelForms documentation`_.
-When should you use ``form_for_model()`` and ``form_for_instance()``?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Looking for the ``form_for_model`` and ``form_for_instance`` documentation?
+They've been deprecated, but you can still `view the documentation`_.
-The ``form_for_model()`` and ``form_for_instance()`` functions are meant to be
-shortcuts for the common case. If you want to create a form whose fields map to
-more than one model, or a form that contains fields that *aren't* on a model,
-you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way
-isn't that difficult, after all.
+.. _ModelForms documentation: ../modelforms/
+.. _view the documentation: ../form_for_model/
More coming soon
================
diff --git a/docs/request_response.txt b/docs/request_response.txt
index 8da00cdf09..2445b3e6b9 100644
--- a/docs/request_response.txt
+++ b/docs/request_response.txt
@@ -555,12 +555,15 @@ Three things to note about 404 views:
* The 404 view is also called if Django doesn't find a match after checking
every regular expression in the URLconf.
- * If you don't define your own 404 view -- and simply use the default,
- which is recommended -- you still have one obligation: To create a
- ``404.html`` template in the root of your template directory. The default
- 404 view will use that template for all 404 errors.
-
- * If ``DEBUG`` is set to ``True`` (in your settings module) then your 404
+ * If you don't define your own 404 view -- and simply use the
+ default, which is recommended -- you still have one obligation:
+ you must create a ``404.html`` template in the root of your
+ template directory. The default 404 view will use that template
+ for all 404 errors. The default 404 view will pass one variable
+ to the template: ``request_path``, which is the URL that resulted
+ in the 404.
+
+ * If ``DEBUG`` is set to ``True`` (in your settings module), then your 404
view will never be used, and the traceback will be displayed instead.
The 500 (server error) view
@@ -572,7 +575,8 @@ the view ``django.views.defaults.server_error``, which loads and renders the
template ``500.html``.
This means you need to define a ``500.html`` template in your root template
-directory. This template will be used for all server errors.
+directory. This template will be used for all server errors. The
+default 500 view passes no variables to this template.
This ``server_error`` view should suffice for 99% of Web applications, but if
you want to override the view, you can specify ``handler500`` in your
diff --git a/docs/serialization.txt b/docs/serialization.txt
index cf8b196931..dff33f219f 100644
--- a/docs/serialization.txt
+++ b/docs/serialization.txt
@@ -135,8 +135,8 @@ For example::
json_serializer = serializers.get_serializer("json")()
json_serializer.serialize(queryset, ensure_ascii=False, stream=response)
-Django ships with a copy of simplejson_ in the source. Be aware, that if
-you're using that for serializing directly that not all Django output can be
+The Django source code includes the simplejson_ module. Be aware that if
+you're serializing using that module directly, not all Django output can be
passed unmodified to simplejson. In particular, `lazy translation objects`_
need a `special encoder`_ written for them. Something like this will work::
@@ -151,8 +151,3 @@ need a `special encoder`_ written for them. Something like this will work::
.. _lazy translation objects: ../i18n/#lazy-translation
.. _special encoder: http://svn.red-bean.com/bob/simplejson/tags/simplejson-1.7/docs/index.html
-
-Writing custom serializers
-``````````````````````````
-
-XXX ...
diff --git a/docs/sessions.txt b/docs/sessions.txt
index 96e8d36854..eddc3f174a 100644
--- a/docs/sessions.txt
+++ b/docs/sessions.txt
@@ -99,6 +99,8 @@ It implements the following standard dictionary methods:
* ``items()``
+ * ``setdefault()``
+
It also has these three methods:
* ``set_test_cookie()``
diff --git a/docs/settings.txt b/docs/settings.txt
index 1700c248c0..bc73f3e759 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -225,6 +225,27 @@ Whether to append trailing slashes to URLs. This is only used if
``CommonMiddleware`` is installed (see the `middleware docs`_). See also
``PREPEND_WWW``.
+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 `authentication backends
+documentation`_ for details.
+
+.. _authentication backends documentation: ../authentication/#other-authentication-sources
+
+AUTH_PROFILE_MODULE
+-------------------
+
+Default: Not defined
+
+The site-specific user profile model used by this site. See the
+`documentation on user profile models`_ for details.
+
+.. _documentation on user profile models: ../authentication/#storing-additional-information-about-users
+
CACHE_BACKEND
-------------
@@ -393,6 +414,26 @@ Default: ``'webmaster@localhost'``
Default e-mail address to use for various automated correspondence from the
site manager(s).
+DEFAULT_TABLESPACE
+------------------
+
+**New in Django development version**
+
+Default: ``''`` (Empty string)
+
+Default tablespace to use for models that don't specify one, if the
+backend supports it.
+
+DEFAULT_INDEX_TABLESPACE
+------------------------
+
+**New in Django development version**
+
+Default: ``''`` (Empty string)
+
+Default tablespace to use for indexes on fields that don't specify
+one, if the backend supports it.
+
DISALLOWED_USER_AGENTS
----------------------
@@ -1129,12 +1170,12 @@ If you're not setting the ``DJANGO_SETTINGS_MODULE`` environment variable, you
settings.
If you don't set ``DJANGO_SETTINGS_MODULE`` and don't call ``configure()``,
-Django will raise an ``EnvironmentError`` exception the first time a setting
+Django will raise an ``ImportError`` exception the first time a setting
is accessed.
If you set ``DJANGO_SETTINGS_MODULE``, access settings values somehow, *then*
-call ``configure()``, Django will raise an ``EnvironmentError`` saying settings
-have already been configured.
+call ``configure()``, Django will raise a ``RuntimeError`` indicating
+that settings have already been configured.
Also, it's an error to call ``configure()`` more than once, or to call
``configure()`` after any setting has been accessed.
diff --git a/docs/shortcuts.txt b/docs/shortcuts.txt
index 6c55486b5f..f94babfecb 100644
--- a/docs/shortcuts.txt
+++ b/docs/shortcuts.txt
@@ -98,8 +98,8 @@ This example is equivalent to::
except MyModel.DoesNotExist:
raise Http404
-Note: As with ``get()``, an ``AssertionError`` will be raised if more than
-one object is found.
+Note: As with ``get()``, an ``MultipleObjectsReturned`` exception will be
+raised if more than one object is found.
.. _get(): ../db-api/#get-kwargs
diff --git a/docs/syndication_feeds.txt b/docs/syndication_feeds.txt
index 351cead39c..b3edf4c008 100644
--- a/docs/syndication_feeds.txt
+++ b/docs/syndication_feeds.txt
@@ -234,6 +234,7 @@ request to the URL ``/rss/beats/0613/``:
``get_object()`` method, passing it the bits. In this case, bits is
``['0613']``. For a request to ``/rss/beats/0613/foo/bar/``, bits would
be ``['0613', 'foo', 'bar']``.
+
* ``get_object()`` is responsible for retrieving the given beat, from the
given ``bits``. In this case, it uses the Django database API to retrieve
the beat. Note that ``get_object()`` should raise
@@ -243,6 +244,7 @@ request to the URL ``/rss/beats/0613/``:
raises ``Beat.DoesNotExist`` on failure, and ``Beat.DoesNotExist`` is a
subclass of ``ObjectDoesNotExist``. Raising ``ObjectDoesNotExist`` in
``get_object()`` tells Django to produce a 404 error for that request.
+
* To generate the feed's ``<title>``, ``<link>`` and ``<description>``,
Django uses the ``title()``, ``link()`` and ``description()`` methods. In
the previous example, they were simple string class attributes, but this
@@ -258,9 +260,9 @@ request to the URL ``/rss/beats/0613/``:
Inside the ``link()`` method, we handle the possibility that ``obj``
might be ``None``, which can occur when the URL isn't fully specified. In
some cases, you might want to do something else in this case, which would
- mean you'd need to check for ``obj`` existing in other methods as well
- (the ``link()`` method is called very early in the feed generation
- process, so is a good place to bail out early).
+ mean you'd need to check for ``obj`` existing in other methods as well.
+ (The ``link()`` method is called very early in the feed generation
+ process, so it's a good place to bail out early.)
* Finally, note that ``items()`` in this example also takes the ``obj``
argument. The algorithm for ``items`` is the same as described in the
@@ -603,8 +605,9 @@ This example illustrates all possible attributes and methods for a ``Feed`` clas
# ITEM LINK -- One of these three is required. The framework looks for
# them in this order.
- # First, the framework tries the get_absolute_url() method on each item
- # returned by items(). Failing that, it tries these two methods:
+ # First, the framework tries the two methods below, in
+ # order. Failing that, it falls back to the get_absolute_url()
+ # method on each item returned by items().
def item_link(self, item):
"""
diff --git a/docs/templates.txt b/docs/templates.txt
index 1c80f7578a..ffda321512 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -310,58 +310,106 @@ Automatic HTML escaping
**New in Django development version**
-A very real problem when creating HTML (and other) output using templates and
-variable substitution is the possibility of accidently inserting some variable
-value that affects the resulting HTML. For example, a template fragment such as
-::
+When generating HTML from templates, there's always a risk that a variable will
+include characters that affect the resulting HTML. For example, consider this
+template fragment::
Hello, {{ name }}.
-seems like a harmless way to display the user's name. However, if you are
-displaying data that the user entered directly and they had entered their name as ::
+At first, this seems like a harmless way to display a user's name, but consider
+what would happen if the user entered his name as this::
<script>alert('hello')</script>
-this would always display a Javascript alert box when the page was loaded.
-Similarly, if you were displaying some data generated by another process and it
-contained a '<' symbol, you couldn't just dump this straight into your HTML,
-because it would be treated as the start of an element. The effects of these
-sorts of problems can vary from merely annoying to allowing exploits via `Cross
-Site Scripting`_ (XSS) attacks.
+With this name value, the template would be rendered as::
+
+ Hello, <script>alert('hello')</script>
+
+...which means the browser would pop-up a JavaScript alert box!
+
+Similarly, what if the name contained a ``'<'`` symbol, like this?
+
+ <b>username
+
+That would result in a rendered template like this::
+
+ Hello, <b>username
+
+...which, in turn, would result in the remainder of the Web page being bolded!
+
+Clearly, user-submitted data shouldn't be trusted blindly and inserted directly
+into your Web pages, because a malicious user could use this kind of hole to
+do potentially bad things. This type of security exploit is called a
+`Cross Site Scripting`_ (XSS) attack.
+
+To avoid this problem, you have two options:
+
+ * One, you can make sure to run each untrusted variable through the
+ ``escape`` filter (documented below), which converts potentially harmful
+ HTML characters to unharmful ones. This was default the default solution
+ in Django for its first few years, but the problem is that it puts the
+ onus on *you*, the developer / template author, to ensure you're escaping
+ everything. It's easy to forget to escape data.
+
+ * Two, you can take advantage of Django's automatic HTML escaping. The
+ remainder of this section describes how auto-escaping works.
+
+By default in the Django development version, every template automatically
+escapes the output of every variable tag. Specifically, these five characters
+are escaped:
+
+ * ``<`` is converted to ``&lt;``
+ * ``>`` is converted to ``&gt;``
+ * ``'`` (single quote) is converted to ``&#39;``
+ * ``"`` (double quote) is converted to ``&quot;``
+ * ``&`` is converted to ``&amp;``
+
+Again, we stress that this behavior is on by default. If you're using Django's
+template system, you're protected.
.. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
-In order to provide some protection against these problems, Django
-provides automatic (but controllable) HTML escaping for data coming from
-tempate variables. Inside this tag, any data that comes from template
-variables is examined to see if it contains one of the five HTML characters
-(<, >, ', " and &) that often need escaping and those characters are converted
-to their respective HTML entities. It causes no harm if a character is
-converted to an entity when it doesn't need to be, so all five characters are
-always converted.
-
-Since some variables will contain data that is *intended* to be rendered
-as HTML, template tag and filter writers can mark their output strings as
-requiring no further escaping. For example, the ``unordered_list`` filter is
-designed to return raw HTML and we want the template processor to simply
-display the results as returned, without applying any escaping. That is taken
-care of by the filter. The template author need do nothing special in that
-case.
-
-By default, automatic HTML escaping is always applied. However, sometimes you
-will not want this to occur (for example, if you're using the templating
-system to create an email). To control automatic escaping inside your template,
-wrap the affected content in the ``autoescape`` tag, like so::
+How to turn it off
+------------------
+
+If you don't want data to be auto-escaped, on a per-site, per-template level or
+per-variable level, you can turn it off in several ways.
+
+Why would you want to turn it off? Because sometimes, template variables
+contain data that you *intend* to be rendered as raw HTML, in which case you
+don't want their contents to be escaped. For example, you might store a blob of
+HTML in your database and want to embed that directly into your template. Or,
+you might be using Django's template system to produce text that is *not* HTML
+-- like an e-mail message, for instance.
+
+For individual variables
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+To disable auto-escaping for an individual variable, use the ``safe`` filter::
+
+ This will be escaped: {{ data }}
+ This will not be escaped: {{ data|safe }}
+
+Think of *safe* as shorthand for *safe from further escaping* or *can be
+safely interpreted as HTML*. In this example, if ``data`` contains ``'<b>'``,
+the output will be::
+
+ This will be escaped: &lt;b&gt;
+ This will not be escaped: <b>
+
+For template blocks
+~~~~~~~~~~~~~~~~~~~
+
+To control auto-escaping for a template, wrap the template (or just a
+particular section of the template) in the ``autoescape`` tag, like so::
{% autoescape off %}
Hello {{ name }}
{% endautoescape %}
-The auto-escaping tag passes its effect onto templates that extend the
-current one as well as templates included via the ``include`` tag, just like
-all block tags.
-
-The ``autoescape`` tag takes either ``on`` or ``off`` as its argument. At times, you might want to force auto-escaping when it would otherwise be disabled. For example::
+The ``autoescape`` tag takes either ``on`` or ``off`` as its argument. At
+times, you might want to force auto-escaping when it would otherwise be
+disabled. Here is an example template::
Auto-escaping is on by default. Hello {{ name }}
@@ -370,52 +418,60 @@ The ``autoescape`` tag takes either ``on`` or ``off`` as its argument. At times,
Nor this: {{ other_data }}
{% autoescape on %}
- Auto-escaping applies again, {{ name }}
+ Auto-escaping applies again: {{ name }}
{% endautoescape %}
{% endautoescape %}
-For individual variables, the ``safe`` filter can also be used to indicate
-that the contents should not be automatically escaped::
+The auto-escaping tag passes its effect onto templates that extend the
+current one as well as templates included via the ``include`` tag, just like
+all block tags. For example::
- This will be escaped: {{ data }}
- This will not be escaped: {{ data|safe }}
+ # base.html
-Think of *safe* as shorthand for *safe from further escaping* or *can be
-safely interpreted as HTML*. In this example, if ``data`` contains ``'<a>'``,
-the output will be::
+ {% autoescape off %}
+ <h1>{% block title %}</h1>
+ {% block content %}
+ {% endautoescape %}
- This will be escaped: &lt;a&gt;
- This will not be escaped: <a>
-
-Generally, you won't need to worry about auto-escaping very much. View
-developers and custom filter authors need to think about when their data
-shouldn't be escaped and mark it appropriately. They are in a better position
-to know when that should happen than the template author, so it is their
-responsibility. By default, all output is escaped unless the template
-processor is explicitly told otherwise.
-
-You should also note that if you are trying to write a template that might be
-used in situations where automatic escaping is enabled or disabled and you
-don't know which (such as when your template is included in other templates),
-you can safely write as if you were in an ``{% autoescape off %}`` situation.
-Scatter ``escape`` filters around for any variables that need escaping. When
-auto-escaping is on, these extra filters won't change the output -- any
-variables that use the ``escape`` filter do not have further automatic
-escaping applied to them.
+
+ # child.html
+
+ {% extends "base.html" %}
+ {% block title %}This & that{% endblock %}
+ {% block content %}<b>Hello!</b>{% endblock %}
+
+Because auto-escaping is turned off in the base template, it will also be
+turned off in the child template, resulting in the following rendered HTML::
+
+ <h1>This & that</h1>
+ <b>Hello!</b>
+
+Notes
+-----
+
+Generally, template authors don't need to worry about auto-escaping very much.
+Developers on the Python side (people writing views and custom filters) need to
+think about the cases in which data shouldn't be escaped, and mark data
+appropriately, so things Just Work in the template.
+
+If you're creating a template that might be used in situations where you're
+not sure whether auto-escaping is enabled, then add an ``escape`` filter to any
+variable that needs escaping. When auto-escaping is on, there's no danger of
+the ``escape`` filter *double-escaping* data -- the ``escape`` filter does not
+affect auto-escaped variables.
String literals and automatic escaping
--------------------------------------
-Sometimes you will pass a string literal as an argument to a filter. For
-example::
+As we mentioned earlier, filter arguments can be strings::
{{ data|default:"This is a string literal." }}
All string literals are inserted **without** any automatic escaping into the
-template, if they are used (it's as if they were all passed through the
-``safe`` filter). The reasoning behind this is that the template author is in
-control of what goes into the string literal, so they can make sure the text
-is correctly escaped when the template is written.
+template -- they act as if they were all passed through the ``safe`` filter.
+The reasoning behind this is that the template author is in control of what
+goes into the string literal, so they can make sure the text is correctly
+escaped when the template is written.
This means you would write ::
@@ -426,7 +482,7 @@ This means you would write ::
{{ data|default:"3 > 2" }} <-- Bad! Don't do this.
This doesn't affect what happens to data coming from the variable itself.
-The variable's contents are still automatically escaped, if necessary, since
+The variable's contents are still automatically escaped, if necessary, because
they're beyond the control of the template author.
Using the built-in reference
@@ -509,17 +565,17 @@ autoescape
**New in Django development version**
-Control the current auto-escaping behaviour. This tag takes either ``on`` or
+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.
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
been applied). This is equivalent to manually applying the ``escape`` filter
-attached to each variable.
+to each variable.
-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
+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.
block
@@ -1171,8 +1227,10 @@ Adds the arg to the value.
addslashes
~~~~~~~~~~
-Adds slashes. Useful for passing strings to JavaScript, for example.
+Adds slashes before quotes. Useful for escaping strings in CSV, for example.
+**New in Django development version**: for escaping data in JavaScript strings,
+use the `escapejs` filter instead.
capfirst
~~~~~~~~
@@ -1230,11 +1288,11 @@ once, after all other filters).
Escapes a string's HTML. Specifically, it makes these replacements:
- * ``"&"`` to ``"&amp;"``
- * ``<`` to ``"&lt;"``
- * ``>`` to ``"&gt;"``
- * ``'"'`` (double quote) to ``'&quot;'``
- * ``"'"`` (single quote) to ``'&#39;'``
+ * ``<`` is converted to ``&lt;``
+ * ``>`` is converted to ``&gt;``
+ * ``'`` (single quote) is converted to ``&#39;``
+ * ``"`` (double quote) is converted to ``&quot;``
+ * ``&`` is converted to ``&amp;``
The escaping is only applied when the string is output, so it does not matter
where in a chained sequence of filters you put ``escape``: it will always be
@@ -1246,6 +1304,15 @@ applied to the result will only result in one round of escaping being done. So
it is safe to use this function even in auto-escaping environments. If you want
multiple escaping passes to be applied, use the ``force_escape`` filter.
+escapejs
+~~~~~~~~
+
+**New in Django development version**
+
+Escapes characters for use in JavaScript strings. This does *not* make the
+string safe for use in HTML, but does protect you from syntax errors when using
+templates to generate JavaScript/JSON.
+
filesizeformat
~~~~~~~~~~~~~~
@@ -1268,36 +1335,36 @@ floatformat
When used without an argument, rounds a floating-point number to one decimal
place -- but only if there's a decimal part to be displayed. For example:
-======== ======================= ======
-value Template Output
-======== ======================= ======
-34.23234 {{ value|floatformat }} 34.2
-34.00000 {{ value|floatformat }} 34
-34.26000 {{ value|floatformat }} 34.3
-======== ======================= ======
+============ =========================== ========
+``value`` Template Output
+============ =========================== ========
+``34.23234`` ``{{ value|floatformat }}`` ``34.2``
+``34.00000`` ``{{ value|floatformat }}`` ``34``
+``34.26000`` ``{{ value|floatformat }}`` ``34.3``
+============ =========================== ========
If used with a numeric integer argument, ``floatformat`` rounds a number to
-that many decimal places. For example:
+that many decimal places. For example:
-======== ========================= ======
-value Template Output
-======== ========================= ======
-34.23234 {{ value|floatformat:3 }} 34.232
-34.00000 {{ value|floatformat:3 }} 34.000
-34.26000 {{ value|floatformat:3 }} 34.260
-======== ========================= ======
+============ ============================= ==========
+``value`` Template Output
+============ ============================= ==========
+``34.23234`` ``{{ value|floatformat:3 }}`` ``34.232``
+``34.00000`` ``{{ value|floatformat:3 }}`` ``34.000``
+``34.26000`` ``{{ value|floatformat:3 }}`` ``34.260``
+============ ============================= ==========
If the argument passed to ``floatformat`` is negative, it will round a number
to that many decimal places -- but only if there's a decimal part to be
displayed. For example:
-======== ============================ ======
-value Template Output
-======== ============================ ======
-34.23234 {{ value|floatformat:"-3" }} 34.232
-34.00000 {{ value|floatformat:"-3" }} 34
-34.26000 {{ value|floatformat:"-3" }} 34.260
-======== ============================ ======
+============ ================================ ==========
+``value`` Template Output
+============ ================================ ==========
+``34.23234`` ``{{ value|floatformat:"-3" }}`` ``34.232``
+``34.00000`` ``{{ value|floatformat:"-3" }}`` ``34``
+``34.26000`` ``{{ value|floatformat:"-3" }}`` ``34.260``
+============ ================================ ==========
Using ``floatformat`` with no argument is equivalent to using ``floatformat``
with an argument of ``-1``.
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index 64b67a1333..4865f65331 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -727,134 +727,144 @@ Filters and auto-escaping
**New in Django development version**
-When you are writing a custom filter, you need to give some thought to how
-this filter will interact with Django's auto-escaping behaviour. Firstly, you
-should realise that there are three types of strings that can be passed around
-inside the template code:
-
- * raw strings are the native Python ``str`` or ``unicode`` types. On
- output, they are escaped if auto-escaping is in effect and presented
- unchanged, otherwise.
-
- * "safe" strings are strings that are safe from further escaping at output
- time. Any necessary escaping has already been done. They are commonly used
- for output that contains raw HTML that is intended to be intrepreted on the
- client side.
-
- Internally, these strings are of type ``SafeString`` or ``SafeUnicode``,
- although they share a common base class in ``SafeData``, so you can test
- for them using code like::
-
- if isinstance(value, SafeData):
- # Do something with the "safe" string.
-
- * strings which are marked as "needing escaping" are *always* escaped on
- output, regardless of whether they are in an ``autoescape`` block or not.
- These strings are only escaped once, however, even if auto-escaping
- applies. This type of string is internally represented by the types
- ``EscapeString`` and ``EscapeUnicode``. You will not normally need to worry
- about these; they exist for the implementation of the ``escape`` filter.
-
-When you are writing a filter, your code will typically fall into one of two
-situations:
-
- 1. Your filter does not introduce any HTML-unsafe characters (``<``, ``>``,
- ``'``, ``"`` or ``&``) into the result that were not already present. In
- this case, you can let Django take care of all the auto-escaping handling
- for you. All you need to do is put the ``is_safe`` attribute on your
- filter function and set it to ``True``. This attribute tells Django that
- is a "safe" string is passed into your filter, the result will still be
- "safe" and if a non-safe string is passed in, Django will automatically
- escape it, if necessary. The reason ``is_safe`` is necessary is because
- there are plenty of normal string operations that will turn a ``SafeData``
- object back into a normal ``str`` or ``unicode`` object and, rather than
- try to catch them all, which would be very difficult, Django repairs the
- damage after the filter has completed.
-
- For example, suppose you have a filter that adds the string ``xx`` to the
- end of any input. Since this introduces no dangerous HTML characters into
- the result (aside from any that were already present), you should mark
- your filter with ``is_safe``::
+When writing a custom filter, give some thought to how the filter will interact
+with Django's auto-escaping behavior. Note that three types of strings can be
+passed around inside the template code:
+
+ * **Raw strings** are the native Python ``str`` or ``unicode`` types. On
+ output, they're escaped if auto-escaping is in effect and presented
+ unchanged, otherwise.
+
+ * **Safe strings** are strings that have been marked safe from further
+ escaping at output time. Any necessary escaping has already been done.
+ They're commonly used for output that contains raw HTML that is intended
+ to be interpreted as-is on the client side.
+
+ Internally, these strings are of type ``SafeString`` or ``SafeUnicode``.
+ They share a common base class of ``SafeData``, so you can test
+ for them using code like::
+
+ if isinstance(value, SafeData):
+ # Do something with the "safe" string.
+
+ * **Strings marked as "needing escaping"** are *always* escaped on
+ output, regardless of whether they are in an ``autoescape`` block or not.
+ These strings are only escaped once, however, even if auto-escaping
+ applies.
+
+ Internally, these strings are of type ``EscapeString`` or
+ ``EscapeUnicode``. Generally you don't have to worry about these; they
+ exist for the implementation of the ``escape`` filter.
+
+Template filter code falls into one of two situations:
+
+ 1. Your filter does not introduce any HTML-unsafe characters (``<``, ``>``,
+ ``'``, ``"`` or ``&``) into the result that were not already present. In
+ this case, you can let Django take care of all the auto-escaping
+ handling for you. All you need to do is put the ``is_safe`` attribute on
+ your filter function and set it to ``True``, like so::
+
+ @register.filter
+ def myfilter(value):
+ return value
+ myfilter.is_safe = True
+
+ This attribute tells Django that if a "safe" string is passed into your
+ filter, the result will still be "safe" and if a non-safe string is
+ passed in, Django will automatically escape it, if necessary.
+
+ You can think of this as meaning "this filter is safe -- it doesn't
+ introduce any possibility of unsafe HTML."
+
+ The reason ``is_safe`` is necessary is because there are plenty of
+ normal string operations that will turn a ``SafeData`` object back into
+ a normal ``str`` or ``unicode`` object and, rather than try to catch
+ them all, which would be very difficult, Django repairs the damage after
+ the filter has completed.
+
+ For example, suppose you have a filter that adds the string ``xx`` to the
+ end of any input. Since this introduces no dangerous HTML characters to
+ the result (aside from any that were already present), you should mark
+ your filter with ``is_safe``::
@register.filter
def add_xx(value):
return '%sxx' % value
add_xx.is_safe = True
- When this filter is used in a template where auto-escaping is enabled,
- Django will escape the output whenever the input is not already marked as
- "safe".
-
- By default, ``is_safe`` defaults to ``False`` and you can omit it from
- any filters where it isn't required.
-
- Be careful when deciding if your filter really does leave safe strings
- as safe. Sometimes if you are *removing* characters, you can
- inadvertently leave unbalanced HTML tags or entities in the result.
- For example, removing a ``>`` from the input might turn ``<a>`` into
- ``<a``, which would need to be escaped on output to avoid causing
- problems. Similarly, removing a semicolon (``;``) can turn ``&amp;``
- into ``&amp``, which is no longer a valid entity and thus needs
- further escaping. Most cases won't be nearly this tricky, but keep an
- eye out for any problems like that when reviewing your code.
-
- 2. Alternatively, your filter code can manually take care of any necessary
- escaping. This is usually necessary when you are introducing new HTML
- markup into the result. You want to mark the output as safe from further
- escaping so that your HTML markup isn't escaped further, so you'll need to
- handle the input yourself.
-
- To mark the output as a safe string, use
- ``django.utils.safestring.mark_safe()``.
-
- Be careful, though. You need to do more than just mark the output as
- safe. You need to ensure it really *is* safe and what you do will often
- depend upon whether or not auto-escaping is in effect. The idea is to
- write filters than can operate in templates where auto-escaping is either
- on or off in order to make things easier for your template authors.
-
- In order for you filter to know the current auto-escaping state, set the
- ``needs_autoescape`` attribute to ``True`` on your function (if you don't
- specify this attribute, it defaults to ``False``). This attribute tells
- Django that your filter function wants to be passed an extra keyword
- argument, called ``autoescape`` that is ``True`` is auto-escaping is in
- effect and ``False`` otherwise.
-
- An example might make this clearer. Let's write a filter that emphasizes
- the first character of a string::
-
- from django.utils.html import conditional_escape
- from django.utils.safestring import mark_safe
-
- def initial_letter_filter(text, autoescape=None):
- first, other = text[0] ,text[1:]
- if autoescape:
- esc = conditional_escape
- else:
- esc = lambda x: x
- result = '<strong>%s</strong>%s' % (esc(first), esc(other))
- return mark_safe(result)
- initial_letter_filter.needs_autoescape = True
-
- The ``needs_autoescape`` attribute on the filter function and the
- ``autoescape`` keyword argument mean that our function will know whether
- or not automatic escaping is in effect when the filter is called. We use
- ``autoescape`` to decide whether the input data needs to be passed through
- ``django.utils.html.conditional_escape`` or not (in the latter case, we
- just use the identity function as the "escape" function). The
- ``conditional_escape()`` function is like ``escape()`` except it only
- escapes input that is **not** a ``SafeData`` instance. If a ``SafeData``
- instance is passed to ``conditional_escape()``, the data is returned
- unchanged.
-
- Finally, in the above example, we remember to mark the result as safe
- so that our HTML is inserted directly into the template without further
- escaping.
-
- There is no need to worry about the ``is_safe`` attribute in this case
- (although including it wouldn't hurt anything). Whenever you are manually
- handling the auto-escaping issues and returning a safe string, the
- ``is_safe`` attribute won't change anything either way.
+ When this filter is used in a template where auto-escaping is enabled,
+ Django will escape the output whenever the input is not already marked as
+ "safe".
+
+ By default, ``is_safe`` defaults to ``False``, and you can omit it from
+ any filters where it isn't required.
+
+ Be careful when deciding if your filter really does leave safe strings
+ as safe. If you're *removing* characters, you might inadvertently leave
+ unbalanced HTML tags or entities in the result. For example, removing a
+ ``>`` from the input might turn ``<a>`` into ``<a``, which would need to
+ be escaped on output to avoid causing problems. Similarly, removing a
+ semicolon (``;``) can turn ``&amp;`` into ``&amp``, which is no longer a
+ valid entity and thus needs further escaping. Most cases won't be nearly
+ this tricky, but keep an eye out for any problems like that when
+ reviewing your code.
+
+ 2. Alternatively, your filter code can manually take care of any necessary
+ escaping. This is necessary when you're introducing new HTML markup into
+ the result. You want to mark the output as safe from further
+ escaping so that your HTML markup isn't escaped further, so you'll need
+ to handle the input yourself.
+
+ To mark the output as a safe string, use ``django.utils.safestring.mark_safe()``.
+
+ Be careful, though. You need to do more than just mark the output as
+ safe. You need to ensure it really *is* safe, and what you do depends on
+ whether auto-escaping is in effect. The idea is to write filters than
+ can operate in templates where auto-escaping is either on or off in
+ order to make things easier for your template authors.
+
+ In order for you filter to know the current auto-escaping state, set the
+ ``needs_autoescape`` attribute to ``True`` on your function. (If you
+ don't specify this attribute, it defaults to ``False``). This attribute
+ tells Django that your filter function wants to be passed an extra
+ keyword argument, called ``autoescape``, that is ``True`` is
+ auto-escaping is in effect and ``False`` otherwise.
+
+ For example, let's write a filter that emphasizes the first character of
+ a string::
+
+ from django.utils.html import conditional_escape
+ from django.utils.safestring import mark_safe
+
+ def initial_letter_filter(text, autoescape=None):
+ first, other = text[0], text[1:]
+ if autoescape:
+ esc = conditional_escape
+ else:
+ esc = lambda x: x
+ result = '<strong>%s</strong>%s' % (esc(first), esc(other))
+ return mark_safe(result)
+ initial_letter_filter.needs_autoescape = True
+
+ The ``needs_autoescape`` attribute on the filter function and the
+ ``autoescape`` keyword argument mean that our function will know whether
+ automatic escaping is in effect when the filter is called. We use
+ ``autoescape`` to decide whether the input data needs to be passed through
+ ``django.utils.html.conditional_escape`` or not. (In the latter case, we
+ just use the identity function as the "escape" function.) The
+ ``conditional_escape()`` function is like ``escape()`` except it only
+ escapes input that is **not** a ``SafeData`` instance. If a ``SafeData``
+ instance is passed to ``conditional_escape()``, the data is returned
+ unchanged.
+
+ Finally, in the above example, we remember to mark the result as safe
+ so that our HTML is inserted directly into the template without further
+ escaping.
+
+ There's no need to worry about the ``is_safe`` attribute in this case
+ (although including it wouldn't hurt anything). Whenever you manually
+ handle the auto-escaping issues and return a safe string, the
+ ``is_safe`` attribute won't change anything either way.
Writing custom template tags
----------------------------
@@ -981,7 +991,7 @@ Auto-escaping considerations
The output from template tags is **not** automatically run through the
auto-escaping filters. However, there are still a couple of things you should
-keep in mind when writing a template tag:
+keep in mind when writing a template tag.
If the ``render()`` function of your template stores the result in a context
variable (rather than returning the result in a string), it should take care
@@ -991,18 +1001,17 @@ time, so content that should be safe from further escaping needs to be marked
as such.
Also, if your template tag creates a new context for performing some
-sub-rendering, you should be careful to set the auto-escape attribute to the
-current context's value. The ``__init__`` method for the ``Context`` class
-takes a parameter called ``autoescape`` that you can use for this purpose. For
-example::
+sub-rendering, set the auto-escape attribute to the current context's value.
+The ``__init__`` method for the ``Context`` class takes a parameter called
+``autoescape`` that you can use for this purpose. For example::
def render(self, context):
# ...
new_context = Context({'var': obj}, autoescape=context.autoescape)
# ... Do something with new_context ...
-This is not a very common situation, but it is sometimes useful, particularly
-if you are rendering a template yourself. For example::
+This is not a very common situation, but it's useful if you're rendering a
+template yourself. For example::
def render(self, context):
t = template.load_template('small_fragment.html')
@@ -1010,7 +1019,7 @@ if you are rendering a template yourself. For example::
If we had neglected to pass in the current ``context.autoescape`` value to our
new ``Context`` in this example, the results would have *always* been
-automatically escaped, which may not be the desired behaviour if the template
+automatically escaped, which may not be the desired behavior if the template
tag is used inside a ``{% autoescape off %}`` block.
Registering the tag
diff --git a/docs/tutorial02.txt b/docs/tutorial02.txt
index d96c38f2bd..42c9800591 100644
--- a/docs/tutorial02.txt
+++ b/docs/tutorial02.txt
@@ -48,7 +48,7 @@ Recall from Tutorial 1 that you start the development server like so::
Now, open a Web browser and go to "/admin/" on your local domain -- e.g.,
http://127.0.0.1:8000/admin/. You should see the admin's login screen:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin01.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin01.png
:alt: Django admin login screen
Enter the admin site
@@ -57,9 +57,9 @@ Enter the admin site
Now, try logging in. (You created a superuser account in the first part of this
tutorial, remember?) You should see the Django admin index page:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin02t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin02t.png
:alt: Django admin index page
- :target: http://media.djangoproject.com/img/doc/tutorial/admin02.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin02.png
You should see a few other types of editable content, including groups, users
and sites. These are core features Django ships with by default.
@@ -95,23 +95,23 @@ Explore the free admin functionality
Now that ``Poll`` has the inner ``Admin`` class, Django knows that it should be
displayed on the admin index page:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin03t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin03t.png
:alt: Django admin index page, now with polls displayed
- :target: http://media.djangoproject.com/img/doc/tutorial/admin03.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin03.png
Click "Polls." Now you're at the "change list" page for polls. This page
displays all the polls in the database and lets you choose one to change it.
There's the "What's up?" poll we created in the first tutorial:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin04t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04t.png
:alt: Polls change list page
- :target: http://media.djangoproject.com/img/doc/tutorial/admin04.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04.png
Click the "What's up?" poll to edit it:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin05t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin05t.png
:alt: Editing form for poll object
- :target: http://media.djangoproject.com/img/doc/tutorial/admin05.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin05.png
Things to note here:
@@ -138,9 +138,9 @@ click "Save and continue editing." Then click "History" in the upper right.
You'll see a page listing all changes made to this object via the Django admin,
with the timestamp and username of the person who made the change:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin06t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin06t.png
:alt: History page for poll object
- :target: http://media.djangoproject.com/img/doc/tutorial/admin06.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin06.png
Customize the admin form
========================
@@ -157,7 +157,7 @@ Let's customize this a bit. We can reorder the fields by explicitly adding a
That made the "Publication date" show up first instead of second:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin07.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin07.png
:alt: Fields have been reordered
This isn't impressive with only two fields, but for admin forms with dozens
@@ -175,9 +175,9 @@ up into fieldsets::
The first element of each tuple in ``fields`` is the title of the fieldset.
Here's what our form looks like now:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin08t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin08t.png
:alt: Form has fieldsets now
- :target: http://media.djangoproject.com/img/doc/tutorial/admin08.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin08.png
You can assign arbitrary HTML classes to each fieldset. Django provides a
``"collapse"`` class that displays a particular fieldset initially collapsed.
@@ -190,7 +190,7 @@ aren't commonly used::
('Date information', {'fields': ('pub_date',), 'classes': 'collapse'}),
)
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin09.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin09.png
:alt: Fieldset is initially collapsed
Adding related objects
@@ -213,7 +213,7 @@ that would look like::
Now "Choices" is an available option in the Django admin. The "Add choice" form
looks like this:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin10.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin10.png
:alt: Choice admin page
In that form, the "Poll" field is a select box containing every poll in the
@@ -250,9 +250,9 @@ deletion of that existing Choice object."
Load the "Add poll" page to see how that looks:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin11t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin11t.png
:alt: Add poll page now has choices on it
- :target: http://media.djangoproject.com/img/doc/tutorial/admin11.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin11.png
It works like this: There are three slots for related Choices -- as specified
by ``num_in_admin`` -- but each time you come back to the "Change" page for an
@@ -270,7 +270,7 @@ alternate way of displaying inline related objects::
With that ``edit_inline=models.TABULAR`` (instead of ``models.STACKED``), the
related objects are displayed in a more compact, table-based format:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin12.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin12.png
:alt: Add poll page now has more compact choices
Customize the admin change list
@@ -281,9 +281,9 @@ Now that the Poll admin page is looking good, let's make some tweaks to the
Here's what it looks like at this point:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin04t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04t.png
:alt: Polls change list page
- :target: http://media.djangoproject.com/img/doc/tutorial/admin04.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin04.png
By default, Django displays the ``str()`` of each object. But sometimes it'd
be more helpful if we could display individual fields. To do that, use the
@@ -303,9 +303,9 @@ method from Tutorial 1::
Now the poll change list page looks like this:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin13t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin13t.png
:alt: Polls change list page, updated
- :target: http://media.djangoproject.com/img/doc/tutorial/admin13.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin13.png
You can click on the column headers to sort by those values -- except in the
case of the ``was_published_today`` header, because sorting by the output of
@@ -327,9 +327,9 @@ following line to ``Poll.Admin``::
That adds a "Filter" sidebar that lets people filter the change list by the
``pub_date`` field:
-.. image:: http://media.djangoproject.com/img/doc/tutorial/admin14t.png
+.. image:: http://media.djangoproject.com/img/doc/tutorial-trunk/admin14t.png
:alt: Polls change list page, updated
- :target: http://media.djangoproject.com/img/doc/tutorial/admin14.png
+ :target: http://media.djangoproject.com/img/doc/tutorial-trunk/admin14.png
The type of filter displayed depends on the type of field you're filtering on.
Because ``pub_date`` is a DateTimeField, Django knows to give the default
diff --git a/tests/modeltests/empty/models.py b/tests/modeltests/empty/models.py
index 2493b53837..d57087134e 100644
--- a/tests/modeltests/empty/models.py
+++ b/tests/modeltests/empty/models.py
@@ -1,5 +1,5 @@
"""
-39. Empty model tests
+40. Empty model tests
These test that things behave sensibly for the rare corner-case of a model with
no fields.
diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py
index 1132f1ca41..fe80cce406 100644
--- a/tests/modeltests/field_defaults/models.py
+++ b/tests/modeltests/field_defaults/models.py
@@ -48,4 +48,9 @@ u'Default headline'
>>> d = now - a.pub_date
>>> d.seconds < 5
True
+
+# make sure that SafeUnicode fields work
+>>> from django.utils.safestring import SafeUnicode
+>>> a.headline = SafeUnicode(u'SafeUnicode Headline')
+>>> a.save()
"""}
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index ce1d824ca8..ff86823d07 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -18,42 +18,42 @@ class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
-
+
content_object = generic.GenericForeignKey()
-
+
class Meta:
ordering = ["tag"]
-
+
def __unicode__(self):
return self.tag
class Animal(models.Model):
common_name = models.CharField(max_length=150)
latin_name = models.CharField(max_length=150)
-
+
tags = generic.GenericRelation(TaggedItem)
def __unicode__(self):
return self.common_name
-
+
class Vegetable(models.Model):
name = models.CharField(max_length=150)
is_yucky = models.BooleanField(default=True)
-
+
tags = generic.GenericRelation(TaggedItem)
-
+
def __unicode__(self):
return self.name
-
+
class Mineral(models.Model):
name = models.CharField(max_length=150)
hardness = models.PositiveSmallIntegerField()
-
+
# note the lack of an explicit GenericRelation here...
-
+
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")
@@ -117,13 +117,13 @@ __test__ = {'API_TESTS':"""
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
[(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
-# If Generic Relation is not explicitly defined, any related objects
+# 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', <ContentType: mineral>, 1), (u'fatty', <ContentType: vegetable>, 2), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
-# If you delete a tag, the objects using the tag are unaffected
+# 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()
@@ -132,4 +132,8 @@ __test__ = {'API_TESTS':"""
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
[(u'clearish', <ContentType: mineral>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 2)]
+>>> ctype = ContentType.objects.get_for_model(lion)
+>>> Animal.objects.filter(tags__content_type=ctype)
+[<Animal: Platypus>]
+
"""}
diff --git a/tests/modeltests/get_object_or_404/models.py b/tests/modeltests/get_object_or_404/models.py
index bd800317d3..d9f276b024 100644
--- a/tests/modeltests/get_object_or_404/models.py
+++ b/tests/modeltests/get_object_or_404/models.py
@@ -78,7 +78,7 @@ Http404: No Article matches the given query.
>>> get_object_or_404(Author.objects.all())
Traceback (most recent call last):
...
-AssertionError: get() returned more than one Author -- it returned ...! Lookup parameters were {}
+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")
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py
index b746af6dba..8a480a2381 100644
--- a/tests/modeltests/invalid_models/models.py
+++ b/tests/modeltests/invalid_models/models.py
@@ -108,6 +108,10 @@ class Car(models.Model):
colour = models.CharField(max_length=5)
model = models.ForeignKey(Model)
+class MissingRelations(models.Model):
+ rel1 = models.ForeignKey("Rel1")
+ rel2 = models.ManyToManyField("Rel2")
+
model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute.
@@ -191,4 +195,6 @@ invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related
invalid_models.selfclashm2m: Accessor for m2m field 'm2m_4' clashes with related m2m field 'SelfClashM2M.selfclashm2m_set'. Add a related_name argument to the definition for 'm2m_4'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_3' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_3'.
invalid_models.selfclashm2m: Reverse query name for m2m field 'm2m_4' clashes with field 'SelfClashM2M.selfclashm2m'. Add a related_name argument to the definition for 'm2m_4'.
+invalid_models.missingrelations: 'rel2' has m2m relation with model Rel2, which has not been installed
+invalid_models.missingrelations: 'rel1' has relation with model Rel1, which has not been installed
"""
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index 9b0126ff4f..17c3b3551c 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -1,25 +1,10 @@
"""
-36. Generating HTML forms from models
-
-Django provides shortcuts for creating Form objects from a model class and a
-model instance.
-
-The function django.newforms.form_for_model() takes a model class and returns
-a Form that is tied to the model. This Form works just like any other Form,
-with one additional method: save(). The save() method creates an instance
-of the model and returns that newly created instance. It saves the instance to
-the database if save(commit=True), which is default. If you pass
-commit=False, then you'll get the object without committing the changes to the
-database.
-
-The function django.newforms.form_for_instance() takes a model instance and
-returns a Form that is tied to the instance. This form works just like any
-other Form, with one additional method: save(). The save()
-method updates the model instance. It also takes a commit=True parameter.
-
-The function django.newforms.save_instance() takes a bound form instance and a
-model instance and saves the form's cleaned_data into the instance. It also takes
-a commit=True parameter.
+XX. Generating HTML forms from models
+
+This is mostly just a reworking of the form_for_model/form_for_instance tests
+to use ModelForm. As such, the text may not make sense in all cases, and the
+examples are probably a poor fit for the ModelForm syntax. In other words,
+most of these tests should be rewritten.
"""
from django.db import models
@@ -30,23 +15,6 @@ ARTICLE_STATUS = (
(3, 'Live'),
)
-STEERING_TYPE = (
- ('left', 'Left steering wheel'),
- ('right', 'Right steering wheel'),
-)
-
-FUEL_TYPE = (
- ('gas', 'Gasoline'),
- ('diesel', 'Diesel'),
- ('other', 'Other'),
-)
-
-TRANSMISSION_TYPE = (
- ('at', 'Automatic'),
- ('mt', 'Manual'),
- ('cvt', 'CVT'),
-)
-
class Category(models.Model):
name = models.CharField(max_length=20)
slug = models.SlugField(max_length=20)
@@ -87,20 +55,124 @@ class PhoneNumber(models.Model):
def __unicode__(self):
return self.phone
-class Car(models.Model):
- name = models.CharField(max_length=50)
- steering = models.CharField(max_length=5, choices=STEERING_TYPE, default='left')
- fuel = models.CharField(max_length=10, choices=FUEL_TYPE)
- transmission = models.CharField(max_length=3, choices=TRANSMISSION_TYPE, blank=True, help_text='Leave empty if not applicable.')
-
__test__ = {'API_TESTS': """
->>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField
+>>> from django import newforms as forms
+>>> from django.newforms.models import ModelForm
+
+The bare bones, absolutely nothing custom, basic case.
+
+>>> class CategoryForm(ModelForm):
+... class Meta:
+... model = Category
+>>> CategoryForm.base_fields.keys()
+['name', 'slug', 'url']
+
+
+Extra fields.
+
+>>> class CategoryForm(ModelForm):
+... some_extra_field = forms.BooleanField()
+...
+... class Meta:
+... model = Category
+
+>>> CategoryForm.base_fields.keys()
+['name', 'slug', 'url', 'some_extra_field']
+
+
+Replacing a field.
+
+>>> class CategoryForm(ModelForm):
+... url = forms.BooleanField()
+...
+... class Meta:
+... model = Category
+
+>>> CategoryForm.base_fields['url'].__class__
+<class 'django.newforms.fields.BooleanField'>
+
+
+Using 'fields'.
+
+>>> class CategoryForm(ModelForm):
+...
+... class Meta:
+... model = Category
+... fields = ['url']
+
+>>> CategoryForm.base_fields.keys()
+['url']
+
+
+Using 'exclude'
+
+>>> class CategoryForm(ModelForm):
+...
+... class Meta:
+... model = Category
+... exclude = ['url']
+
+>>> CategoryForm.base_fields.keys()
+['name', 'slug']
+
+
+Using 'fields' *and* 'exclude'. Not sure why you'd want to do this, but uh,
+"be liberal in what you accept" and all.
+
+>>> class CategoryForm(ModelForm):
+...
+... class Meta:
+... model = Category
+... fields = ['name', 'url']
+... exclude = ['url']
+
+>>> CategoryForm.base_fields.keys()
+['name']
+
+Don't allow more than one 'model' definition in the inheritance hierarchy.
+Technically, it would generate a valid form, but the fact that the resulting
+save method won't deal with multiple objects is likely to trip up people not
+familiar with the mechanics.
+
+>>> class CategoryForm(ModelForm):
+... class Meta:
+... model = Category
+
+>>> class BadForm(CategoryForm):
+... class Meta:
+... model = Article
+Traceback (most recent call last):
+...
+ImproperlyConfigured: BadForm defines a different model than its parent.
+
+>>> class ArticleForm(ModelForm):
+... class Meta:
+... model = Article
+
+>>> class BadForm(ArticleForm, CategoryForm):
+... pass
+Traceback (most recent call last):
+...
+ImproperlyConfigured: BadForm's base classes define more than one model.
+
+This one is OK since the subclass specifies the same model as the parent.
+
+>>> class SubCategoryForm(CategoryForm):
+... class Meta:
+... model = Category
+
+
+# Old form_for_x tests #######################################################
+
+>>> from django.newforms import ModelForm, CharField
>>> import datetime
>>> Category.objects.all()
[]
->>> CategoryForm = form_for_model(Category)
+>>> class CategoryForm(ModelForm):
+... class Meta:
+... model = Category
>>> f = CategoryForm()
>>> print f
<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
@@ -184,7 +256,9 @@ Create a couple of Writers.
ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any
fields with the 'choices' attribute are represented by a ChoiceField.
->>> ArticleForm = form_for_model(Article)
+>>> class ArticleForm(ModelForm):
+... class Meta:
+... model = Article
>>> f = ArticleForm(auto_id=False)
>>> print f
<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
@@ -214,28 +288,23 @@ model created with such a form, you need to ensure that the fields
that are _not_ on the form have default values, or are allowed to have
a value of None. If a field isn't specified on a form, the object created
from the form can't provide a value for that field!
->>> PartialArticleForm = form_for_model(Article, fields=('headline','pub_date'))
+>>> class PartialArticleForm(ModelForm):
+... class Meta:
+... model = Article
+... fields = ('headline','pub_date')
>>> f = PartialArticleForm(auto_id=False)
>>> print f
<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
<tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
-You can pass a custom Form class to form_for_model. Make sure it's a
-subclass of BaseForm, not Form.
->>> class CustomForm(BaseForm):
-... def say_hello(self):
-... print 'hello'
->>> CategoryForm = form_for_model(Category, form=CustomForm)
->>> f = CategoryForm()
->>> f.say_hello()
-hello
-
Use form_for_instance to create a Form from a model instance. The difference
between this Form and one created via form_for_model is that the object's
current values are inserted as 'initial' data in each Field.
>>> w = Writer.objects.get(name='Mike Royko')
->>> RoykoForm = form_for_instance(w)
->>> f = RoykoForm(auto_id=False)
+>>> class RoykoForm(ModelForm):
+... class Meta:
+... model = Writer
+>>> f = RoykoForm(auto_id=False, instance=w)
>>> print f
<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr>
@@ -243,8 +312,10 @@ current values are inserted as 'initial' data in each Field.
>>> art.save()
>>> art.id
1
->>> TestArticleForm = form_for_instance(art)
->>> f = TestArticleForm(auto_id=False)
+>>> class TestArticleForm(ModelForm):
+... class Meta:
+... model = Article
+>>> f = TestArticleForm(auto_id=False, instance=art)
>>> print f.as_ul()
<li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li>
<li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li>
@@ -266,7 +337,7 @@ current values are inserted as 'initial' data in each Field.
<option value="2">It&#39;s a test</option>
<option value="3">Third test</option>
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
->>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'})
+>>> f = TestArticleForm({'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}, instance=art)
>>> f.is_valid()
True
>>> test_art = f.save()
@@ -278,8 +349,11 @@ u'Test headline'
You can create a form over a subset of the available fields
by specifying a 'fields' argument to form_for_instance.
->>> PartialArticleForm = form_for_instance(art, fields=('headline', 'slug', 'pub_date'))
->>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False)
+>>> class PartialArticleForm(ModelForm):
+... class Meta:
+... model = Article
+... fields=('headline', 'slug', 'pub_date')
+>>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False, instance=art)
>>> print f.as_ul()
<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
@@ -299,8 +373,10 @@ Add some categories and test the many-to-many form output.
>>> new_art.categories.add(Category.objects.get(name='Entertainment'))
>>> new_art.categories.all()
[<Category: Entertainment>]
->>> TestArticleForm = form_for_instance(new_art)
->>> f = TestArticleForm(auto_id=False)
+>>> class TestArticleForm(ModelForm):
+... class Meta:
+... model = Article
+>>> f = TestArticleForm(auto_id=False, instance=new_art)
>>> print f.as_ul()
<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
@@ -324,7 +400,7 @@ Add some categories and test the many-to-many form output.
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
-... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']})
+... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}, instance=new_art)
>>> new_art = f.save()
>>> new_art.id
1
@@ -334,7 +410,7 @@ Add some categories and test the many-to-many form output.
Now, submit form data with no categories. This deletes the existing categories.
>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
-... 'writer': u'1', 'article': u'Hello.'})
+... 'writer': u'1', 'article': u'Hello.'}, instance=new_art)
>>> new_art = f.save()
>>> new_art.id
1
@@ -343,7 +419,9 @@ Now, submit form data with no categories. This deletes the existing categories.
[]
Create a new article, with categories, via the form.
->>> ArticleForm = form_for_model(Article)
+>>> class ArticleForm(ModelForm):
+... class Meta:
+... model = Article
>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
>>> new_art = f.save()
@@ -354,7 +432,9 @@ Create a new article, with categories, via the form.
[<Category: Entertainment>, <Category: It's a test>]
Create a new article, with no categories, via the form.
->>> ArticleForm = form_for_model(Article)
+>>> class ArticleForm(ModelForm):
+... class Meta:
+... model = Article
>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
... 'writer': u'1', 'article': u'Test.'})
>>> new_art = f.save()
@@ -366,7 +446,9 @@ Create a new article, with no categories, via the form.
Create a new article, with categories, via the form, but use commit=False.
The m2m data won't be saved until save_m2m() is invoked on the form.
->>> ArticleForm = form_for_model(Article)
+>>> class ArticleForm(ModelForm):
+... class Meta:
+... model = Article
>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01',
... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
>>> new_art = f.save(commit=False)
@@ -386,10 +468,10 @@ The m2m data won't be saved until save_m2m() is invoked on the form.
>>> new_art.categories.order_by('name')
[<Category: Entertainment>, <Category: It's a test>]
-Here, we define a custom Form. Because it happens to have the same fields as
-the Category model, we can use save_instance() to apply its changes to an
+Here, we define a custom ModelForm. Because it happens to have the same fields as
+the Category model, we can just call the form's save() to apply its changes to an
existing Category instance.
->>> class ShortCategory(Form):
+>>> class ShortCategory(ModelForm):
... name = CharField(max_length=5)
... slug = CharField(max_length=5)
... url = CharField(max_length=3)
@@ -398,8 +480,8 @@ existing Category instance.
<Category: Third test>
>>> cat.id
3
->>> sc = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'})
->>> save_instance(sc, cat)
+>>> form = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}, instance=cat)
+>>> form.save()
<Category: Third>
>>> Category.objects.get(id=3)
<Category: Third>
@@ -407,7 +489,9 @@ existing Category instance.
Here, we demonstrate that choices for a ForeignKey ChoiceField are determined
at runtime, based on the data in the database when the form is displayed, not
the data in the database when the form is instantiated.
->>> ArticleForm = form_for_model(Article)
+>>> class ArticleForm(ModelForm):
+... class Meta:
+... model = Article
>>> f = ArticleForm(auto_id=False)
>>> print f.as_ul()
<li>Headline: <input type="text" name="headline" maxlength="50" /></li>
@@ -609,60 +693,12 @@ ValidationError: [u'Select a valid choice. 4 is not one of the available choices
# PhoneNumberField ############################################################
->>> PhoneNumberForm = form_for_model(PhoneNumber)
+>>> class PhoneNumberForm(ModelForm):
+... class Meta:
+... model = PhoneNumber
>>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'})
>>> f.is_valid()
True
>>> f.cleaned_data
{'phone': u'312-555-1212', 'description': u'Assistance'}
-
-# form_for_* blank choices ####################################################
-
-Show the form for a new Car. Note that steering field doesn't include the blank choice,
-because the field is obligatory and has an explicit default.
->>> CarForm = form_for_model(Car)
->>> f = CarForm(auto_id=False)
->>> print f
-<tr><th>Name:</th><td><input type="text" name="name" maxlength="50" /></td></tr>
-<tr><th>Steering:</th><td><select name="steering">
-<option value="left" selected="selected">Left steering wheel</option>
-<option value="right">Right steering wheel</option>
-</select></td></tr>
-<tr><th>Fuel:</th><td><select name="fuel">
-<option value="" selected="selected">---------</option>
-<option value="gas">Gasoline</option>
-<option value="diesel">Diesel</option>
-<option value="other">Other</option>
-</select></td></tr>
-<tr><th>Transmission:</th><td><select name="transmission">
-<option value="" selected="selected">---------</option>
-<option value="at">Automatic</option>
-<option value="mt">Manual</option>
-<option value="cvt">CVT</option>
-</select><br />Leave empty if not applicable.</td></tr>
-
-Create a Car, and display the form for modifying it. Note that now the fuel
-selector doesn't include the blank choice as well, since the field is
-obligatory and can not be changed to be blank.
->>> honda = Car(name='Honda Accord Wagon', steering='right', fuel='gas', transmission='at')
->>> honda.save()
->>> HondaForm = form_for_instance(honda)
->>> f = HondaForm(auto_id=False)
->>> print f
-<tr><th>Name:</th><td><input type="text" name="name" value="Honda Accord Wagon" maxlength="50" /></td></tr>
-<tr><th>Steering:</th><td><select name="steering">
-<option value="left">Left steering wheel</option>
-<option value="right" selected="selected">Right steering wheel</option>
-</select></td></tr>
-<tr><th>Fuel:</th><td><select name="fuel">
-<option value="gas" selected="selected">Gasoline</option>
-<option value="diesel">Diesel</option>
-<option value="other">Other</option>
-</select></td></tr>
-<tr><th>Transmission:</th><td><select name="transmission">
-<option value="">---------</option>
-<option value="at" selected="selected">Automatic</option>
-<option value="mt">Manual</option>
-<option value="cvt">CVT</option>
-</select><br />Leave empty if not applicable.</td></tr>
"""}
diff --git a/tests/modeltests/select_related/models.py b/tests/modeltests/select_related/models.py
index 43efab3a7d..f0fd121665 100644
--- a/tests/modeltests/select_related/models.py
+++ b/tests/modeltests/select_related/models.py
@@ -1,5 +1,5 @@
"""
-40. Tests for select_related()
+41. Tests for select_related()
``select_related()`` follows all relationships and pre-caches any foreign key
values so that complex trees can be fetched in a single query. However, this
diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py
index a2388223f0..0ccc19f895 100644
--- a/tests/modeltests/serializers/models.py
+++ b/tests/modeltests/serializers/models.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
-41. Serialization
+42. Serialization
``django.core.serializers`` provides interfaces to converting Django querysets
to and from "flat" data (i.e. strings).
@@ -218,3 +218,41 @@ None
3.4
"""}
+
+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
+<BLANKLINE>
+
+>>> obs = list(serializers.deserialize("yaml", serialized))
+>>> for i in obs:
+... print i
+<DeserializedObject: Just kidding; I love TV poker>
+<DeserializedObject: Time to reform copyright>
+
+"""
+except ImportError: pass
+
diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py
index c7aaaff67d..1a6e1bdc18 100644
--- a/tests/modeltests/test_client/models.py
+++ b/tests/modeltests/test_client/models.py
@@ -1,6 +1,6 @@
# coding: utf-8
"""
-38. Testing using the Test Client
+39. Testing using the Test Client
The test client is a class that can act like a simple
browser for testing purposes.
diff --git a/tests/modeltests/user_commands/models.py b/tests/modeltests/user_commands/models.py
index 5f96806dac..6db4b049df 100644
--- a/tests/modeltests/user_commands/models.py
+++ b/tests/modeltests/user_commands/models.py
@@ -1,5 +1,5 @@
"""
-37. User-registered management commands
+38. User-registered management commands
The manage.py utility provides a number of useful commands for managing a
Django project. If you want to add a utility command of your own, you can.
@@ -27,4 +27,4 @@ Traceback (most recent call last):
CommandError: Unknown command: 'explode'
-"""} \ No newline at end of file
+"""}
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index e94ea33139..f050348c77 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -3,8 +3,8 @@
# Unit tests for cache framework
# Uses whatever cache backend is set in the test settings file.
-import time, unittest
-
+import time
+import unittest
from django.core.cache import cache
from django.utils.cache import patch_vary_headers
from django.http import HttpResponse
@@ -27,7 +27,7 @@ class Cache(unittest.TestCase):
cache.add("addkey1", "value")
cache.add("addkey1", "newvalue")
self.assertEqual(cache.get("addkey1"), "value")
-
+
def test_non_existent(self):
# get with non-existent keys
self.assertEqual(cache.get("does_not_exist"), None)
@@ -72,12 +72,20 @@ class Cache(unittest.TestCase):
'function' : f,
'class' : C,
}
+ cache.set("stuff", stuff)
+ self.assertEqual(cache.get("stuff"), stuff)
def test_expiration(self):
- # expiration
- cache.set('expire', 'very quickly', 1)
- time.sleep(2)
- self.assertEqual(cache.get("expire"), None)
+ cache.set('expire1', 'very quickly', 1)
+ cache.set('expire2', 'very quickly', 1)
+ cache.set('expire3', 'very quickly', 1)
+
+ time.sleep(2)
+ self.assertEqual(cache.get("expire1"), None)
+
+ cache.add("expire2", "newvalue")
+ self.assertEqual(cache.get("expire2"), "newvalue")
+ self.assertEqual(cache.has_key("expire3"), False)
def test_unicode(self):
stuff = {
@@ -90,6 +98,44 @@ class Cache(unittest.TestCase):
cache.set(key, value)
self.assertEqual(cache.get(key), value)
+import os
+import md5
+import shutil
+import tempfile
+from django.core.cache.backends.filebased import CacheClass as FileCache
+
+class FileBasedCacheTests(unittest.TestCase):
+ """
+ Specific test cases for the file-based cache.
+ """
+ def setUp(self):
+ self.dirname = tempfile.mktemp()
+ os.mkdir(self.dirname)
+ self.cache = FileCache(self.dirname, {})
+
+ def tearDown(self):
+ shutil.rmtree(self.dirname)
+
+ def test_hashing(self):
+ """Test that keys are hashed into subdirectories correctly"""
+ self.cache.set("foo", "bar")
+ keyhash = md5.new("foo").hexdigest()
+ keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
+ self.assert_(os.path.exists(keypath))
+
+ def test_subdirectory_removal(self):
+ """
+ Make sure that the created subdirectories are correctly removed when empty.
+ """
+ self.cache.set("foo", "bar")
+ keyhash = md5.new("foo").hexdigest()
+ keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:])
+ self.assert_(os.path.exists(keypath))
+
+ self.cache.delete("foo")
+ self.assert_(not os.path.exists(keypath))
+ self.assert_(not os.path.exists(os.path.dirname(keypath)))
+ self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath))))
class CacheUtils(unittest.TestCase):
"""TestCase for django.utils.cache functions."""
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index bfa03cd6e1..668ecb9d5a 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -49,6 +49,18 @@ u'\\\\ : backslashes, too'
>>> capfirst(u'hello world')
u'Hello world'
+>>> escapejs(u'"double quotes" and \'single quotes\'')
+u'\\"double quotes\\" and \\\'single quotes\\\''
+
+>>> escapejs(ur'\ : backslashes, too')
+u'\\\\ : backslashes, too'
+
+>>> escapejs(u'and lots of whitespace: \r\n\t\v\f\b')
+u'and lots of whitespace: \\r\\n\\t\\v\\f\\b'
+
+>>> escapejs(ur'<script>and this</script>')
+u'<script>and this<\\/script>'
+
>>> fix_ampersands(u'Jack & Jill & Jeroboam')
u'Jack &amp; Jill &amp; Jeroboam'
diff --git a/tests/regressiontests/forms/localflavor/za.py b/tests/regressiontests/forms/localflavor/za.py
new file mode 100644
index 0000000000..a948964b8d
--- /dev/null
+++ b/tests/regressiontests/forms/localflavor/za.py
@@ -0,0 +1,40 @@
+tests = r"""
+# ZAIDField #################################################################
+
+ZAIDField validates that the date is a valid birthdate and that the value
+has a valid checksum. It allows spaces and dashes, and returns a plain
+string of digits.
+>>> from django.contrib.localflavor.za.forms import ZAIDField
+>>> f = ZAIDField()
+>>> f.clean('0002290001003')
+'0002290001003'
+>>> f.clean('000229 0001 003')
+'0002290001003'
+>>> f.clean('0102290001001')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid South African ID number']
+>>> f.clean('811208')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid South African ID number']
+>>> f.clean('0002290001004')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid South African ID number']
+
+# ZAPostCodeField ###########################################################
+>>> from django.contrib.localflavor.za.forms import ZAPostCodeField
+>>> f = ZAPostCodeField()
+>>> f.clean('abcd')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid South African postal code']
+>>> f.clean('0000')
+u'0000'
+>>> f.clean(' 7530')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid South African postal code']
+
+"""
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index 6acd4f2e1d..bb0e30b874 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -22,6 +22,7 @@ from localflavor.pl import tests as localflavor_pl_tests
from localflavor.sk import tests as localflavor_sk_tests
from localflavor.uk import tests as localflavor_uk_tests
from localflavor.us import tests as localflavor_us_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
@@ -50,6 +51,7 @@ __test__ = {
'localflavor_sk_tests': localflavor_sk_tests,
'localflavor_uk_tests': localflavor_uk_tests,
'localflavor_us_tests': localflavor_us_tests,
+ 'localflavor_za_tests': localflavor_za_tests,
'regression_tests': regression_tests,
'util_tests': util_tests,
'widgets_tests': widgets_tests,
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
index 2ffc62f90d..94e792cf54 100644
--- a/tests/regressiontests/i18n/tests.py
+++ b/tests/regressiontests/i18n/tests.py
@@ -43,7 +43,7 @@ u'django'
Translating a string requiring no auto-escaping shouldn't change the "safe"
status.
->>> from django.utils.safestring import mark_safe
+>>> from django.utils.safestring import mark_safe, SafeString
>>> s = mark_safe('Password')
>>> type(s)
<class 'django.utils.safestring.SafeString'>
@@ -51,6 +51,19 @@ status.
>>> type(ugettext(s))
<class 'django.utils.safestring.SafeUnicode'>
>>> deactivate()
+
+>>> SafeString('a') + s
+'aPassword'
+>>> s + SafeString('a')
+'Passworda'
+>>> s + mark_safe('a')
+'Passworda'
+>>> mark_safe('a') + s
+'aPassword'
+>>> mark_safe('a') + mark_safe('s')
+'as'
+>>> print s
+Password
"""
__test__ = {
diff --git a/tests/regressiontests/maxlength/tests.py b/tests/regressiontests/maxlength/tests.py
index 8a5f874c78..c7ed1f91c0 100644
--- a/tests/regressiontests/maxlength/tests.py
+++ b/tests/regressiontests/maxlength/tests.py
@@ -22,12 +22,12 @@ Don't print out the deprecation warnings during testing.
>>> legacy_maxlength(10, 12)
Traceback (most recent call last):
...
-TypeError: field can not take both the max_length argument and the legacy maxlength argument.
+TypeError: Field cannot take both the max_length argument and the legacy maxlength argument.
>>> legacy_maxlength(0, 10)
Traceback (most recent call last):
...
-TypeError: field can not take both the max_length argument and the legacy maxlength argument.
+TypeError: Field cannot take both the max_length argument and the legacy maxlength argument.
>>> legacy_maxlength(0, None)
0
@@ -48,7 +48,7 @@ TypeError: field can not take both the max_length argument and the legacy maxlen
>>> fields.Field(maxlength=10, max_length=15)
Traceback (most recent call last):
...
-TypeError: field can not take both the max_length argument and the legacy maxlength argument.
+TypeError: Field cannot take both the max_length argument and the legacy maxlength argument.
# Test max_length
>>> new.max_length
diff --git a/tests/regressiontests/middleware/__init__.py b/tests/regressiontests/middleware/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/middleware/__init__.py
diff --git a/tests/regressiontests/middleware/tests.py b/tests/regressiontests/middleware/tests.py
new file mode 100644
index 0000000000..cb5c29abe1
--- /dev/null
+++ b/tests/regressiontests/middleware/tests.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+
+import unittest
+
+from django.test import TestCase
+from django.http import HttpRequest
+from django.middleware.common import CommonMiddleware
+from django.conf import settings
+
+class CommonMiddlewareTest(TestCase):
+ def _get_request(self, path):
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'testserver',
+ 'SERVER_PORT': 80,
+ }
+ request.path = "/middleware/%s" % path
+ return request
+
+ def test_append_slash_have_slash(self):
+ """
+ tests that urls with slashes go unmolested
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('slash/')
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_slashless_resource(self):
+ """
+ tests that matches to explicit slashless urls go unmolested
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('noslash')
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_slashless_unknown(self):
+ """
+ tests that APPEND_SLASH doesn't redirect to unknown resources
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('unknown')
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_redirect(self):
+ """
+ tests that APPEND_SLASH redirects slashless urls to a valid pattern
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('slash')
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(r['Location'], 'http://testserver/middleware/slash/')
+
+ def test_append_slash_no_redirect_on_POST_in_DEBUG(self):
+ """
+ tests that while in debug mode, an exception is raised with a warning
+ when a failed attempt is made to POST to an url which would normally be
+ redirected to a slashed version
+ """
+ settings.APPEND_SLASH = True
+ settings.DEBUG = True
+ request = self._get_request('slash')
+ request.method = 'POST'
+ self.assertRaises(
+ RuntimeError,
+ CommonMiddleware().process_request,
+ request)
+ try:
+ CommonMiddleware().process_request(request)
+ except RuntimeError, e:
+ self.assertTrue('end in a slash' in str(e))
+
+ def test_append_slash_disabled(self):
+ """
+ tests disabling append slash functionality
+ """
+ settings.APPEND_SLASH = False
+ request = self._get_request('slash')
+ self.assertEquals(CommonMiddleware().process_request(request), None)
+
+ def test_append_slash_quoted(self):
+ """
+ tests that urls which require quoting are redirected to their slash
+ version ok
+ """
+ settings.APPEND_SLASH = True
+ request = self._get_request('needsquoting#')
+ r = CommonMiddleware().process_request(request)
+ self.assertEquals(r.status_code, 301)
+ self.assertEquals(
+ r['Location'],
+ 'http://testserver/middleware/needsquoting%23/')
+
diff --git a/tests/regressiontests/middleware/urls.py b/tests/regressiontests/middleware/urls.py
new file mode 100644
index 0000000000..88a4b37ddc
--- /dev/null
+++ b/tests/regressiontests/middleware/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import patterns
+
+urlpatterns = patterns('',
+ (r'^noslash$', 'view'),
+ (r'^slash/$', 'view'),
+ (r'^needsquoting#/$', 'view'),
+)
diff --git a/tests/regressiontests/model_regress/models.py b/tests/regressiontests/model_regress/models.py
index 00c3bc96f0..02e73a5aa9 100644
--- a/tests/regressiontests/model_regress/models.py
+++ b/tests/regressiontests/model_regress/models.py
@@ -11,6 +11,7 @@ class Article(models.Model):
pub_date = models.DateTimeField()
status = models.IntegerField(blank=True, null=True, choices=CHOICES)
misc_data = models.CharField(max_length=100, blank=True)
+ article_text = models.TextField()
class Meta:
ordering = ('pub_date','headline')
@@ -41,5 +42,14 @@ Empty strings should be returned as Unicode
>>> a2 = Article.objects.get(pk=a.id)
>>> a2.misc_data
u''
+
+# TextFields can hold more than 4000 characters (this was broken in Oracle).
+>>> a3 = Article(headline="Really, really big", pub_date=datetime.now())
+>>> a3.article_text = "ABCDE" * 1000
+>>> a3.save()
+>>> a4 = Article.objects.get(pk=a3.id)
+>>> len(a4.article_text)
+5000
+
"""
}
diff --git a/tests/regressiontests/string_lookup/models.py b/tests/regressiontests/string_lookup/models.py
index 12ebd0cf07..9deeb18763 100644
--- a/tests/regressiontests/string_lookup/models.py
+++ b/tests/regressiontests/string_lookup/models.py
@@ -18,26 +18,26 @@ class Bar(models.Model):
return "Bar %s" % self.place.name
class Whiz(models.Model):
- name = models.CharField(max_length = 50)
+ name = models.CharField(max_length=50)
def __unicode__(self):
return "Whiz %s" % self.name
class Child(models.Model):
parent = models.OneToOneField('Base')
- name = models.CharField(max_length = 50)
+ name = models.CharField(max_length=50)
def __unicode__(self):
return "Child %s" % self.name
class Base(models.Model):
- name = models.CharField(max_length = 50)
+ name = models.CharField(max_length=50)
def __unicode__(self):
return "Base %s" % self.name
class Article(models.Model):
- name = models.CharField(maxlength = 50)
+ name = models.CharField(max_length=50)
text = models.TextField()
def __str__(self):
diff --git a/tests/regressiontests/templates/context.py b/tests/regressiontests/templates/context.py
new file mode 100644
index 0000000000..d8b0f39abe
--- /dev/null
+++ b/tests/regressiontests/templates/context.py
@@ -0,0 +1,18 @@
+# coding: utf-8
+
+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.pop()
+{'a': 2}
+>>> c['a']
+1
+"""
+
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index cbbd88b06c..846023afc9 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -18,11 +18,13 @@ from django.utils.safestring import mark_safe
from django.utils.tzinfo import LocalTimezone
from unicode import unicode_tests
+from context import context_tests
import filters
# Some other tests we would like to run
__test__ = {
'unicode': unicode_tests,
+ 'context': context_tests,
}
#################################
diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py
index 9e0bbb2d66..956432e7d5 100644
--- a/tests/regressiontests/views/views.py
+++ b/tests/regressiontests/views/views.py
@@ -1,7 +1,5 @@
from django.http import HttpResponse
-from django.template import RequestContext
def index_page(request):
"""Dummy index page"""
return HttpResponse('<html><body>Dummy page</body></html>')
-
diff --git a/tests/runtests.py b/tests/runtests.py
index 843850074b..2d3b737cec 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -107,8 +107,7 @@ def django_tests(verbosity, interactive, test_labels):
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.common.CommonMiddleware',
)
- if not hasattr(settings, 'SITE_ID'):
- settings.SITE_ID = 1
+ settings.SITE_ID = 1
# Load all the ALWAYS_INSTALLED_APPS.
# (This import statement is intentionally delayed until after we
diff --git a/tests/urls.py b/tests/urls.py
index d7251007c5..174e06969b 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -14,4 +14,7 @@ urlpatterns = patterns('',
# django built-in views
(r'^views/', include('regressiontests.views.urls')),
+
+ # test urlconf for middleware tests
+ (r'^middleware/', include('regressiontests.middleware.urls')),
)