{% trans "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." %}
+
+{% endblock %}
diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html
index 0dbe7af743..fc568305ca 100644
--- a/django/contrib/admin/templates/admin/object_history.html
+++ b/django/contrib/admin/templates/admin/object_history.html
@@ -24,8 +24,8 @@
{% for action in action_list %}
{% endfor %}
diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py
index 5e544fe19f..10e09ca0b6 100644
--- a/django/contrib/admin/templatetags/adminapplist.py
+++ b/django/contrib/admin/templatetags/adminapplist.py
@@ -42,7 +42,12 @@ class AdminApplistNode(template.Node):
})
if model_list:
- model_list.sort()
+ # Sort using verbose decorate-sort-undecorate pattern
+ # instead of key argument to sort() for python 2.3 compatibility
+ decorated = [(x['name'], x) for x in model_list]
+ decorated.sort()
+ model_list = [x for key, x in decorated]
+
app_list.append({
'name': app_label.title(),
'has_module_perms': has_module_perms,
diff --git a/django/contrib/admin/urls.py b/django/contrib/admin/urls.py
index dde848d766..a2d3ccae48 100644
--- a/django/contrib/admin/urls.py
+++ b/django/contrib/admin/urls.py
@@ -2,7 +2,7 @@ from django.conf.urls.defaults import *
urlpatterns = patterns('',
('^$', 'django.contrib.admin.views.main.index'),
- ('^r/(\d+)/(\d+)/$', 'django.views.defaults.shortcut'),
+ ('^r/(\d+)/(.*)/$', 'django.views.defaults.shortcut'),
('^jsi18n/$', 'django.views.i18n.javascript_catalog', {'packages': 'django.conf'}),
('^logout/$', 'django.contrib.auth.views.logout'),
('^password_change/$', 'django.contrib.auth.views.password_change'),
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index d9ac07c11b..8fe3c95178 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -35,6 +35,7 @@ ORDER_TYPE_VAR = 'ot'
PAGE_VAR = 'p'
SEARCH_VAR = 'q'
IS_POPUP_VAR = 'pop'
+ERROR_FLAG = 'e'
# Text to display within change-list table cells if the value is blank.
EMPTY_CHANGELIST_VALUE = '(None)'
@@ -73,8 +74,7 @@ def unquote(s):
for item in list:
if item[1:2]:
try:
- myappend(mychr(myatoi(item[:2], 16))
- + item[2:])
+ myappend(mychr(myatoi(item[:2], 16)) + item[2:])
except ValueError:
myappend('_' + item)
else:
@@ -143,9 +143,9 @@ class AdminBoundField(object):
return self._display
except AttributeError:
if isinstance(self.field.rel, models.ManyToOneRel):
- self._display = getattr(self.original, self.field.attname)
+ self._display = getattr(self.original, self.field.name)
elif isinstance(self.field.rel, models.ManyToManyRel):
- self._display = ", ".join([str(obj) for obj in getattr(self.original, self.field.attname).all()])
+ self._display = ", ".join([str(obj) for obj in getattr(self.original, self.field.name).all()])
return self._display
def __repr__(self):
@@ -557,6 +557,8 @@ class ChangeList(object):
self.params = dict(request.GET.items())
if self.params.has_key(PAGE_VAR):
del self.params[PAGE_VAR]
+ if self.params.has_key(ERROR_FLAG):
+ del self.params[ERROR_FLAG]
self.order_field, self.order_type = self.get_ordering()
self.query = request.GET.get(SEARCH_VAR, '')
@@ -730,7 +732,14 @@ def change_list(request, app_label, model_name):
try:
cl = ChangeList(request, model)
except IncorrectLookupParameters:
- return HttpResponseRedirect(request.path)
+ # Wacky lookup parameters were given, so redirect to the main
+ # changelist page, without parameters, and pass an 'invalid=1'
+ # parameter via the query string. If wacky parameters were given and
+ # the 'invalid=1' parameter was already in the query string, something
+ # is screwed up with the database, so display an error page.
+ if ERROR_FLAG in request.GET.keys():
+ return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
+ return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
c = template.RequestContext(request, {
'title': cl.title,
'is_popup': cl.is_popup,
diff --git a/django/contrib/auth/handlers/modpython.py b/django/contrib/auth/handlers/modpython.py
index b1d7680a33..e6719794a1 100644
--- a/django/contrib/auth/handlers/modpython.py
+++ b/django/contrib/auth/handlers/modpython.py
@@ -10,8 +10,6 @@ def authenhandler(req, **kwargs):
# that so that the following import works
os.environ.update(req.subprocess_env)
- from django.contrib.auth.models import User
-
# check for PythonOptions
_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
@@ -19,6 +17,11 @@ def authenhandler(req, **kwargs):
permission_name = options.get('DjangoPermissionName', None)
staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on"))
superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off"))
+ settings_module = options.get('DJANGO_SETTINGS_MODULE', None)
+ if settings_module:
+ os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
+
+ from django.contrib.auth.models import User
# check that the username is valid
kwargs = {'username': req.user, 'is_active': True}
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 40037b96b4..80f3ea0d6b 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -32,7 +32,7 @@ class Permission(models.Model):
ordering = ('content_type', 'codename')
def __str__(self):
- return "%r | %s" % (self.content_type, self.name)
+ return "%s | %s" % (self.content_type, self.name)
class Group(models.Model):
name = models.CharField(_('name'), maxlength=80, unique=True)
diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py
index dae8a11554..da1130f560 100644
--- a/django/contrib/sessions/middleware.py
+++ b/django/contrib/sessions/middleware.py
@@ -79,9 +79,14 @@ class SessionMiddleware:
else:
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
session_key = request.session.session_key or Session.objects.get_new_session_key()
+ if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
+ max_age = None
+ expires = None
+ else:
+ max_age = settings.SESSION_COOKIE_AGE
+ expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
new_session = Session.objects.save(session_key, request.session._session,
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
- expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
- max_age=settings.SESSION_COOKIE_AGE, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN)
+ max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN)
return response
diff --git a/django/core/management.py b/django/core/management.py
index 8e8133f443..f7bbf29227 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -334,8 +334,8 @@ def get_sql_initial_data_for_model(model):
r"""( # each statement is...
(?: # one or more chunks of ...
(?:[^;'"]+) # not the end of a statement or start of a quote
- | (?:'[^']+') # something in single quotes
- | (?:"[^"]+") # something in double quotes
+ | (?:'[^']*') # something in single quotes
+ | (?:"[^"]*") # something in double quotes
)+)""", re.VERBOSE)
# Find custom SQL, if it's available.
diff --git a/django/core/paginator.py b/django/core/paginator.py
index 6e01c1ccec..f4941cb678 100644
--- a/django/core/paginator.py
+++ b/django/core/paginator.py
@@ -54,6 +54,26 @@ class ObjectPaginator:
def has_previous_page(self, page_number):
return page_number > 0
+ def first_on_page(self, page_number):
+ """
+ Returns the 1-based index of the first object on the given page,
+ relative to total objects found (hits).
+ """
+ if page_number == 0:
+ return 1
+ return (self.num_per_page * page_number) + 1
+
+ def last_on_page(self, page_number):
+ """
+ Returns the 1-based index of the last object on the given page,
+ relative to total objects found (hits).
+ """
+ if page_number == 0 and self.num_per_page >= self._hits:
+ return self._hits
+ elif page_number == (self._pages - 1) and (page_number + 1) * self.num_per_page > self._hits:
+ return self._hits
+ return (page_number + 1) * self.num_per_page
+
def _get_hits(self):
if self._hits is None:
self._hits = self.query_set.count()
diff --git a/django/core/validators.py b/django/core/validators.py
index 91d72033de..27505b7d5a 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -20,7 +20,10 @@ alnumurl_re = re.compile(r'^[-\w/]+$')
ansi_date_re = re.compile('^%s$' % _datere)
ansi_time_re = re.compile('^%s$' % _timere)
ansi_datetime_re = re.compile('^%s %s$' % (_datere, _timere))
-email_re = re.compile(r'^[A-Z0-9._%-][+A-Z0-9._%-]*@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}$', re.IGNORECASE)
+email_re = re.compile(
+ r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
+ r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
+ r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}$', re.IGNORECASE) # domain
integer_re = re.compile(r'^-?\d+$')
ip4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
phone_re = re.compile(r'^[A-PR-Y0-9]{3}-[A-PR-Y0-9]{3}-[A-PR-Y0-9]{4}$', re.IGNORECASE)
@@ -143,7 +146,11 @@ def isValidImage(field_data, all_data):
from PIL import Image
from cStringIO import StringIO
try:
- Image.open(StringIO(field_data['content']))
+ content = field_data['content']
+ except TypeError:
+ raise ValidationError, gettext("No file was submitted. Check the encoding type on the form.")
+ try:
+ Image.open(StringIO(content))
except IOError: # Python Imaging Library doesn't recognize it as an image
raise ValidationError, gettext("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
@@ -363,9 +370,13 @@ class HasAllowableSize:
self.max_error_message = max_error_message or lazy_inter(gettext_lazy("Make sure your uploaded file is at most %s bytes big."), max_size)
def __call__(self, field_data, all_data):
- if self.min_size is not None and len(field_data['content']) < self.min_size:
+ try:
+ content = field_data['content']
+ except TypeError:
+ raise ValidationError, gettext_lazy("No file was submitted. Check the encoding type on the form.")
+ if self.min_size is not None and len(content) < self.min_size:
raise ValidationError, self.min_error_message
- if self.max_size is not None and len(field_data['content']) > self.max_size:
+ if self.max_size is not None and len(content) > self.max_size:
raise ValidationError, self.max_error_message
class MatchesRegularExpression:
diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
index c4f759da10..c3a16d61c3 100644
--- a/django/db/backends/postgresql/introspection.py
+++ b/django/db/backends/postgresql/introspection.py
@@ -45,27 +45,26 @@ def get_indexes(cursor, table_name):
{'primary_key': boolean representing whether it's the primary key,
'unique': boolean representing whether it's a unique index}
"""
- # Get the table description because we only have the column indexes, and we
- # need the column names.
- desc = get_table_description(cursor, table_name)
- # This query retrieves each index on the given table.
+ # This query retrieves each index on the given table, including the
+ # first associated field name
cursor.execute("""
- SELECT idx.indkey, idx.indisunique, idx.indisprimary
+ SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
- pg_catalog.pg_index idx
+ pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
WHERE c.oid = idx.indrelid
AND idx.indexrelid = c2.oid
+ AND attr.attrelid = c.oid
+ AND attr.attnum = idx.indkey[0]
AND c.relname = %s""", [table_name])
indexes = {}
for row in cursor.fetchall():
- # row[0] (idx.indkey) is stored in the DB as an array. It comes out as
+ # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
# a string of space-separated integers. This designates the field
# indexes (1-based) of the fields that have indexes on the table.
# Here, we skip any indexes across multiple fields.
- if ' ' in row[0]:
+ if ' ' in row[1]:
continue
- col_name = desc[int(row[0])-1][0]
- indexes[col_name] = {'primary_key': row[2], 'unique': row[1]}
+ indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
return indexes
# Maps type codes to Django Field types.
diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py
index 88c44f98d7..b991493d39 100644
--- a/django/db/backends/postgresql_psycopg2/introspection.py
+++ b/django/db/backends/postgresql_psycopg2/introspection.py
@@ -45,27 +45,26 @@ def get_indexes(cursor, table_name):
{'primary_key': boolean representing whether it's the primary key,
'unique': boolean representing whether it's a unique index}
"""
- # Get the table description because we only have the column indexes, and we
- # need the column names.
- desc = get_table_description(cursor, table_name)
- # This query retrieves each index on the given table.
+ # This query retrieves each index on the given table, including the
+ # first associated field name
cursor.execute("""
- SELECT idx.indkey, idx.indisunique, idx.indisprimary
+ SELECT attr.attname, idx.indkey, idx.indisunique, idx.indisprimary
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
- pg_catalog.pg_index idx
+ pg_catalog.pg_index idx, pg_catalog.pg_attribute attr
WHERE c.oid = idx.indrelid
AND idx.indexrelid = c2.oid
+ AND attr.attrelid = c.oid
+ AND attr.attnum = idx.indkey[0]
AND c.relname = %s""", [table_name])
indexes = {}
for row in cursor.fetchall():
- # row[0] (idx.indkey) is stored in the DB as an array. It comes out as
+ # row[1] (idx.indkey) is stored in the DB as an array. It comes out as
# a string of space-separated integers. This designates the field
# indexes (1-based) of the fields that have indexes on the table.
# Here, we skip any indexes across multiple fields.
- if ' ' in row[0]:
+ if ' ' in row[1]:
continue
- col_name = desc[int(row[0])-1][0]
- indexes[col_name] = {'primary_key': row[2], 'unique': row[1]}
+ indexes[row[0]] = {'primary_key': row[3], 'unique': row[2]}
return indexes
# Maps type codes to Django Field types.
diff --git a/django/db/backends/util.py b/django/db/backends/util.py
index b9c6f573c9..3098a53556 100644
--- a/django/db/backends/util.py
+++ b/django/db/backends/util.py
@@ -12,6 +12,10 @@ class CursorDebugWrapper:
return self.cursor.execute(sql, params)
finally:
stop = time()
+ # If params was a list, convert it to a tuple, because string
+ # formatting with '%' only works with tuples or dicts.
+ if not isinstance(params, (tuple, dict)):
+ params = tuple(params)
self.db.queries.append({
'sql': sql % tuple(params),
'time': "%.3f" % (stop - start),
diff --git a/django/db/models/base.py b/django/db/models/base.py
index bc722de505..3538826356 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -161,7 +161,7 @@ class Model(object):
(backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val])
# If it does already exist, do an UPDATE.
if cursor.fetchone():
- db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), False)) for f in non_pks]
+ db_values = [f.get_db_prep_save(f.pre_save(self, False)) for f in non_pks]
cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
(backend.quote_name(self._meta.db_table),
','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]),
@@ -171,11 +171,11 @@ class Model(object):
record_exists = False
if not pk_set or not record_exists:
field_names = [backend.quote_name(f.column) for f in self._meta.fields if not isinstance(f, AutoField)]
- db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), True)) for f in self._meta.fields if not isinstance(f, AutoField)]
+ db_values = [f.get_db_prep_save(f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)]
# If the PK has been manually set, respect that.
if pk_set:
field_names += [f.column for f in self._meta.fields if isinstance(f, AutoField)]
- db_values += [f.get_db_prep_save(f.pre_save(getattr(self, f.column), True)) for f in self._meta.fields if isinstance(f, AutoField)]
+ db_values += [f.get_db_prep_save(f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)]
placeholders = ['%s'] * len(field_names)
if self._meta.order_with_respect_to:
field_names.append(backend.quote_name('_order'))
@@ -279,7 +279,7 @@ class Model(object):
order_field = self._meta.order_with_respect_to
where = ['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \
(backend.quote_name('_order'), op, backend.quote_name('_order'),
- backend.quote_name(opts.db_table), backend.quote_name(opts.pk.column)),
+ backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)),
'%s=%%s' % backend.quote_name(order_field.column)]
params = [self._get_pk_val(), getattr(self, order_field.attname)]
obj = self._default_manager.order_by('_order').extra(where=where, params=params)[:1].get()
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 334aa01766..b5245d6624 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -152,9 +152,9 @@ class Field(object):
def get_internal_type(self):
return self.__class__.__name__
- def pre_save(self, value, add):
+ def pre_save(self, model_instance, add):
"Returns field's value just before saving."
- return value
+ return getattr(model_instance, self.attname)
def get_db_prep_save(self, value):
"Returns field's value prepared for saving into a database."
@@ -417,10 +417,13 @@ class DateField(Field):
value = str(value)
return Field.get_db_prep_lookup(self, lookup_type, value)
- def pre_save(self, value, add):
+ def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
- return datetime.datetime.now()
- return value
+ value = datetime.datetime.now()
+ setattr(model_instance, self.attname, value)
+ return value
+ else:
+ return super(DateField, self).pre_save(model_instance, add)
def contribute_to_class(self, cls, name):
super(DateField,self).contribute_to_class(cls, name)
@@ -723,10 +726,13 @@ class TimeField(Field):
value = str(value)
return Field.get_db_prep_lookup(self, lookup_type, value)
- def pre_save(self, value, add):
+ def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
- return datetime.datetime.now().time()
- return value
+ value = datetime.datetime.now().time()
+ setattr(model_instance, self.attname, value)
+ return value
+ else:
+ return super(TimeField, self).pre_save(model_instance, add)
def get_db_prep_save(self, value):
# Casts dates into string format for entry into database.
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 91ae294a62..3517d6bed5 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -3,8 +3,8 @@ from django.db.models.fields import DateField, FieldDoesNotExist
from django.db.models import signals
from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
-
import operator
+import re
# For Python 2.3
if not hasattr(__builtins__, 'set'):
@@ -59,7 +59,7 @@ def orderlist2sql(order_list, opts, prefix=''):
return ', '.join(output)
def quote_only_if_word(word):
- if ' ' in word:
+ if re.search('\W', word): # Don't quote if there are spaces or non-word chars.
return word
else:
return backend.quote_name(word)
@@ -315,7 +315,7 @@ class QuerySet(object):
def complex_filter(self, filter_obj):
"""Returns a new QuerySet instance with filter_obj added to the filters.
- filter_obj can be a Q object (has 'get_sql' method) or a dictionary of
+ filter_obj can be a Q object (has 'get_sql' method) or a dictionary of
keyword lookup arguments."""
# This exists to support framework features such as 'limit_choices_to',
# and usually it will be more natural to use other methods.
@@ -380,6 +380,10 @@ class QuerySet(object):
# (so that A.filter(args1) & A.filter(args2) does the same as
# A.filter(args1).filter(args2)
combined = other._clone()
+ if self._select: combined._select.update(self._select)
+ if self._where: combined._where.extend(self._where)
+ if self._params: combined._params.extend(self._params)
+ if self._tables: combined._tables.extend(self._tables)
# If 'self' is ordered and 'other' isn't, propagate 'self's ordering
if (self._order_by is not None and len(self._order_by) > 0) and \
(combined._order_by is None or len(combined._order_by) == 0):
diff --git a/django/db/transaction.py b/django/db/transaction.py
index 906995ca02..60a743c42a 100644
--- a/django/db/transaction.py
+++ b/django/db/transaction.py
@@ -12,7 +12,10 @@ Managed transactions don't do those commits, but will need some kind of manual
or implicit commits or rollbacks.
"""
-import thread
+try:
+ import thread
+except ImportError:
+ import dummy_thread as thread
from django.db import connection
from django.conf import settings
diff --git a/django/forms/__init__.py b/django/forms/__init__.py
index 7ad26a4d71..b67e1d0f84 100644
--- a/django/forms/__init__.py
+++ b/django/forms/__init__.py
@@ -577,7 +577,7 @@ class SelectMultipleField(SelectField):
selected_html = ''
if str(value) in str_data_list:
selected_html = ' selected="selected"'
- output.append(' ' % (escape(value), selected_html, choice))
+ output.append(' ' % (escape(value), selected_html, escape(choice)))
output.append(' ')
return '\n'.join(output)
@@ -641,7 +641,11 @@ class FileUploadField(FormField):
self.validator_list = [self.isNonEmptyFile] + validator_list
def isNonEmptyFile(self, field_data, all_data):
- if not field_data['content']:
+ try:
+ content = field_data['content']
+ except TypeError:
+ raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.")
+ if not content:
raise validators.CriticalValidationError, gettext("The submitted file is empty.")
def render(self, data):
diff --git a/django/shortcuts/__init__.py b/django/shortcuts/__init__.py
index b42ede0339..76d54917ad 100644
--- a/django/shortcuts/__init__.py
+++ b/django/shortcuts/__init__.py
@@ -10,14 +10,14 @@ def render_to_response(*args, **kwargs):
return HttpResponse(loader.render_to_string(*args, **kwargs))
load_and_render = render_to_response # For backwards compatibility.
-def get_object_or_404(klass, **kwargs):
+def get_object_or_404(klass, *args, **kwargs):
try:
- return klass._default_manager.get(**kwargs)
+ return klass._default_manager.get(*args, **kwargs)
except klass.DoesNotExist:
raise Http404
-def get_list_or_404(klass, **kwargs):
- obj_list = list(klass._default_manager.filter(**kwargs))
+def get_list_or_404(klass, *args, **kwargs):
+ obj_list = list(klass._default_manager.filter(*args, **kwargs))
if not obj_list:
raise Http404
return obj_list
diff --git a/django/utils/_threading_local.py b/django/utils/_threading_local.py
index 90717a8d84..bf9a25753a 100644
--- a/django/utils/_threading_local.py
+++ b/django/utils/_threading_local.py
@@ -234,4 +234,7 @@ class local(_localbase):
return __del__
__del__ = __del__()
-from threading import currentThread, enumerate, RLock
+try:
+ from threading import currentThread, enumerate, RLock
+except ImportError:
+ from dummy_threading import currentThread, enumerate, RLock
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index 1edec190d8..6363af7835 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -28,7 +28,12 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-import os, sys, thread, time
+import os, sys, time
+
+try:
+ import thread
+except ImportError:
+ import dummy_thread as thread
RUN_RELOADER = True
diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py
index f7c25f2933..4c5d515f41 100644
--- a/django/utils/feedgenerator.py
+++ b/django/utils/feedgenerator.py
@@ -217,7 +217,7 @@ class Atom1Feed(SyndicationFeed):
for item in self.items:
handler.startElement(u"entry", {})
handler.addQuickElement(u"title", item['title'])
- handler.addQuickElement(u"link", u"", {u"href": item['link']})
+ handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"alternate"})
if item['pubdate'] is not None:
handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('ascii'))
diff --git a/django/views/generic/date_based.py b/django/views/generic/date_based.py
index 1a6cbc8369..0fc657d2c1 100644
--- a/django/views/generic/date_based.py
+++ b/django/views/generic/date_based.py
@@ -6,7 +6,8 @@ import datetime, time
def archive_index(request, queryset, date_field, num_latest=15,
template_name=None, template_loader=loader,
- extra_context={}, allow_empty=False, context_processors=None):
+ extra_context={}, allow_empty=False, context_processors=None,
+ mimetype=None):
"""
Generic top-level archive of date-based objects.
@@ -40,11 +41,12 @@ def archive_index(request, queryset, date_field, num_latest=15,
c[key] = value()
else:
c[key] = value
- return HttpResponse(t.render(c))
+ return HttpResponse(t.render(c), mimetype=mimetype)
def archive_year(request, year, queryset, date_field, template_name=None,
template_loader=loader, extra_context={}, allow_empty=False,
- context_processors=None):
+ context_processors=None, template_object_name='object', mimetype=None,
+ make_object_list=False):
"""
Generic yearly archive view.
@@ -54,6 +56,9 @@ def archive_year(request, year, queryset, date_field, template_name=None,
List of months in this year with objects
year
This year
+ object_list
+ List of objects published in the given month
+ (Only available if make_object_list argument is True)
"""
model = queryset.model
now = datetime.datetime.now()
@@ -66,24 +71,29 @@ def archive_year(request, year, queryset, date_field, template_name=None,
date_list = queryset.filter(**lookup_kwargs).dates(date_field, 'month')
if not date_list and not allow_empty:
raise Http404
+ if make_object_list:
+ object_list = queryset.filter(**lookup_kwargs).order_by(date_field)
+ else:
+ object_list = []
if not template_name:
template_name = "%s/%s_archive_year.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
c = RequestContext(request, {
'date_list': date_list,
'year': year,
+ '%s_list' % template_object_name: object_list,
}, context_processors)
for key, value in extra_context.items():
if callable(value):
c[key] = value()
else:
c[key] = value
- return HttpResponse(t.render(c))
+ return HttpResponse(t.render(c), mimetype=mimetype)
def archive_month(request, year, month, queryset, date_field,
month_format='%b', template_name=None, template_loader=loader,
extra_context={}, allow_empty=False, context_processors=None,
- template_object_name='object'):
+ template_object_name='object', mimetype=None):
"""
Generic monthly archive view.
@@ -134,12 +144,12 @@ def archive_month(request, year, month, queryset, date_field,
c[key] = value()
else:
c[key] = value
- return HttpResponse(t.render(c))
+ return HttpResponse(t.render(c), mimetype=mimetype)
def archive_week(request, year, week, queryset, date_field,
template_name=None, template_loader=loader,
extra_context={}, allow_empty=True, context_processors=None,
- template_object_name='object'):
+ template_object_name='object', mimetype=None):
"""
Generic weekly archive view.
@@ -181,12 +191,13 @@ def archive_week(request, year, week, queryset, date_field,
c[key] = value()
else:
c[key] = value
- return HttpResponse(t.render(c))
+ return HttpResponse(t.render(c), mimetype=mimetype)
def archive_day(request, year, month, day, queryset, date_field,
month_format='%b', day_format='%d', template_name=None,
template_loader=loader, extra_context={}, allow_empty=False,
- context_processors=None, template_object_name='object'):
+ context_processors=None, template_object_name='object',
+ mimetype=None):
"""
Generic daily archive view.
@@ -233,7 +244,7 @@ def archive_day(request, year, month, day, queryset, date_field,
c[key] = value()
else:
c[key] = value
- return HttpResponse(t.render(c))
+ return HttpResponse(t.render(c), mimetype=mimetype)
def archive_today(request, **kwargs):
"""
@@ -251,7 +262,7 @@ def object_detail(request, year, month, day, queryset, date_field,
month_format='%b', day_format='%d', object_id=None, slug=None,
slug_field=None, template_name=None, template_name_field=None,
template_loader=loader, extra_context={}, context_processors=None,
- template_object_name='object'):
+ template_object_name='object', mimetype=None):
"""
Generic detail view from year/month/day/slug or year/month/day/id structure.
@@ -300,6 +311,6 @@ def object_detail(request, year, month, day, queryset, date_field,
c[key] = value()
else:
c[key] = value
- response = HttpResponse(t.render(c))
+ response = HttpResponse(t.render(c), mimetype=mimetype)
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
return response
diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py
index 7d254c08da..77b97ca711 100644
--- a/django/views/generic/list_detail.py
+++ b/django/views/generic/list_detail.py
@@ -6,7 +6,8 @@ from django.core.exceptions import ObjectDoesNotExist
def object_list(request, queryset, paginate_by=None, allow_empty=False,
template_name=None, template_loader=loader,
- extra_context={}, context_processors=None, template_object_name='object'):
+ extra_context={}, context_processors=None, template_object_name='object',
+ mimetype=None):
"""
Generic list of objects.
@@ -73,12 +74,13 @@ def object_list(request, queryset, paginate_by=None, allow_empty=False,
model = queryset.model
template_name = "%s/%s_list.html" % (model._meta.app_label, model._meta.object_name.lower())
t = template_loader.get_template(template_name)
- return HttpResponse(t.render(c))
+ return HttpResponse(t.render(c), mimetype=mimetype)
def object_detail(request, queryset, object_id=None, slug=None,
slug_field=None, template_name=None, template_name_field=None,
template_loader=loader, extra_context={},
- context_processors=None, template_object_name='object'):
+ context_processors=None, template_object_name='object',
+ mimetype=None):
"""
Generic list of objects.
@@ -113,6 +115,6 @@ def object_detail(request, queryset, object_id=None, slug=None,
c[key] = value()
else:
c[key] = value
- response = HttpResponse(t.render(c))
+ response = HttpResponse(t.render(c), mimetype=mimetype)
populate_xheaders(request, response, model, getattr(obj, obj._meta.pk.name))
return response
diff --git a/docs/apache_auth.txt b/docs/apache_auth.txt
index 0dd5d00cbe..72e0841305 100644
--- a/docs/apache_auth.txt
+++ b/docs/apache_auth.txt
@@ -56,6 +56,15 @@ location to users marked as staff members. You can use a set of
required.
================================ =========================================
+Note that sometimes ``SetEnv`` doesn't play well in this mod_python
+configuration, for reasons unknown. If you're having problems getting
+mod_python to recognize your ``DJANGO_SETTINGS_MODULE``, you can set it using
+``PythonOption`` instead of ``SetEnv``. Therefore, these two Apache directives
+are equivalent::
+
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ PythonOption DJANGO_SETTINGS_MODULE mysite.settings
+
.. _authentication system: http://www.djangoproject.com/documentation/authentication/
.. _Subversion: http://subversion.tigris.org/
.. _mod_dav: http://httpd.apache.org/docs/2.0/mod/mod_dav.html
diff --git a/docs/generic_views.txt b/docs/generic_views.txt
index 5b978af1de..317828a2b2 100644
--- a/docs/generic_views.txt
+++ b/docs/generic_views.txt
@@ -182,6 +182,9 @@ a date in the *future* are not included.
* ``context_processors``: A list of template-context processors to apply to
the view's template. See the `RequestContext docs`_.
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_MIME_TYPE`` setting.
+
**Template name:**
If ``template_name`` isn't specified, this view will use the template
@@ -247,6 +250,21 @@ with a date in the *future* are not displayed.
* ``context_processors``: A list of template-context processors to apply to
the view's template. See the `RequestContext docs`_.
+ * ``template_object_name``: Designates the name of the template variable
+ to use in the template context. By default, this is ``'object'``. The
+ view will append ``'_list'`` to the value of this parameter in
+ determining the variable's name.
+
+ * ``make_object_list``: A boolean specifying whether to retrieve the full
+ list of objects for this year and pass those to the template. If ``True``,
+ this list of objects will be made available to the template as
+ ``object_list``. (The name ``object_list`` may be different; see the docs
+ for ``object_list`` in the "Template context" section below.) By default,
+ this is ``False``.
+
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_MIME_TYPE`` setting.
+
**Template name:**
If ``template_name`` isn't specified, this view will use the template
@@ -259,8 +277,19 @@ In addition to ``extra_context``, the template's context will be:
* ``date_list``: A list of ``datetime.date`` objects representing all
months that have objects available in the given year, according to
``queryset``, in ascending order.
+
* ``year``: The given year, as a four-character string.
+ * ``object_list``: If the ``make_object_list`` parameter is ``True``, this
+ will be set to a list of objects available for the given year, ordered by
+ the date field. This variable's name depends on the
+ ``template_object_name`` parameter, which is ``'object'`` by default. If
+ ``template_object_name`` is ``'foo'``, this variable's name will be
+ ``foo_list``.
+
+ If ``make_object_list`` is ``False``, ``object_list`` will be passed to
+ the template as an empty list.
+
``django.views.generic.date_based.archive_month``
-------------------------------------------------
@@ -314,6 +343,9 @@ date in the *future* are not displayed.
view will append ``'_list'`` to the value of this parameter in
determining the variable's name.
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_MIME_TYPE`` setting.
+
**Template name:**
If ``template_name`` isn't specified, this view will use the template
@@ -387,6 +419,9 @@ in the *future* are not displayed.
view will append ``'_list'`` to the value of this parameter in
determining the variable's name.
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_MIME_TYPE`` setting.
+
**Template name:**
If ``template_name`` isn't specified, this view will use the template
@@ -463,6 +498,9 @@ a 404 error, regardless of whether any objects exist for future days.
view will append ``'_list'`` to the value of this parameter in
determining the variable's name.
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_MIME_TYPE`` setting.
+
**Template name:**
If ``template_name`` isn't specified, this view will use the template
@@ -563,6 +601,9 @@ A page representing an individual object.
* ``template_object_name``: Designates the name of the template variable
to use in the template context. By default, this is ``'object'``.
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_MIME_TYPE`` setting.
+
**Template name:**
If ``template_name`` isn't specified, this view will use the template
@@ -627,6 +668,9 @@ A page representing a list of objects.
view will append ``'_list'`` to the value of this parameter in
determining the variable's name.
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_MIME_TYPE`` setting.
+
**Template name:**
If ``template_name`` isn't specified, this view will use the template
@@ -717,6 +761,9 @@ A page representing an individual object.
* ``template_object_name``: Designates the name of the template variable
to use in the template context. By default, this is ``'object'``.
+ * ``mimetype``: The MIME type to use for the resulting document. Defaults
+ to the value of the ``DEFAULT_MIME_TYPE`` setting.
+
**Template name:**
If ``template_name`` isn't specified, this view will use the template
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 3e40a6f63f..9cc5b8f203 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -162,11 +162,15 @@ A date field. Has a few extra optional arguments:
====================== ===================================================
``auto_now`` Automatically set the field to now every time the
object is saved. Useful for "last-modified"
- timestamps.
+ timestamps. Note that the current date is *always*
+ used; it's not just a default value that you can
+ override.
``auto_now_add`` Automatically set the field to now when the object
is first created. Useful for creation of
- timestamps.
+ timestamps. Note that the current date is *always*
+ used; it's not just a default value that you can
+ override.
====================== ===================================================
The admin represents this as an ```` with a JavaScript
@@ -488,6 +492,10 @@ cleared, the object will be deleted.
It is an error to have an inline-editable relation without at least one
``core=True`` field.
+Please note that each field marked "core" is treated as a required field by the
+Django admin site. Essentially, this means you should put ``core=True`` on all
+required fields in your related object that is being edited inline.
+
``db_column``
~~~~~~~~~~~~~
diff --git a/docs/sessions.txt b/docs/sessions.txt
index b7f16b67b9..2dba491159 100644
--- a/docs/sessions.txt
+++ b/docs/sessions.txt
@@ -197,6 +197,22 @@ will be sent on every request.
Similarly, the ``expires`` part of a session cookie is updated each time the
session cookie is sent.
+Browser-length sessions vs. persistent sessions
+===============================================
+
+You can control whether the session framework uses browser-length sessions vs.
+persistent sessions with the ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` setting.
+
+By default, ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` is set to ``False``, which
+means session cookies will be stored in users' browsers for as long as
+``SESSION_COOKIE_AGE``. Use this if you don't want people to have to log in
+every time they open a browser.
+
+If ``SESSION_EXPIRE_AT_BROWSER_CLOSE`` is set to ``True``, Django will use
+browser-length cookies -- cookies that expire as soon as the user closes his or
+her browser. Use this if you want people to have to log in every time they open
+a browser.
+
Settings
========
@@ -225,6 +241,14 @@ Default: ``'sessionid'``
The name of the cookie to use for sessions. This can be whatever you want.
+SESSION_EXPIRE_AT_BROWSER_CLOSE
+-------------------------------
+
+Default: ``False``
+
+Whether to expire the session when the user closes his or her browser. See
+"Browser-length sessions vs. persistent sessions" above.
+
SESSION_SAVE_EVERY_REQUEST
--------------------------
diff --git a/docs/settings.txt b/docs/settings.txt
index 80000fad5b..26d5930f21 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -603,6 +603,14 @@ Default: ``'sessionid'``
The name of the cookie to use for sessions. This can be whatever you want.
See the `session docs`_.
+SESSION_EXPIRE_AT_BROWSER_CLOSE
+-------------------------------
+
+Default: ``False``
+
+Whether to expire the session when the user closes his or her browser.
+See the `session docs`_.
+
SESSION_SAVE_EVERY_REQUEST
--------------------------
diff --git a/docs/tutorial02.txt b/docs/tutorial02.txt
index 4cc263dddc..84eae3eb83 100644
--- a/docs/tutorial02.txt
+++ b/docs/tutorial02.txt
@@ -85,7 +85,7 @@ creating an empty class means "give this object an admin interface using
all the default options."
Now reload the Django admin page to see your changes. Note that you don't have
-to restart the development server -- the server will auto-reloads your project,
+to restart the development server -- the server will auto-reload your project,
so any modifications code will be seen immediately in your browser.
Explore the free admin functionality
diff --git a/docs/tutorial03.txt b/docs/tutorial03.txt
index f368d54c53..3a830eb76f 100644
--- a/docs/tutorial03.txt
+++ b/docs/tutorial03.txt
@@ -209,13 +209,13 @@ So let's use Django's template system to separate the design from Python::
})
return HttpResponse(t.render(c))
-That code loads the template called "polls/index" and passes it a context. The
+That code loads the template called "polls/index.html" and passes it a context. The
context is a dictionary mapping template variable names to Python objects.
Reload the page. Now you'll see an error::
- TemplateDoesNotExist: Your TEMPLATE_DIRS settings is empty.
- Change it to point to at least one template directory.
+ TemplateDoesNotExist at /polls/
+ polls/index.html
Ah. There's no template yet. First, create a directory, somewhere on your
filesystem, whose contents Django can access. (Django runs as whatever user
diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py
index a6ae80029a..ceecea2fc6 100644
--- a/tests/modeltests/custom_managers/models.py
+++ b/tests/modeltests/custom_managers/models.py
@@ -1,5 +1,12 @@
"""
23. Giving models a custom manager
+
+You can use a custom ``Manager`` in a particular model by extending the base
+``Manager`` class and instantiating your custom ``Manager`` in your model.
+
+There are two reasons you might want to customize a ``Manager``: to add extra
+``Manager`` methods, and/or to modify the initial ``QuerySet`` the ``Manager``
+returns.
"""
from django.db import models
@@ -19,7 +26,7 @@ class Person(models.Model):
def __repr__(self):
return "%s %s" % (self.first_name, self.last_name)
-# An example of a custom manager that sets a core_filter on its lookups.
+# An example of a custom manager that sets get_query_set().
class PublishedBookManager(models.Manager):
def get_query_set(self):
diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py
index 3fdefca6bf..6cc3fe8548 100644
--- a/tests/modeltests/custom_methods/models.py
+++ b/tests/modeltests/custom_methods/models.py
@@ -1,5 +1,5 @@
"""
-3. Giving models custom methods and custom managers
+3. Giving models custom methods
Any method you add to a model will be available to instances.
"""
diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py
index e5b7fd8e6d..0d69ffd8be 100644
--- a/tests/modeltests/field_defaults/models.py
+++ b/tests/modeltests/field_defaults/models.py
@@ -1,7 +1,12 @@
"""
-XXX. Callable defaults
+31. Callable defaults
-???
+You can pass callable objects as the ``default`` parameter to a field. When
+the object is created without an explicit value passed in, Django will call
+the method to determine the default value.
+
+This example uses ``datetime.datetime.now`` as the default for the ``pub_date``
+field.
"""
from django.db import models
@@ -9,9 +14,9 @@ from datetime import datetime
class Article(models.Model):
headline = models.CharField(maxlength=100, default='Default headline')
- pub_date = models.DateTimeField(default = datetime.now)
-
- def __repr__(self):
+ pub_date = models.DateTimeField(default=datetime.now)
+
+ def __str__(self):
return self.headline
API_TESTS = """
@@ -43,6 +48,4 @@ API_TESTS = """
>>> d = now - a.pub_date
>>> d.seconds < 5
True
-
-
"""
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py
index 0b3ffcf073..127cc9e0d2 100644
--- a/tests/modeltests/invalid_models/models.py
+++ b/tests/modeltests/invalid_models/models.py
@@ -1,5 +1,7 @@
"""
-26. A test to check that the model validator works can correctly identify errors in a model.
+26. Invalid models
+
+This example exists purely to point out errors in models.
"""
from django.db import models
@@ -11,16 +13,16 @@ class FieldErrors(models.Model):
prepopulate = models.CharField(maxlength=10, prepopulate_from='bad')
choices = models.CharField(maxlength=10, choices='bad')
choices2 = models.CharField(maxlength=10, choices=[(1,2,3),(1,2,3)])
- index = models.CharField(maxlength=10, db_index='bad')
+ index = models.CharField(maxlength=10, db_index='bad')
class Target(models.Model):
tgt_safe = models.CharField(maxlength=10)
-
+
clash1_set = models.CharField(maxlength=10)
-
+
class Clash1(models.Model):
src_safe = models.CharField(maxlength=10)
-
+
foreign = models.ForeignKey(Target)
m2m = models.ManyToManyField(Target)
@@ -36,27 +38,27 @@ class Clash2(models.Model):
class Target2(models.Model):
foreign_tgt = models.ForeignKey(Target)
clashforeign_set = models.ForeignKey(Target)
-
+
m2m_tgt = models.ManyToManyField(Target)
clashm2m_set = models.ManyToManyField(Target)
class Clash3(models.Model):
foreign_1 = models.ForeignKey(Target2, related_name='foreign_tgt')
foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt')
-
+
m2m_1 = models.ManyToManyField(Target2, related_name='foreign_tgt')
m2m_2 = models.ManyToManyField(Target2, related_name='m2m_tgt')
-
+
class ClashForeign(models.Model):
foreign = models.ForeignKey(Target2)
class ClashM2M(models.Model):
m2m = models.ManyToManyField(Target2)
-
+
class SelfClashForeign(models.Model):
src_safe = models.CharField(maxlength=10)
-
- selfclashforeign_set = models.ForeignKey("SelfClashForeign")
+
+ selfclashforeign_set = models.ForeignKey("SelfClashForeign")
foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id')
foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe')
diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py
index 29631e5779..7a685ecbd2 100644
--- a/tests/modeltests/m2m_and_m2o/models.py
+++ b/tests/modeltests/m2m_and_m2o/models.py
@@ -1,8 +1,7 @@
"""
-27. Many-to-many and many-to-one relationships to the same table.
-
-This is a response to bug #1535
+28. Many-to-many and many-to-one relationships to the same table
+Make sure to set ``related_name`` if you use relationships to the same table.
"""
from django.db import models
@@ -14,9 +13,10 @@ class Issue(models.Model):
num = models.IntegerField()
cc = models.ManyToManyField(User, blank=True, related_name='test_issue_cc')
client = models.ForeignKey(User, related_name='test_issue_client')
- def __repr__(self):
- return "" % (self.num,)
-
+
+ def __str__(self):
+ return str(self.num)
+
class Meta:
ordering = ('num',)
@@ -47,20 +47,20 @@ API_TESTS = """
>>> i3.cc.add(r)
>>> from django.db.models.query import Q
>>> Issue.objects.filter(client=r.id)
-[, ]
+[, ]
>>> Issue.objects.filter(client=g.id)
-[]
+[]
>>> Issue.objects.filter(cc__id__exact=g.id)
[]
>>> Issue.objects.filter(cc__id__exact=r.id)
-[, ]
+[, ]
# Queries that combine results from the m2m and the m2o relationship.
# 3 ways of saying the same thing:
>>> Issue.objects.filter(Q(cc__id__exact=r.id) | Q(client=r.id))
-[, , ]
+[, , ]
>>> Issue.objects.filter(cc__id__exact=r.id) | Issue.objects.filter(client=r.id)
-[, , ]
+[, , ]
>>> Issue.objects.filter(Q(client=r.id) | Q(cc__id__exact=r.id))
-[, , ]
+[, , ]
"""
diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py
index ff8a5a8f47..877a41fd14 100644
--- a/tests/modeltests/m2m_recursive/models.py
+++ b/tests/modeltests/m2m_recursive/models.py
@@ -1,15 +1,15 @@
"""
-26. Many-to-many relationships between the same two tables
+27. Many-to-many relationships between the same two tables
-In this example, A Person can have many friends, who are also people. Friendship is a
+In this example, A Person can have many friends, who are also people. Friendship is a
symmetrical relationshiup - if I am your friend, you are my friend.
A person can also have many idols - but while I may idolize you, you may not think
-the same of me. 'Idols' is an example of a non-symmetrical m2m field. Only recursive
+the same of me. 'Idols' is an example of a non-symmetrical m2m field. Only recursive
m2m fields may be non-symmetrical, and they are symmetrical by default.
This test validates that the m2m table will create a mangled name for the m2m table if
-there will be a clash, and tests that symmetry is preserved where appropriate.
+there will be a clash, and tests that symmetry is preserved where appropriate.
"""
from django.db import models
@@ -40,7 +40,7 @@ API_TESTS = """
>>> d.friends.add(a,c)
# Who is friends with Anne?
->>> a.friends.all()
+>>> a.friends.all()
[Bill, Chuck, David]
# Who is friends with Bill?
@@ -52,14 +52,14 @@ API_TESTS = """
[Anne, David]
# Who is friends with David?
->>> d.friends.all()
+>>> d.friends.all()
[Anne, Chuck]
# Bill is already friends with Anne - add Anne again, but in the reverse direction
>>> b.friends.add(a)
# Who is friends with Anne?
->>> a.friends.all()
+>>> a.friends.all()
[Bill, Chuck, David]
# Who is friends with Bill?
@@ -70,7 +70,7 @@ API_TESTS = """
>>> b.friends.remove(a)
# Who is friends with Anne?
->>> a.friends.all()
+>>> a.friends.all()
[Chuck, David]
# Who is friends with Bill?
@@ -81,7 +81,7 @@ API_TESTS = """
>>> a.friends.clear()
# Who is friends with Anne?
->>> a.friends.all()
+>>> a.friends.all()
[]
# Reverse relationships should also be gone
@@ -90,7 +90,7 @@ API_TESTS = """
[David]
# Who is friends with David?
->>> d.friends.all()
+>>> d.friends.all()
[Chuck]
@@ -105,7 +105,7 @@ API_TESTS = """
>>> d.stalkers.add(a,c)
# Who are Anne's idols?
->>> a.idols.all()
+>>> a.idols.all()
[Bill, Chuck, David]
# Who is stalking Anne?
@@ -140,7 +140,7 @@ API_TESTS = """
>>> b.stalkers.add(a)
# Who are Anne's idols?
->>> a.idols.all()
+>>> a.idols.all()
[Bill, Chuck, David]
# Who is stalking Anne?
@@ -158,7 +158,7 @@ API_TESTS = """
>>> b.stalkers.remove(a)
# Who are Anne's idols?
->>> a.idols.all()
+>>> a.idols.all()
[Chuck, David]
# Who is stalking Anne?
@@ -177,7 +177,7 @@ API_TESTS = """
>>> a.idols.clear()
# Who are Anne's idols
->>> a.idols.all()
+>>> a.idols.all()
[]
# Reverse relationships should also be gone
@@ -186,7 +186,7 @@ API_TESTS = """
[]
# Who is friends with David?
->>> d.stalkers.all()
+>>> d.stalkers.all()
[Chuck]
"""
diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
index da47c0afd5..cf833cc468 100644
--- a/tests/modeltests/manipulators/models.py
+++ b/tests/modeltests/manipulators/models.py
@@ -1,5 +1,7 @@
"""
-25. Default manipulators
+26. Default manipulators
+
+Each model gets an AddManipulator and ChangeManipulator by default.
"""
from django.db import models
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index cdc4b4e2ac..f2d184017c 100644
--- a/tests/modeltests/model_inheritance/models.py
+++ b/tests/modeltests/model_inheritance/models.py
@@ -1,6 +1,7 @@
"""
XX. Model inheritance
+Model inheritance isn't yet supported.
"""
from django.db import models
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
index 6525168b97..3667c84d69 100644
--- a/tests/modeltests/pagination/models.py
+++ b/tests/modeltests/pagination/models.py
@@ -1,21 +1,20 @@
"""
-20. Object Pagination
-
-Django provides a framework for paginating a list of objects in a few.
-This is often useful for dividing search results or long lists of objects
-in to easily readable pages.
-
+29. Object pagination
+Django provides a framework for paginating a list of objects in a few lines
+of code. This is often useful for dividing search results or long lists of
+objects into easily readable pages.
"""
+
from django.db import models
class Article(models.Model):
headline = models.CharField(maxlength=100, default='Default headline')
pub_date = models.DateTimeField()
-
+
def __repr__(self):
- return self.headline
-
+ return self.headline
+
API_TESTS = """
# prepare a list of objects for pagination
>>> from datetime import datetime
@@ -34,8 +33,8 @@ API_TESTS = """
>>> paginator.pages
2
-# get the first page (zero-based)
->>> paginator.get_page(0)
+# get the first page (zero-based)
+>>> paginator.get_page(0)
[Article 1, Article 2, Article 3, Article 4, Article 5]
# get the second page
@@ -45,7 +44,7 @@ API_TESTS = """
# does the first page have a next or previous page?
>>> paginator.has_next_page(0)
True
-
+
>>> paginator.has_previous_page(0)
False
@@ -55,5 +54,14 @@ False
>>> paginator.has_previous_page(1)
True
-
+
+>>> paginator.first_on_page(0)
+1
+>>> paginator.first_on_page(1)
+6
+>>> paginator.last_on_page(0)
+5
+>>> paginator.last_on_page(1)
+9
+
"""
diff --git a/tests/modeltests/properties/models.py b/tests/modeltests/properties/models.py
index 2c2190e989..e9d8da9594 100644
--- a/tests/modeltests/properties/models.py
+++ b/tests/modeltests/properties/models.py
@@ -1,5 +1,7 @@
"""
22. Using properties on models
+
+Use properties on models just like on any other Python object.
"""
from django.db import models
diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py
index 22f38f7a0c..ef7791c3f3 100644
--- a/tests/modeltests/transactions/models.py
+++ b/tests/modeltests/transactions/models.py
@@ -1,5 +1,5 @@
"""
-XXX. Transactions
+15. Transactions
Django handles transactions in three different ways. The default is to commit
each transaction upon a write, but you can decorate a function to get
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
index d03fffea25..8904c42727 100644
--- a/tests/modeltests/validation/models.py
+++ b/tests/modeltests/validation/models.py
@@ -1,5 +1,7 @@
"""
-XX. Validation
+30. Validation
+
+This is an experimental feature!
Each model instance has a validate() method that returns a dictionary of
validation errors in the instance's fields. This method has a side effect