+
+
{% endif %}
diff --git a/django/contrib/comments/templates/comments/freeform.html b/django/contrib/comments/templates/comments/freeform.html
index 99a02b4d97..f0d00b91c7 100644
--- a/django/contrib/comments/templates/comments/freeform.html
+++ b/django/contrib/comments/templates/comments/freeform.html
@@ -3,9 +3,11 @@
{% endif %}
diff --git a/django/core/management.py b/django/core/management.py
index 04a5b71ac1..3b7b8a403b 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -1006,6 +1006,8 @@ def get_validation_errors(outfile, app=None):
# Check core=True, if needed.
for related in opts.get_followed_related_objects():
+ if not related.edit_inline:
+ continue
try:
for f in related.opts.fields:
if f.core:
@@ -1045,7 +1047,10 @@ def _check_for_validation_errors(app=None):
s = StringIO()
num_errors = get_validation_errors(s, app)
if num_errors:
- sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app))
+ if app:
+ sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app))
+ else:
+ sys.stderr.write(style.ERROR("Error: Couldn't install apps, because there were errors in one or more models:\n"))
s.seek(0)
sys.stderr.write(s.read())
sys.exit(1)
diff --git a/django/db/backends/postgresql/client.py b/django/db/backends/postgresql/client.py
index 3d0d7a0d2a..8123ec7848 100644
--- a/django/db/backends/postgresql/client.py
+++ b/django/db/backends/postgresql/client.py
@@ -2,13 +2,14 @@ from django.conf import settings
import os
def runshell():
- args = ['']
- args += ["-U%s" % settings.DATABASE_USER]
+ args = ['psql']
+ if settings.DATABASE_USER:
+ args += ["-U", settings.DATABASE_USER]
if settings.DATABASE_PASSWORD:
args += ["-W"]
if settings.DATABASE_HOST:
- args += ["-h %s" % settings.DATABASE_HOST]
+ args.extend(["-h", settings.DATABASE_HOST])
if settings.DATABASE_PORT:
- args += ["-p %s" % settings.DATABASE_PORT]
+ args.extend(["-p", str(settings.DATABASE_PORT)])
args += [settings.DATABASE_NAME]
os.execvp('psql', args)
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
index c5fa738249..cdabffdc1c 100644
--- a/django/db/backends/sqlite3/introspection.py
+++ b/django/db/backends/sqlite3/introspection.py
@@ -2,18 +2,56 @@ from django.db import transaction
from django.db.backends.sqlite3.base import quote_name
def get_table_list(cursor):
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
+ "Returns a list of table names in the current database."
+ # Skip the sqlite_sequence system table used for autoincrement key
+ # generation.
+ cursor.execute("""
+ SELECT name FROM sqlite_master
+ WHERE type='table' AND NOT name='sqlite_sequence'
+ ORDER BY name""")
return [row[0] for row in cursor.fetchall()]
def get_table_description(cursor, table_name):
- cursor.execute("PRAGMA table_info(%s)" % quote_name(table_name))
- return [(row[1], row[2], None, None) for row in cursor.fetchall()]
+ "Returns a description of the table, with the DB-API cursor.description interface."
+ return [(info['name'], info['type'], None, None, None, None,
+ info['null_ok']) for info in _table_info(cursor, table_name)]
def get_relations(cursor, table_name):
raise NotImplementedError
def get_indexes(cursor, table_name):
- raise NotImplementedError
+ """
+ Returns a dictionary of fieldname -> infodict for the given table,
+ where each infodict is in the format:
+ {'primary_key': boolean representing whether it's the primary key,
+ 'unique': boolean representing whether it's a unique index}
+ """
+ indexes = {}
+ for info in _table_info(cursor, table_name):
+ indexes[info['name']] = {'primary_key': info['pk'] != 0,
+ 'unique': False}
+ cursor.execute('PRAGMA index_list(%s)' % quote_name(table_name))
+ # seq, name, unique
+ for index, unique in [(field[1], field[2]) for field in cursor.fetchall()]:
+ if not unique:
+ continue
+ cursor.execute('PRAGMA index_info(%s)' % quote_name(index))
+ info = cursor.fetchall()
+ # Skip indexes across multiple fields
+ if len(info) != 1:
+ continue
+ name = info[0][2] # seqno, cid, name
+ indexes[name]['unique'] = True
+ return indexes
+
+def _table_info(cursor, name):
+ cursor.execute('PRAGMA table_info(%s)' % quote_name(name))
+ # cid, name, type, notnull, dflt_value, pk
+ return [{'name': field[1],
+ 'type': field[2],
+ 'null_ok': not field[3],
+ 'pk': field[5] # undocumented
+ } for field in cursor.fetchall()]
# Maps SQL types to Django Field types. Some of the SQL types have multiple
# entries here because SQLite allows for anything and doesn't normalize the
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 883ed7c81a..e40c3350ae 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -44,6 +44,11 @@ class ModelBase(type):
# For 'django.contrib.sites.models', this would be 'sites'.
new_class._meta.app_label = model_module.__name__.split('.')[-2]
+ # Bail out early if we have already created this class.
+ m = get_model(new_class._meta.app_label, name)
+ if m is not None:
+ return m
+
# Add all attributes to the class.
for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj)
@@ -60,10 +65,10 @@ class ModelBase(type):
new_class._prepare()
register_models(new_class._meta.app_label, new_class)
- # Because of the way imports happen (recursively), we may or may not be
- # the first class for this model to register with the framework. There
- # should only be one class for each model, so we must always return the
- # registered version.
+ # Because of the way imports happen (recursively), we may or may not be
+ # the first class for this model to register with the framework. There
+ # should only be one class for each model, so we must always return the
+ # registered version.
return get_model(new_class._meta.app_label, name)
class Model(object):
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 6af00abf28..73275d66a4 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -679,7 +679,7 @@ def get_cached_row(klass, row, index_start):
def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen):
"""
Helper function that recursively populates the select, tables and where (in
- place) for fill-cache queries.
+ place) for select_related queries.
"""
backend = opts.connection_info.backend
for f in opts.fields:
@@ -701,6 +701,7 @@ def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen)
def parse_lookup(kwarg_items, opts):
# Helper function that handles converting API kwargs
# (e.g. "name__exact": "tom") to SQL.
+ # Returns a tuple of (tables, joins, where, params).
# 'joins' is a sorted dictionary describing the tables that must be joined
# to complete the query. The dictionary is sorted because creation order
diff --git a/django/forms/__init__.py b/django/forms/__init__.py
index 1e9cb2c596..4907bd76f7 100644
--- a/django/forms/__init__.py
+++ b/django/forms/__init__.py
@@ -773,15 +773,18 @@ class DatetimeField(TextField):
def html2python(data):
"Converts the field into a datetime.datetime object"
import datetime
- date, time = data.split()
- y, m, d = date.split('-')
- timebits = time.split(':')
- h, mn = timebits[:2]
- if len(timebits) > 2:
- s = int(timebits[2])
- else:
- s = 0
- return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s)
+ try:
+ date, time = data.split()
+ y, m, d = date.split('-')
+ timebits = time.split(':')
+ h, mn = timebits[:2]
+ if len(timebits) > 2:
+ s = int(timebits[2])
+ else:
+ s = 0
+ return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s)
+ except ValueError:
+ return None
html2python = staticmethod(html2python)
class DateField(TextField):
@@ -806,7 +809,7 @@ class DateField(TextField):
time_tuple = time.strptime(data, '%Y-%m-%d')
return datetime.date(*time_tuple[0:3])
except (ValueError, TypeError):
- return data
+ return None
html2python = staticmethod(html2python)
class TimeField(TextField):
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 08f433fec9..a1d1d402d0 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -318,7 +318,7 @@ class Parser(object):
self.tags.update(lib.tags)
self.filters.update(lib.filters)
- def compile_filter(self,token):
+ def compile_filter(self, token):
"Convenient wrapper for FilterExpression"
return FilterExpression(token, self)
@@ -543,11 +543,14 @@ class FilterExpression(object):
raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
self.var, self.filters = var, filters
- def resolve(self, context):
+ def resolve(self, context, ignore_failures=False):
try:
obj = resolve_variable(self.var, context)
except VariableDoesNotExist:
- obj = settings.TEMPLATE_STRING_IF_INVALID
+ if ignore_failures:
+ return None
+ else:
+ return settings.TEMPLATE_STRING_IF_INVALID
for func, args in self.filters:
arg_vals = []
for lookup, arg in args:
@@ -611,7 +614,11 @@ def resolve_variable(path, context):
(The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
"""
- if path[0].isdigit():
+ if path == 'False':
+ current = False
+ elif path == 'True':
+ current = True
+ elif path[0].isdigit():
number_type = '.' in path and float or int
try:
current = number_type(path)
diff --git a/django/template/context.py b/django/template/context.py
index 44a97f95a8..6d9efdc7ec 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -37,7 +37,7 @@ class Context(object):
for d in self.dicts:
if d.has_key(key):
return d[key]
- return settings.TEMPLATE_STRING_IF_INVALID
+ raise KeyError(key)
def __delitem__(self, key):
"Delete a variable from the current context"
@@ -49,7 +49,7 @@ class Context(object):
return True
return False
- def get(self, key, otherwise):
+ def get(self, key, otherwise=None):
for d in self.dicts:
if d.has_key(key):
return d[key]
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 9bd6e7cb3c..5d56ec0c49 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -430,20 +430,32 @@ def filesizeformat(bytes):
return "%.1f MB" % (bytes / (1024 * 1024))
return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
-def pluralize(value):
- "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'"
+def pluralize(value, arg='s'):
+ """
+ Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes'
+ By default, 's' is used as a suffix; if an argument is provided, that string
+ is used instead. If the provided argument contains a comma, the text before
+ the comma is used for the singular case.
+ """
+ if not ',' in arg:
+ arg = ',' + arg
+ bits = arg.split(',')
+ if len(bits) > 2:
+ return ''
+ singular_suffix, plural_suffix = bits[:2]
+
try:
if int(value) != 1:
- return 's'
+ return plural_suffix
except ValueError: # invalid string that's not a number
pass
except TypeError: # value isn't a string or a number; maybe it's a list?
try:
if len(value) != 1:
- return 's'
+ return plural_suffix
except TypeError: # len() of unsized object
pass
- return ''
+ return singular_suffix
def phone2numeric(value):
"Takes a phone number and converts it in to its numerical equivalent"
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 88cb5f68be..0a4fe33d82 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -45,7 +45,10 @@ class FirstOfNode(Node):
def render(self, context):
for var in self.vars:
- value = resolve_variable(var, context)
+ try:
+ value = resolve_variable(var, context)
+ except VariableDoesNotExist:
+ continue
if value:
return str(value)
return ''
@@ -144,8 +147,14 @@ class IfEqualNode(Node):
return ""
def render(self, context):
- val1 = resolve_variable(self.var1, context)
- val2 = resolve_variable(self.var2, context)
+ try:
+ val1 = resolve_variable(self.var1, context)
+ except VariableDoesNotExist:
+ val1 = None
+ try:
+ val2 = resolve_variable(self.var2, context)
+ except VariableDoesNotExist:
+ val2 = None
if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
return self.nodelist_true.render(context)
return self.nodelist_false.render(context)
@@ -177,7 +186,7 @@ class IfNode(Node):
if self.link_type == IfNode.LinkTypes.or_:
for ifnot, bool_expr in self.bool_exprs:
try:
- value = bool_expr.resolve(context)
+ value = bool_expr.resolve(context, True)
except VariableDoesNotExist:
value = None
if (value and not ifnot) or (ifnot and not value):
@@ -186,7 +195,7 @@ class IfNode(Node):
else:
for ifnot, bool_expr in self.bool_exprs:
try:
- value = bool_expr.resolve(context)
+ value = bool_expr.resolve(context, True)
except VariableDoesNotExist:
value = None
if not ((value and not ifnot) or (ifnot and not value)):
diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
index 37b8d93908..6718a0fbac 100644
--- a/django/templatetags/i18n.py
+++ b/django/templatetags/i18n.py
@@ -12,7 +12,7 @@ class GetAvailableLanguagesNode(Node):
def render(self, context):
from django.conf import settings
- context[self.variable] = settings.LANGUAGES
+ context[self.variable] = [(k, translation.gettext(v)) for k, v in settings.LANGUAGES]
return ''
class GetCurrentLanguageNode(Node):
@@ -30,7 +30,7 @@ class GetCurrentLanguageBidiNode(Node):
def render(self, context):
context[self.variable] = translation.get_language_bidi()
return ''
-
+
class TranslateNode(Node):
def __init__(self, value, noop):
self.value = value
@@ -171,7 +171,7 @@ def do_translate(parser, token):
else:
noop = False
return (value, noop)
- (value, noop) = TranslateParser(token.contents).top()
+ value, noop = TranslateParser(token.contents).top()
return TranslateNode(value, noop)
def do_block_translate(parser, token):
@@ -216,7 +216,7 @@ def do_block_translate(parser, token):
raise TemplateSyntaxError, "unknown subtag %s for 'blocktrans' found" % tag
return (countervar, counter, extra_context)
- (countervar, counter, extra_context) = BlockTranslateParser(token.contents).top()
+ countervar, counter, extra_context = BlockTranslateParser(token.contents).top()
singular = []
plural = []
diff --git a/django/utils/synch.py b/django/utils/synch.py
index 1e6b546a78..6fcd81390e 100644
--- a/django/utils/synch.py
+++ b/django/utils/synch.py
@@ -6,7 +6,10 @@ Synchronization primitives:
(Contributed to Django by eugene@lazutkin.com)
"""
-import threading
+try:
+ import threading
+except ImportError:
+ import dummy_threading as threading
class RWLock:
"""
diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
new file mode 100644
index 0000000000..276e59f4bd
--- /dev/null
+++ b/django/utils/translation/__init__.py
@@ -0,0 +1,8 @@
+from django.conf import settings
+
+if settings.USE_I18N:
+ from trans_real import *
+else:
+ from trans_null import *
+
+del settings
diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py
new file mode 100644
index 0000000000..e3a97d4912
--- /dev/null
+++ b/django/utils/translation/trans_null.py
@@ -0,0 +1,18 @@
+# These are versions of the functions in django.utils.translation.trans_real
+# that don't actually do anything. This is purely for performance, so that
+# settings.USE_I18N = False can use this module rather than trans_real.py.
+
+from django.conf import settings
+
+def ngettext(singular, plural, number):
+ if number == 1: return singular
+ return plural
+ngettext_lazy = ngettext
+
+gettext = gettext_noop = gettext_lazy = _ = lambda x: x
+string_concat = lambda *strings: ''.join([str(el) for el in strings])
+activate = lambda x: None
+deactivate = install = lambda: None
+get_language = lambda: settings.LANGUAGE_CODE
+get_date_formats = lambda: settings.DATE_FORMAT, settings.DATETIME_FORMAT, settings.TIME_FORMAT
+get_partial_date_formats = lambda: settings.YEAR_MONTH_FORMAT, settings.MONTH_DAY_FORMAT
diff --git a/django/utils/translation.py b/django/utils/translation/trans_real.py
similarity index 99%
rename from django/utils/translation.py
rename to django/utils/translation/trans_real.py
index a73c43c257..94df23a8e9 100644
--- a/django/utils/translation.py
+++ b/django/utils/translation/trans_real.py
@@ -1,4 +1,4 @@
-"translation helper functions"
+"Translation helper functions"
import os, re, sys
import gettext as gettext_module
@@ -221,7 +221,6 @@ def get_language_bidi():
False = left-to-right layout
True = right-to-left layout
"""
-
from django.conf import settings
return get_language() in settings.LANGUAGES_BIDI
@@ -389,7 +388,7 @@ def get_partial_date_formats():
def install():
"""
Installs the gettext function as the default translation function under
- the name _.
+ the name '_'.
"""
__builtins__['_'] = gettext
diff --git a/docs/db-api.txt b/docs/db-api.txt
index 15b70ee028..ce6bb0ab3b 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -1121,35 +1121,37 @@ Note this is only available in MySQL and requires direct manipulation of the
database to add the full-text index.
Default lookups are exact
-~~~~~~~~~~~~~~~~~~~~~~~~~
+-------------------------
If you don't provide a lookup type -- that is, if your keyword argument doesn't
contain a double underscore -- the lookup type is assumed to be ``exact``.
For example, the following two statements are equivalent::
- Blog.objects.get(id=14)
- Blog.objects.get(id__exact=14)
+ Blog.objects.get(id__exact=14) # Explicit form
+ Blog.objects.get(id=14) # __exact is implied
This is for convenience, because ``exact`` lookups are the common case.
The pk lookup shortcut
-~~~~~~~~~~~~~~~~~~~~~~
+----------------------
For convenience, Django provides a ``pk`` lookup type, which stands for
"primary_key". This is shorthand for "an exact lookup on the primary-key."
In the example ``Blog`` model, the primary key is the ``id`` field, so these
-two statements are equivalent::
+three statements are equivalent::
- Blog.objects.get(id__exact=14)
- Blog.objects.get(pk=14)
+ Blog.objects.get(id__exact=14) # Explicit form
+ Blog.objects.get(id=14) # __exact is implied
+ Blog.objects.get(pk=14) # pk implies id__exact
-``pk`` lookups also work across joins. For example, these two statements are
+``pk`` lookups also work across joins. For example, these three statements are
equivalent::
- Entry.objects.filter(blog__id__exact=3)
- Entry.objects.filter(blog__pk=3)
+ Entry.objects.filter(blog__id__exact=3) # Explicit form
+ Entry.objects.filter(blog__id=3) # __exact is implied
+ Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
Lookups that span relationships
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1550,6 +1552,21 @@ loaded, Django iterates over every model in ``INSTALLED_APPS`` and creates the
backward relationships in memory as needed. Essentially, one of the functions
of ``INSTALLED_APPS`` is to tell Django the entire model domain.
+Queries over related objects
+----------------------------
+
+Queries involving related objects follow the same rules as queries involving
+normal value fields. When specifying the the value for a query to match, you
+may use either an object instance itself, or the primary key value for the
+object.
+
+For example, if you have a Blog object ``b`` with ``id=5``, the following
+three queries would be identical::
+
+ Entry.objects.filter(blog=b) # Query using object instance
+ Entry.objects.filter(blog=b.id) # Query using id from instance
+ Entry.objects.filter(blog=5) # Query using id directly
+
Deleting objects
================
diff --git a/docs/django-admin.txt b/docs/django-admin.txt
index 3334ae4530..04d86aa3b4 100644
--- a/docs/django-admin.txt
+++ b/docs/django-admin.txt
@@ -126,8 +126,9 @@ you run it, you'll want to look over the generated models yourself to make
customizations. In particular, you'll need to rearrange models' order, so that
models that refer to other models are ordered properly.
-Primary keys are automatically introspected for PostgreSQL and MySQL, in which
-case Django puts in the ``primary_key=True`` where needed.
+Primary keys are automatically introspected for PostgreSQL, MySQL and
+SQLite, in which case Django puts in the ``primary_key=True`` where
+needed.
``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection
only works in PostgreSQL and with certain types of MySQL tables.
diff --git a/docs/forms.txt b/docs/forms.txt
index 5026bc1bab..2fbe373744 100644
--- a/docs/forms.txt
+++ b/docs/forms.txt
@@ -50,7 +50,7 @@ model that "knows" how to create or modify instances of that model and how to
validate data for the object. Manipulators come in two flavors:
``AddManipulators`` and ``ChangeManipulators``. Functionally they are quite
similar, but the former knows how to create new instances of the model, while
-the later modifies existing instances. Both types of classes are automatically
+the latter modifies existing instances. Both types of classes are automatically
created when you define a new class::
>>> from mysite.myapp.models import Place
diff --git a/docs/outputting_pdf.txt b/docs/outputting_pdf.txt
index a58cf2c217..edd34aca24 100644
--- a/docs/outputting_pdf.txt
+++ b/docs/outputting_pdf.txt
@@ -110,7 +110,7 @@ efficient. Here's the above "Hello World" example rewritten to use
from cStringIO import StringIO
from reportlab.pdfgen import canvas
- from django.utils.httpwrappers import HttpResponse
+ from django.http import HttpResponse
def some_view(request):
# Create the HttpResponse object with the appropriate PDF headers.
diff --git a/docs/sessions.txt b/docs/sessions.txt
index 2dba491159..c473d0a3db 100644
--- a/docs/sessions.txt
+++ b/docs/sessions.txt
@@ -10,18 +10,22 @@ Cookies contain a session ID -- not the data itself.
Enabling sessions
=================
-Sessions are implemented via middleware_.
+Sessions are implemented via a piece of middleware_ and a Django model.
-Turn session functionality on and off by editing the ``MIDDLEWARE_CLASSES``
-setting. To activate sessions, make sure ``MIDDLEWARE_CLASSES`` contains
-``'django.contrib.sessions.middleware.SessionMiddleware'``.
+To enable session functionality, do these two things:
-The default ``settings.py`` created by ``django-admin.py startproject`` has
-``SessionMiddleware`` activated.
+ * Edit the ``MIDDLEWARE_CLASSES`` setting and make sure
+ ``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
+ The default ``settings.py`` created by ``django-admin.py startproject`` has
+ ``SessionMiddleware`` activated.
+
+ * Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and
+ run ``manage.py syncdb`` to install the single database table that stores
+ session data.
If you don't want to use sessions, you might as well remove the
-``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES``. It'll save you a small
-bit of overhead.
+``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'``
+from your ``INSTALLED_APPS``. It'll save you a small bit of overhead.
.. _middleware: http://www.djangoproject.com/documentation/middleware/
diff --git a/docs/settings.txt b/docs/settings.txt
index 4f4fb70298..9e1c6b529b 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -729,6 +729,10 @@ TIME_ZONE
Default: ``'America/Chicago'``
A string representing the time zone for this installation. `See available choices`_.
+(Note that list of available choices lists more than one on the same line;
+you'll want to use just one of the choices for a given time zone. For instance,
+one line says ``'Europe/London GB GB-Eire'``, but you should use the first bit
+of that -- ``'Europe/London'`` -- as your ``TIME_ZONE`` setting.)
Note that this is the time zone to which Django will convert all dates/times --
not necessarily the timezone of the server. For example, one server may serve
@@ -750,7 +754,7 @@ Default: ``True``
A boolean that specifies whether Django's internationalization system should be
enabled. This provides an easy way to turn it off, for performance. If this is
-set to ``False, Django will make some optimizations so as not to load the
+set to ``False``, Django will make some optimizations so as not to load the
internationalization machinery.
YEAR_MONTH_FORMAT
diff --git a/docs/templates.txt b/docs/templates.txt
index 42947510d1..4ba52b3263 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -951,12 +951,26 @@ any string.
pluralize
~~~~~~~~~
-Returns ``'s'`` if the value is not 1.
+Returns a plural suffix if the value is not 1. By default, this suffix is ``'s'``.
Example::
You have {{ num_messages }} message{{ num_messages|pluralize }}.
+For words that require a suffix other than ``'s'``, you can provide an alternate
+suffix as a parameter to the filter.
+
+Example::
+
+ You have {{ num_walruses }} walrus{{ num_walrus|pluralize:"es" }}.
+
+For words that don't pluralize by simple suffix, you can specify both a
+singular and plural suffix, separated by a comma.
+
+Example::
+
+ You have {{ num_cherries }} cherr{{ num_cherries|pluralize:"y,ies" }}.
+
pprint
~~~~~~
diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py
index 46f2519285..1636b948d0 100644
--- a/tests/othertests/defaultfilters.py
+++ b/tests/othertests/defaultfilters.py
@@ -313,6 +313,36 @@ False
>>> pluralize(2)
's'
+>>> pluralize([1])
+''
+
+>>> pluralize([])
+'s'
+
+>>> pluralize([1,2,3])
+'s'
+
+>>> pluralize(1,'es')
+''
+
+>>> pluralize(0,'es')
+'es'
+
+>>> pluralize(2,'es')
+'es'
+
+>>> pluralize(1,'y,ies')
+'y'
+
+>>> pluralize(0,'y,ies')
+'ies'
+
+>>> pluralize(2,'y,ies')
+'ies'
+
+>>> pluralize(0,'y,ies,error')
+''
+
>>> phone2numeric('0800 flowers')
'0800 3569377'
diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py
index c0333c90a6..d3b09c5310 100644
--- a/tests/othertests/templates.py
+++ b/tests/othertests/templates.py
@@ -78,7 +78,7 @@ TEMPLATE_TESTS = {
'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"),
# Fail silently when a variable is not found in the current context
- 'basic-syntax04': ("as{{ missing }}df", {}, "asdf"),
+ 'basic-syntax04': ("as{{ missing }}df", {}, "asINVALIDdf"),
# A variable may not contain more than one word
'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError),
@@ -94,7 +94,7 @@ TEMPLATE_TESTS = {
'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"),
# Fail silently when a variable's attribute isn't found
- 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, ""),
+ 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, "INVALID"),
# Raise TemplateSyntaxError when trying to access a variable beginning with an underscore
'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError),
@@ -110,10 +110,10 @@ TEMPLATE_TESTS = {
'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"),
# Fail silently when a variable's dictionary key isn't found
- 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, ""),
+ 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, "INVALID"),
# Fail silently when accessing a non-simple method
- 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ""),
+ 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "INVALID"),
# Basic filter usage
'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
@@ -152,7 +152,7 @@ TEMPLATE_TESTS = {
'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
# Fail silently for methods that raise an exception with a "silent_variable_failure" attribute
- 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "12"),
+ 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "1INVALID2"),
# In methods that raise an exception without a "silent_variable_attribute" set to True,
# the exception propogates
@@ -495,7 +495,7 @@ TEMPLATE_TESTS = {
'{{ item.foo }}' + \
'{% endfor %},' + \
'{% endfor %}',
- {}, ''),
+ {}, 'INVALID:INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALID,'),
### TEMPLATETAG TAG #######################################################
'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
@@ -579,6 +579,9 @@ def run_tests(verbosity=0, standalone=False):
# Turn TEMPLATE_DEBUG off, because tests assume that.
old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
+ # Set TEMPLATE_STRING_IF_INVALID to a known string
+ old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID'
+
for name, vals in tests:
install()
if 'LANGUAGE_CODE' in vals[1]:
@@ -609,6 +612,7 @@ def run_tests(verbosity=0, standalone=False):
loader.template_source_loaders = old_template_loaders
deactivate()
settings.TEMPLATE_DEBUG = old_td
+ settings.TEMPLATE_STRING_IF_INVALID = old_invalid
if failed_tests and not standalone:
msg = "Template tests %s failed." % failed_tests
diff --git a/tests/regressiontests/many_to_one_regress/__init__.py b/tests/regressiontests/many_to_one_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py
new file mode 100644
index 0000000000..485e928777
--- /dev/null
+++ b/tests/regressiontests/many_to_one_regress/models.py
@@ -0,0 +1,13 @@
+from django.db import models
+
+class First(models.Model):
+ second = models.IntegerField()
+
+class Second(models.Model):
+ first = models.ForeignKey(First, related_name = 'the_first')
+
+# If ticket #1578 ever slips back in, these models will not be able to be
+# created (the field names being lower-cased versions of their opposite
+# classes is important here).
+
+API_TESTS = ""