1
0
mirror of https://github.com/django/django.git synced 2025-07-03 17:29:12 +00:00

newforms-admin: Merged from trunk up to [7808]. Fixed #7519, #7573

git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7809 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Brian Rosner 2008-06-30 15:38:16 +00:00
parent c349ba4cfc
commit 829fd5a967
60 changed files with 4233 additions and 1879 deletions

View File

@ -95,6 +95,7 @@ answer newbie questions, and generally made Django that much better:
Sengtha Chay <sengtha@e-khmer.com>
ivan.chelubeev@gmail.com
Bryan Chow <bryan at verdjn dot com>
Antonis Christofides <anthony@itia.ntua.gr>
Michal Chruszcz <troll@pld-linux.org>
Can Burak Çilingir <canburak@cs.bilgi.edu.tr>
Ian Clelland <clelland@gmail.com>
@ -195,6 +196,7 @@ answer newbie questions, and generally made Django that much better:
jcrasta@gmail.com
jdetaeye
Zak Johnson <zakj@nox.cx>
Nis Jørgensen <nis@superlativ.dk>
Michael Josephson <http://www.sdjournal.com/>
jpellerin@gmail.com
junzhang.jn@gmail.com

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ from django.contrib import admin
class GroupAdmin(admin.ModelAdmin):
search_fields = ('name',)
ordering = ('name',)
filter_horizontal = ('permissions',)
class UserAdmin(admin.ModelAdmin):
@ -21,6 +22,7 @@ class UserAdmin(admin.ModelAdmin):
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_filter = ('is_staff', 'is_superuser')
search_fields = ('username', 'first_name', 'last_name', 'email')
ordering = ('username',)
filter_horizontal = ('user_permissions',)
def add_view(self, request):

View File

@ -0,0 +1,56 @@
[
{
"pk": "1",
"model": "auth.user",
"fields": {
"username": "testclient",
"first_name": "Test",
"last_name": "Client",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"last_login": "2006-12-17 07:03:31",
"groups": [],
"user_permissions": [],
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
"email": "testclient@example.com",
"date_joined": "2006-12-17 07:03:31"
}
},
{
"pk": "2",
"model": "auth.user",
"fields": {
"username": "inactive",
"first_name": "Inactive",
"last_name": "User",
"is_active": false,
"is_superuser": false,
"is_staff": false,
"last_login": "2006-12-17 07:03:31",
"groups": [],
"user_permissions": [],
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
"email": "testclient@example.com",
"date_joined": "2006-12-17 07:03:31"
}
},
{
"pk": "3",
"model": "auth.user",
"fields": {
"username": "staff",
"first_name": "Staff",
"last_name": "Member",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"last_login": "2006-12-17 07:03:31",
"groups": [],
"user_permissions": [],
"password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
"email": "staffmember@example.com",
"date_joined": "2006-12-17 07:03:31"
}
}
]

View File

@ -96,8 +96,7 @@ class Group(models.Model):
class Meta:
verbose_name = _('group')
verbose_name_plural = _('groups')
ordering = ('name',)
def __unicode__(self):
return self.name
@ -150,8 +149,7 @@ class User(models.Model):
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
ordering = ('username',)
def __unicode__(self):
return self.username

View File

@ -1,8 +1,8 @@
from django.contrib.auth.tests.basic import BASIC_TESTS
from django.contrib.auth.tests.forms import FORM_TESTS, PasswordResetFormTestCase
from django.contrib.auth.tests.basic import BASIC_TESTS, PasswordResetTest
from django.contrib.auth.tests.forms import FORM_TESTS
__test__ = {
'BASIC_TESTS': BASIC_TESTS,
'PASSWORDRESET_TESTS': PasswordResetFormTestCase,
'PASSWORDRESET_TESTS': PasswordResetTest,
'FORM_TESTS': FORM_TESTS,
}

View File

@ -53,4 +53,25 @@ Superuser created successfully.
u'joe@somewhere.org'
>>> u.password
u'!'
"""
"""
from django.test import TestCase
from django.core import mail
class PasswordResetTest(TestCase):
fixtures = ['authtestdata.json']
urls = 'django.contrib.auth.urls'
def test_email_not_found(self):
"Error is raised if the provided email address isn't currently registered"
response = self.client.get('/password_reset/')
self.assertEquals(response.status_code, 200)
response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'})
self.assertContains(response, "That e-mail address doesn't have an associated user account")
self.assertEquals(len(mail.outbox), 0)
def test_email_found(self):
"Email is sent if a valid email address is provided for password reset"
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'})
self.assertEquals(response.status_code, 302)
self.assertEquals(len(mail.outbox), 1)

View File

@ -1,33 +1,4 @@
from django.core import mail
from django.test import TestCase
from django.contrib.auth.models import User
from django.contrib.auth.forms import PasswordResetForm
class PasswordResetFormTestCase(TestCase):
def testValidUser(self):
data = {
'email': 'nonexistent@example.com',
}
form = PasswordResetForm(data)
self.assertEqual(form.is_valid(), False)
self.assertEqual(form["email"].errors, [u"That e-mail address doesn't have an associated user account. Are you sure you've registered?"])
def testEmail(self):
# TODO: remove my email address from the test ;)
User.objects.create_user('atestuser', 'atestuser@example.com', 'test789')
data = {
'email': 'atestuser@example.com',
}
form = PasswordResetForm(data)
self.assertEqual(form.is_valid(), True)
# TODO: look at why using contrib.sites breaks other tests
form.save(domain_override="example.com")
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, u'Password reset on example.com')
# TODO: test mail body. need to figure out a way to get the password in plain text
# self.assertEqual(mail.outbox[0].body, '')
FORM_TESTS = """
>>> from django.contrib.auth.models import User
>>> from django.contrib.auth.forms import UserCreationForm, AuthenticationForm

View File

@ -0,0 +1,13 @@
# These URLs are normally mapped to /admin/urls.py. This URLs file is
# provided as a convenience to those who want to deploy these URLs elsewhere.
# This file is also used to provide a reliable view deployment for test purposes.
from django.conf.urls.defaults import *
urlpatterns = patterns('',
('^logout/$', 'django.contrib.auth.views.logout'),
('^password_change/$', 'django.contrib.auth.views.password_change'),
('^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
('^password_reset/$', 'django.contrib.auth.views.password_reset')
)

View File

@ -8,7 +8,7 @@ class FlatPage(models.Model):
url = models.CharField(_('URL'), max_length=100, validator_list=[validators.isAlphaNumericURL], db_index=True,
help_text=_("Example: '/about/contact/'. Make sure to have leading and trailing slashes."))
title = models.CharField(_('title'), max_length=200)
content = models.TextField(_('content'))
content = models.TextField(_('content'), blank=True)
enable_comments = models.BooleanField(_('enable comments'))
template_name = models.CharField(_('template name'), max_length=70, blank=True,
help_text=_("Example: 'flatpages/contact_page.html'. If this isn't provided, the system will use 'flatpages/default.html'."))

View File

@ -21,18 +21,14 @@ class TestForm(forms.Form):
class PreviewTests(TestCase):
urls = 'django.contrib.formtools.test_urls'
def setUp(self):
self._old_root_urlconf = settings.ROOT_URLCONF
settings.ROOT_URLCONF = 'django.contrib.formtools.test_urls'
# Create a FormPreview instance to share between tests
self.preview = preview.FormPreview(TestForm)
input_template = '<input type="hidden" name="%s" value="%s" />'
self.input = input_template % (self.preview.unused_name('stage'), "%d")
def tearDown(self):
settings.ROOT_URLCONF = self._old_root_urlconf
def test_unused_name(self):
"""
Verifies name mangling to get uniue field name.

View File

@ -21,10 +21,10 @@ class Command(LabelCommand):
for f in fields:
field_output = [qn(f.name), f.db_type()]
field_output.append("%sNULL" % (not f.null and "NOT " or ""))
if f.unique:
field_output.append("UNIQUE")
if f.primary_key:
field_output.append("PRIMARY KEY")
elif f.unique:
field_output.append("UNIQUE")
if f.db_index:
unique = f.unique and "UNIQUE " or ""
index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \

View File

@ -162,3 +162,9 @@ class Command(BaseCommand):
else:
if verbosity > 0:
print "Installed %d object(s) from %d fixture(s)" % (object_count, fixture_count)
# Close the DB connection. This is required as a workaround for an
# edge case in MySQL: if the same connection is used to
# create tables, load data, and query, the query can return
# incorrect results. See Django #7572, MySQL #37735.
connection.close()

View File

@ -268,11 +268,11 @@ def sql_model_create(model, style, known_models=set()):
field_output = [style.SQL_FIELD(qn(f.column)),
style.SQL_COLTYPE(col_type)]
field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
if f.unique and (not f.primary_key or connection.features.allows_unique_and_pk):
field_output.append(style.SQL_KEYWORD('UNIQUE'))
if f.primary_key:
field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
if tablespace and connection.features.supports_tablespaces and (f.unique or f.primary_key) and connection.features.autoindexes_primary_keys:
elif f.unique:
field_output.append(style.SQL_KEYWORD('UNIQUE'))
if tablespace and connection.features.supports_tablespaces and f.unique:
# We must specify the index tablespace inline, because we
# won't be generating a CREATE INDEX statement for this field.
field_output.append(connection.ops.tablespace_sql(tablespace, inline=True))
@ -355,7 +355,7 @@ def many_to_many_sql_for_model(model, style):
for f in opts.local_many_to_many:
if not isinstance(f.rel, generic.GenericRel):
tablespace = f.db_tablespace or opts.db_tablespace
if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys:
if tablespace and connection.features.supports_tablespaces:
tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True)
else:
tablespace_sql = ''
@ -460,15 +460,14 @@ def sql_indexes_for_model(model, style):
qn = connection.ops.quote_name
for f in model._meta.local_fields:
if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys):
unique = f.unique and 'UNIQUE ' or ''
if f.db_index and not f.unique:
tablespace = f.db_tablespace or model._meta.db_tablespace
if tablespace and connection.features.supports_tablespaces:
tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace)
else:
tablespace_sql = ''
output.append(
style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
style.SQL_KEYWORD('CREATE INDEX') + ' ' + \
style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \
style.SQL_KEYWORD('ON') + ' ' + \
style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \

View File

@ -551,6 +551,9 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
def __init__(self, *args, **kwargs):
from django.conf import settings
self.admin_media_prefix = settings.ADMIN_MEDIA_PREFIX
# We set self.path to avoid crashes in log_message() on unsupported
# requests (like "OPTIONS").
self.path = ''
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
def get_environ(self):

View File

@ -40,6 +40,7 @@ Optional Fcgi settings: (setting=value)
workdir=DIRECTORY change to this directory when daemonizing.
outlog=FILE write stdout to this file.
errlog=FILE write stderr to this file.
umask=UMASK umask to use when daemonizing (default 022).
Examples:
Run a "standard" fastcgi process on a file-descriptor
@ -73,6 +74,7 @@ FASTCGI_OPTIONS = {
'maxrequests': 0,
'outlog': None,
'errlog': None,
'umask': None,
}
def fastcgi_help(message=None):
@ -159,6 +161,8 @@ def runfastcgi(argset=[], **kwargs):
daemon_kwargs['out_log'] = options['outlog']
if options['errlog']:
daemon_kwargs['err_log'] = options['errlog']
if options['umask']:
daemon_kwargs['umask'] = int(options['umask'])
if daemonize:
from django.utils.daemonize import become_daemon

View File

@ -296,3 +296,8 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None):
kwargs = kwargs or {}
return iri_to_uri(u'/' + get_resolver(urlconf).reverse(viewname, *args, **kwargs))
def clear_url_caches():
global _resolver_cache
global _callable_cache
_resolver_cache.clear()
_callable_cache.clear()

View File

@ -41,8 +41,6 @@ class BaseDatabaseWrapper(local):
class BaseDatabaseFeatures(object):
allows_group_by_ordinal = True
allows_unique_and_pk = True
autoindexes_primary_keys = True
inline_fk_references = True
needs_datetime_string_cast = True
supports_constraints = True

View File

@ -60,7 +60,6 @@ server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})')
# TRADITIONAL will automatically cause most warnings to be treated as errors.
class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False
inline_fk_references = False
empty_fetchmany_value = ()
update_can_self_select = False
@ -136,7 +135,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
operators = {
'exact': '= %s',
'exact': '= BINARY %s',
'iexact': 'LIKE %s',
'contains': 'LIKE BINARY %s',
'icontains': 'LIKE %s',

View File

@ -64,7 +64,6 @@ class MysqlDebugWrapper:
return getattr(self.cursor, attr)
class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False
inline_fk_references = False
empty_fetchmany_value = ()
update_can_self_select = False
@ -140,7 +139,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
features = DatabaseFeatures()
ops = DatabaseOperations()
operators = {
'exact': '= %s',
'exact': '= BINARY %s',
'iexact': 'LIKE %s',
'contains': 'LIKE BINARY %s',
'icontains': 'LIKE %s',

View File

@ -24,7 +24,6 @@ IntegrityError = Database.IntegrityError
class DatabaseFeatures(BaseDatabaseFeatures):
allows_group_by_ordinal = False
allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259)
empty_fetchmany_value = ()
needs_datetime_string_cast = False
supports_tablespaces = True

View File

@ -23,7 +23,7 @@ DATA_TYPES = {
'ImageField': 'NVARCHAR2(%(max_length)s)',
'IntegerField': 'NUMBER(11)',
'IPAddressField': 'VARCHAR2(15)',
'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(column)s IS NULL))',
'NullBooleanField': 'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
'OneToOneField': 'NUMBER(11)',
'PhoneNumberField': 'VARCHAR2(20)',
'PositiveIntegerField': 'NUMBER(11) CHECK (%(qn_column)s >= 0)',

View File

@ -97,7 +97,7 @@ class DatabaseOperations(BaseDatabaseOperations):
# Use `coalesce` to set the sequence for each model to the max pk value if there are records,
# or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true
# if there are records (as the max pk value is already in use), otherwise set it to false.
for f in model._meta.fields:
for f in model._meta.local_fields:
if isinstance(f, models.AutoField):
output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
(style.SQL_KEYWORD('SELECT'),

View File

@ -50,7 +50,15 @@ class ModelBase(type):
meta = attr_meta
base_meta = getattr(new_class, '_meta', None)
new_class.add_to_class('_meta', Options(meta))
if getattr(meta, 'app_label', None) is None:
# Figure out the app_label by looking one level up.
# For 'django.contrib.sites.models', this would be 'sites'.
model_module = sys.modules[new_class.__module__]
kwargs = {"app_label": model_module.__name__.split('.')[-2]}
else:
kwargs = {}
new_class.add_to_class('_meta', Options(meta, **kwargs))
if not abstract:
new_class.add_to_class('DoesNotExist',
subclass_exception('DoesNotExist', ObjectDoesNotExist, module))
@ -71,11 +79,6 @@ class ModelBase(type):
if new_class._default_manager.model._meta.abstract:
old_default_mgr = new_class._default_manager
new_class._default_manager = None
if getattr(new_class._meta, 'app_label', None) is None:
# Figure out the app_label by looking one level up.
# For 'django.contrib.sites.models', this would be 'sites'.
model_module = sys.modules[new_class.__module__]
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, False)
@ -389,6 +392,21 @@ class Model(object):
for sub_obj in getattr(self, rel_opts_name).all():
sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
# Handle any ancestors (for the model-inheritance case). We do this by
# traversing to the most remote parent classes -- those with no parents
# themselves -- and then adding those instances to the collection. That
# will include all the child instances down to "self".
parent_stack = self._meta.parents.values()
while parent_stack:
link = parent_stack.pop()
parent_obj = getattr(self, link.name)
if parent_obj._meta.parents:
parent_stack.extend(parent_obj._meta.parents.values())
continue
# At this point, parent_obj is base class (no ancestor models). So
# delete it and all its descendents.
parent_obj._collect_sub_objects(seen_objs)
def delete(self):
assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
@ -436,7 +454,7 @@ class Model(object):
def _get_FIELD_filename(self, field):
if getattr(self, field.attname): # value is not blank
return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
return os.path.normpath(os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname)))
return ''
def _get_FIELD_url(self, field):

View File

@ -85,7 +85,7 @@ class Field(object):
self.name = name
self.verbose_name = verbose_name
self.primary_key = primary_key
self.max_length, self.unique = max_length, unique
self.max_length, self._unique = max_length, unique
self.blank, self.null = blank, null
# Oracle treats the empty string ('') as null, so coerce the null
# option whenever '' is a possible value.
@ -160,6 +160,10 @@ class Field(object):
except KeyError:
return None
def unique(self):
return self._unique or self.primary_key
unique = property(unique)
def validate_full(self, field_data, all_data):
"""
Returns a list of errors for this field. This is the main interface,
@ -676,7 +680,7 @@ class DecimalField(Field):
_("This value must be a decimal number."))
def _format(self, value):
if isinstance(value, basestring):
if isinstance(value, basestring) or value is None:
return value
else:
return self.format_number(value)
@ -697,8 +701,7 @@ class DecimalField(Field):
return u"%.*f" % (self.decimal_places, value)
def get_db_prep_save(self, value):
if value is not None:
value = self._format(value)
value = self._format(value)
return super(DecimalField, self).get_db_prep_save(value)
def get_db_prep_lookup(self, lookup_type, value):
@ -1151,12 +1154,3 @@ class XMLField(TextField):
def get_manipulator_field_objs(self):
return [curry(oldforms.XMLLargeTextField, schema_path=self.schema_path)]
class OrderingField(IntegerField):
empty_strings_allowed=False
def __init__(self, with_respect_to, **kwargs):
self.wrt = with_respect_to
kwargs['null'] = True
IntegerField.__init__(self, **kwargs )
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
return [oldforms.HiddenField(name_prefix + self.name)]

View File

@ -185,11 +185,11 @@ class SingleRelatedObjectDescriptor(object):
def __set__(self, instance, value):
if instance is None:
raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name
# The similarity of the code below to the code in
# The similarity of the code below to the code in
# ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch
# of small differences that would make a common base class convoluted.
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
if value is None and self.related.field.null == False:
@ -197,14 +197,14 @@ class SingleRelatedObjectDescriptor(object):
(instance._meta.object_name, self.related.get_accessor_name()))
elif value is not None and not isinstance(value, self.related.model):
raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
(value, instance._meta.object_name,
(value, instance._meta.object_name,
self.related.get_accessor_name(), self.related.opts.object_name))
# Set the value of the related field
setattr(value, self.related.field.rel.get_related_field().attname, instance)
# Since we already know what the related object is, seed the related
# object caches now, too. This avoids another db hit if you get the
# object caches now, too. This avoids another db hit if you get the
# object you just set.
setattr(instance, self.cache_name, value)
setattr(value, self.related.field.get_cache_name(), instance)
@ -243,7 +243,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
def __set__(self, instance, value):
if instance is None:
raise AttributeError, "%s must be accessed via instance" % self._field.name
# If null=True, we can assign null here, but otherwise the value needs
# to be an instance of the related class.
if value is None and self.field.null == False:
@ -251,9 +251,9 @@ class ReverseSingleRelatedObjectDescriptor(object):
(instance._meta.object_name, self.field.name))
elif value is not None and not isinstance(value, self.field.rel.to):
raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' %
(value, instance._meta.object_name,
(value, instance._meta.object_name,
self.field.name, self.field.rel.to._meta.object_name))
# Set the value of the related field
try:
val = getattr(value, self.field.rel.get_related_field().attname)
@ -262,7 +262,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
setattr(instance, self.field.attname, val)
# Since we already know what the related object is, seed the related
# object cache now, too. This avoids another db hit if you get the
# object cache now, too. This avoids another db hit if you get the
# object you just set.
setattr(instance, self.field.get_cache_name(), value)
@ -322,7 +322,9 @@ class ForeignRelatedObjectsDescriptor(object):
clear.alters_data = True
manager = RelatedManager()
manager.core_filters = {'%s__pk' % rel_field.name: getattr(instance, rel_field.rel.get_related_field().attname)}
attname = rel_field.rel.get_related_field().name
manager.core_filters = {'%s__%s' % (rel_field.name, attname):
getattr(instance, attname)}
manager.model = self.related.model
return manager
@ -670,6 +672,11 @@ class ForeignKey(RelatedField, Field):
def contribute_to_class(self, cls, name):
super(ForeignKey, self).contribute_to_class(cls, name)
setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
if isinstance(self.rel.to, basestring):
target = self.rel.to
else:
target = self.rel.to._meta.db_table
cls._meta.duplicate_targets[self.column] = (target, "o2m")
def contribute_to_related_class(self, cls, related):
setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
@ -791,6 +798,12 @@ class ManyToManyField(RelatedField, Field):
# Set up the accessor for the m2m table name for the relation
self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
if isinstance(self.rel.to, basestring):
target = self.rel.to
else:
target = self.rel.to._meta.db_table
cls._meta.duplicate_targets[self.column] = (target, "m2m")
def contribute_to_related_class(self, cls, related):
# m2m relations to self do not have a ManyRelatedObjectsDescriptor,
# as it would be redundant - unless the field is non-symmetrical.

View File

@ -24,7 +24,7 @@ DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
'abstract')
class Options(object):
def __init__(self, meta):
def __init__(self, meta, app_label=None):
self.local_fields, self.local_many_to_many = [], []
self.module_name, self.verbose_name = None, None
self.verbose_name_plural = None
@ -32,7 +32,7 @@ class Options(object):
self.ordering = []
self.unique_together = []
self.permissions = []
self.object_name, self.app_label = None, None
self.object_name, self.app_label = None, app_label
self.get_latest_by = None
self.order_with_respect_to = None
self.db_tablespace = settings.DEFAULT_TABLESPACE
@ -43,8 +43,12 @@ class Options(object):
self.one_to_one_field = None
self.abstract = False
self.parents = SortedDict()
self.duplicate_targets = {}
def contribute_to_class(self, cls, name):
from django.db import connection
from django.db.backends.util import truncate_name
cls._meta = self
self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
# First, construct the default values for these options.
@ -86,9 +90,13 @@ class Options(object):
self.verbose_name_plural = string_concat(self.verbose_name, 's')
del self.meta
# If the db_table wasn't provided, use the app_label + module_name.
if not self.db_table:
self.db_table = "%s_%s" % (self.app_label, self.module_name)
self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
def _prepare(self, model):
from django.db import connection
from django.db.backends.util import truncate_name
if self.order_with_respect_to:
self.order_with_respect_to = self.get_field(self.order_with_respect_to)
self.ordering = ('_order',)
@ -107,10 +115,23 @@ class Options(object):
auto_created=True)
model.add_to_class('id', auto)
# If the db_table wasn't provided, use the app_label + module_name.
if not self.db_table:
self.db_table = "%s_%s" % (self.app_label, self.module_name)
self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
# Determine any sets of fields that are pointing to the same targets
# (e.g. two ForeignKeys to the same remote model). The query
# construction code needs to know this. At the end of this,
# self.duplicate_targets will map each duplicate field column to the
# columns it duplicates.
collections = {}
for column, target in self.duplicate_targets.iteritems():
try:
collections[target].add(column)
except KeyError:
collections[target] = set([column])
self.duplicate_targets = {}
for elt in collections.itervalues():
if len(elt) == 1:
continue
for column in elt:
self.duplicate_targets[column] = elt.difference(set([column]))
def add_field(self, field):
# Insert the given field in the order in which it was created, using

View File

@ -3,7 +3,7 @@ import warnings
from django.conf import settings
from django.db import connection, transaction, IntegrityError
from django.db.models.fields import DateField, FieldDoesNotExist
from django.db.models.query_utils import Q
from django.db.models.query_utils import Q, select_related_descend
from django.db.models import signals, sql
from django.dispatch import dispatcher
from django.utils.datastructures import SortedDict
@ -761,8 +761,7 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0,
index_end = index_start + len(klass._meta.fields)
obj = klass(*row[index_start:index_end])
for f in klass._meta.fields:
if (not f.rel or (not restricted and f.null) or
(restricted and f.name not in requested) or f.rel.parent_link):
if not select_related_descend(f, restricted, requested):
continue
if restricted:
next = requested[f.name]

View File

@ -48,3 +48,20 @@ class Q(tree.Node):
obj.negate()
return obj
def select_related_descend(field, restricted, requested):
"""
Returns True if this field should be used to descend deeper for
select_related() purposes. Used by both the query construction code
(sql.query.fill_related_selections()) and the model instance creation code
(query.get_cached_row()).
"""
if not field.rel:
return False
if field.rel.parent_link:
return False
if restricted and field.name not in requested:
return False
if not restricted and field.null:
return False
return True

View File

@ -7,6 +7,7 @@ databases). The abstraction barrier only works one way: this module has to know
all about the internals of models in order to get the information it needs.
"""
import datetime
from copy import deepcopy
from django.utils.tree import Node
@ -14,9 +15,10 @@ from django.utils.datastructures import SortedDict
from django.dispatch import dispatcher
from django.db import connection
from django.db.models import signals
from django.db.models.fields import FieldDoesNotExist
from django.db.models.query_utils import select_related_descend
from django.db.models.sql.where import WhereNode, EverythingNode, AND, OR
from django.db.models.sql.datastructures import Count
from django.db.models.fields import FieldDoesNotExist
from django.core.exceptions import FieldError
from datastructures import EmptyResultSet, Empty, MultiJoin
from constants import *
@ -56,6 +58,7 @@ class Query(object):
self.start_meta = None
self.select_fields = []
self.related_select_fields = []
self.dupe_avoidance = {}
# SQL-related attributes
self.select = []
@ -164,6 +167,7 @@ class Query(object):
obj.start_meta = self.start_meta
obj.select_fields = self.select_fields[:]
obj.related_select_fields = self.related_select_fields[:]
obj.dupe_avoidance = self.dupe_avoidance.copy()
obj.select = self.select[:]
obj.tables = self.tables[:]
obj.where = deepcopy(self.where)
@ -214,7 +218,7 @@ class Query(object):
obj.select_related = False
obj.related_select_cols = []
obj.related_select_fields = []
if obj.distinct and len(obj.select) > 1:
if len(obj.select) > 1:
obj = self.clone(CountQuery, _query=obj, where=self.where_class(),
distinct=False)
obj.select = []
@ -362,10 +366,21 @@ class Query(object):
item.relabel_aliases(change_map)
self.select.append(item)
self.select_fields = rhs.select_fields[:]
self.extra_select = rhs.extra_select.copy()
self.extra_tables = rhs.extra_tables
self.extra_where = rhs.extra_where
self.extra_params = rhs.extra_params
if connector == OR:
# It would be nice to be able to handle this, but the queries don't
# really make sense (or return consistent value sets). Not worth
# the extra complexity when you can write a real query instead.
if self.extra_select and rhs.extra_select:
raise ValueError("When merging querysets using 'or', you "
"cannot have extra(select=...) on both sides.")
if self.extra_where and rhs.extra_where:
raise ValueError("When merging querysets using 'or', you "
"cannot have extra(where=...) on both sides.")
self.extra_select.update(rhs.extra_select)
self.extra_tables += rhs.extra_tables
self.extra_where += rhs.extra_where
self.extra_params += rhs.extra_params
# Ordering uses the 'rhs' ordering, unless it has none, in which case
# the current ordering is used.
@ -439,28 +454,39 @@ class Query(object):
self._select_aliases = aliases
return result
def get_default_columns(self, with_aliases=False, col_aliases=None):
def get_default_columns(self, with_aliases=False, col_aliases=None,
start_alias=None, opts=None, as_pairs=False):
"""
Computes the default columns for selecting every field in the base
model.
Returns a list of strings, quoted appropriately for use in SQL
directly, as well as a set of aliases used in the select statement.
directly, as well as a set of aliases used in the select statement (if
'as_pairs' is True, returns a list of (alias, col_name) pairs instead
of strings as the first component and None as the second component).
"""
result = []
table_alias = self.tables[0]
root_pk = self.model._meta.pk.column
if opts is None:
opts = self.model._meta
if start_alias:
table_alias = start_alias
else:
table_alias = self.tables[0]
root_pk = opts.pk.column
seen = {None: table_alias}
qn = self.quote_name_unless_alias
qn2 = self.connection.ops.quote_name
aliases = set()
for field, model in self.model._meta.get_fields_with_model():
for field, model in opts.get_fields_with_model():
try:
alias = seen[model]
except KeyError:
alias = self.join((table_alias, model._meta.db_table,
root_pk, model._meta.pk.column))
seen[model] = alias
if as_pairs:
result.append((alias, field.column))
continue
if with_aliases and field.column in col_aliases:
c_alias = 'Col%d' % len(col_aliases)
result.append('%s.%s AS %s' % (qn(alias),
@ -473,6 +499,8 @@ class Query(object):
aliases.add(r)
if with_aliases:
col_aliases.add(field.column)
if as_pairs:
return result, None
return result, aliases
def get_from_clause(self):
@ -609,6 +637,11 @@ class Query(object):
alias, False)
alias = joins[-1]
col = target.column
if not field.rel:
# To avoid inadvertent trimming of a necessary alias, use the
# refcount to show that we are referencing a non-relation field on
# the model.
self.ref_alias(alias)
# Must use left outer joins for nullable fields.
for join in joins:
@ -829,8 +862,8 @@ class Query(object):
if reuse and always_create and table in self.table_map:
# Convert the 'reuse' to case to be "exclude everything but the
# reusable set for this table".
exclusions = set(self.table_map[table]).difference(reuse)
# reusable set, minus exclusions, for this table".
exclusions = set(self.table_map[table]).difference(reuse).union(set(exclusions))
always_create = False
t_ident = (lhs_table, table, lhs_col, col)
if not always_create:
@ -865,7 +898,8 @@ class Query(object):
return alias
def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1,
used=None, requested=None, restricted=None, nullable=None):
used=None, requested=None, restricted=None, nullable=None,
dupe_set=None):
"""
Fill in the information needed for a select_related query. The current
depth is measured as the number of connections away from the root model
@ -875,6 +909,7 @@ class Query(object):
if not restricted and self.max_depth and cur_depth > self.max_depth:
# We've recursed far enough; bail out.
return
if not opts:
opts = self.get_meta()
root_alias = self.get_initial_alias()
@ -882,6 +917,10 @@ class Query(object):
self.related_select_fields = []
if not used:
used = set()
if dupe_set is None:
dupe_set = set()
orig_dupe_set = dupe_set
orig_used = used
# Setup for the case when only particular related fields should be
# included in the related selection.
@ -893,9 +932,10 @@ class Query(object):
restricted = False
for f, model in opts.get_fields_with_model():
if (not f.rel or (restricted and f.name not in requested) or
(not restricted and f.null) or f.rel.parent_link):
if not select_related_descend(f, restricted, requested):
continue
dupe_set = orig_dupe_set.copy()
used = orig_used.copy()
table = f.rel.to._meta.db_table
if nullable or f.null:
promote = True
@ -906,18 +946,32 @@ class Query(object):
alias = root_alias
for int_model in opts.get_base_chain(model):
lhs_col = int_opts.parents[int_model].column
dedupe = lhs_col in opts.duplicate_targets
if dedupe:
used.update(self.dupe_avoidance.get(id(opts), lhs_col),
())
dupe_set.add((opts, lhs_col))
int_opts = int_model._meta
alias = self.join((alias, int_opts.db_table, lhs_col,
int_opts.pk.column), exclusions=used,
promote=promote)
for (dupe_opts, dupe_col) in dupe_set:
self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
else:
alias = root_alias
dedupe = f.column in opts.duplicate_targets
if dupe_set or dedupe:
used.update(self.dupe_avoidance.get((id(opts), f.column), ()))
if dedupe:
dupe_set.add((opts, f.column))
alias = self.join((alias, table, f.column,
f.rel.get_related_field().column), exclusions=used,
promote=promote)
used.add(alias)
self.related_select_cols.extend([(alias, f2.column)
for f2 in f.rel.to._meta.fields])
self.related_select_cols.extend(self.get_default_columns(
start_alias=alias, opts=f.rel.to._meta, as_pairs=True)[0])
self.related_select_fields.extend(f.rel.to._meta.fields)
if restricted:
next = requested.get(f.name, {})
@ -927,8 +981,10 @@ class Query(object):
new_nullable = f.null
else:
new_nullable = None
for dupe_opts, dupe_col in dupe_set:
self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1,
used, next, restricted, new_nullable)
used, next, restricted, new_nullable, dupe_set)
def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
can_reuse=None):
@ -1048,7 +1104,19 @@ class Query(object):
# that's harmless.
self.promote_alias(table)
self.where.add((alias, col, field, lookup_type, value), connector)
# To save memory and copying time, convert the value from the Python
# object to the actual value used in the SQL query.
if field:
params = field.get_db_prep_lookup(lookup_type, value)
else:
params = Field().get_db_prep_lookup(lookup_type, value)
if isinstance(value, datetime.datetime):
annotation = datetime.datetime
else:
annotation = bool(value)
self.where.add((alias, col, field.db_type(), lookup_type, annotation,
params), connector)
if negate:
for alias in join_list:
@ -1058,7 +1126,8 @@ class Query(object):
for alias in join_list:
if self.alias_map[alias][JOIN_TYPE] == self.LOUTER:
j_col = self.alias_map[alias][RHS_JOIN_COL]
entry = Node([(alias, j_col, None, 'isnull', True)])
entry = Node([(alias, j_col, None, 'isnull', True,
[True])])
entry.negate()
self.where.add(entry, AND)
break
@ -1066,7 +1135,7 @@ class Query(object):
# Leaky abstraction artifact: We have to specifically
# exclude the "foo__in=[]" case from this handling, because
# it's short-circuited in the Where class.
entry = Node([(alias, col, field, 'isnull', True)])
entry = Node([(alias, col, None, 'isnull', True, [True])])
entry.negate()
self.where.add(entry, AND)
@ -1114,7 +1183,9 @@ class Query(object):
(which gives the table we are joining to), 'alias' is the alias for the
table we are joining to. If dupe_multis is True, any many-to-many or
many-to-one joins will always create a new alias (necessary for
disjunctive filters).
disjunctive filters). If can_reuse is not None, it's a list of aliases
that can be reused in these joins (nothing else can be reused in this
case).
Returns the final field involved in the join, the target database
column (used for any 'where' constraint), the final 'opts' value and the
@ -1122,7 +1193,14 @@ class Query(object):
"""
joins = [alias]
last = [0]
dupe_set = set()
exclusions = set()
for pos, name in enumerate(names):
try:
exclusions.add(int_alias)
except NameError:
pass
exclusions.add(alias)
last.append(len(joins))
if name == 'pk':
name = opts.pk.name
@ -1141,6 +1219,7 @@ class Query(object):
names = opts.get_all_field_names()
raise FieldError("Cannot resolve keyword %r into field. "
"Choices are: %s" % (name, ", ".join(names)))
if not allow_many and (m2m or not direct):
for alias in joins:
self.unref_alias(alias)
@ -1150,12 +1229,27 @@ class Query(object):
alias_list = []
for int_model in opts.get_base_chain(model):
lhs_col = opts.parents[int_model].column
dedupe = lhs_col in opts.duplicate_targets
if dedupe:
exclusions.update(self.dupe_avoidance.get(
(id(opts), lhs_col), ()))
dupe_set.add((opts, lhs_col))
opts = int_model._meta
alias = self.join((alias, opts.db_table, lhs_col,
opts.pk.column), exclusions=joins)
opts.pk.column), exclusions=exclusions)
joins.append(alias)
exclusions.add(alias)
for (dupe_opts, dupe_col) in dupe_set:
self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
cached_data = opts._join_cache.get(name)
orig_opts = opts
dupe_col = direct and field.column or field.field.column
dedupe = dupe_col in opts.duplicate_targets
if dupe_set or dedupe:
if dedupe:
dupe_set.add((opts, dupe_col))
exclusions.update(self.dupe_avoidance.get((id(opts), dupe_col),
()))
if direct:
if m2m:
@ -1177,9 +1271,11 @@ class Query(object):
target)
int_alias = self.join((alias, table1, from_col1, to_col1),
dupe_multis, joins, nullable=True, reuse=can_reuse)
dupe_multis, exclusions, nullable=True,
reuse=can_reuse)
alias = self.join((int_alias, table2, from_col2, to_col2),
dupe_multis, joins, nullable=True, reuse=can_reuse)
dupe_multis, exclusions, nullable=True,
reuse=can_reuse)
joins.extend([int_alias, alias])
elif field.rel:
# One-to-one or many-to-one field
@ -1195,7 +1291,7 @@ class Query(object):
opts, target)
alias = self.join((alias, table, from_col, to_col),
exclusions=joins, nullable=field.null)
exclusions=exclusions, nullable=field.null)
joins.append(alias)
else:
# Non-relation fields.
@ -1223,9 +1319,11 @@ class Query(object):
target)
int_alias = self.join((alias, table1, from_col1, to_col1),
dupe_multis, joins, nullable=True, reuse=can_reuse)
dupe_multis, exclusions, nullable=True,
reuse=can_reuse)
alias = self.join((int_alias, table2, from_col2, to_col2),
dupe_multis, joins, nullable=True, reuse=can_reuse)
dupe_multis, exclusions, nullable=True,
reuse=can_reuse)
joins.extend([int_alias, alias])
else:
# One-to-many field (ForeignKey defined on the target model)
@ -1243,14 +1341,34 @@ class Query(object):
opts, target)
alias = self.join((alias, table, from_col, to_col),
dupe_multis, joins, nullable=True, reuse=can_reuse)
dupe_multis, exclusions, nullable=True,
reuse=can_reuse)
joins.append(alias)
for (dupe_opts, dupe_col) in dupe_set:
try:
self.update_dupe_avoidance(dupe_opts, dupe_col, int_alias)
except NameError:
self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
if pos != len(names) - 1:
raise FieldError("Join on field %r not permitted." % name)
return field, target, opts, joins, last
def update_dupe_avoidance(self, opts, col, alias):
"""
For a column that is one of multiple pointing to the same table, update
the internal data structures to note that this alias shouldn't be used
for those other columns.
"""
ident = id(opts)
for name in opts.duplicate_targets[col]:
try:
self.dupe_avoidance[ident, name].add(alias)
except KeyError:
self.dupe_avoidance[ident, name] = set([alias])
def split_exclude(self, filter_expr, prefix):
"""
When doing an exclude against any kind of N-to-many relation, we need

View File

@ -49,7 +49,7 @@ class DeleteQuery(Query):
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
where = self.where_class()
where.add((None, related.field.m2m_reverse_name(),
related.field, 'in',
related.field.db_type(), 'in', True,
pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),
AND)
self.do_query(related.field.m2m_db_table(), where)
@ -59,11 +59,11 @@ class DeleteQuery(Query):
if isinstance(f, generic.GenericRelation):
from django.contrib.contenttypes.models import ContentType
field = f.rel.to._meta.get_field(f.content_type_field_name)
w1.add((None, field.column, field, 'exact',
ContentType.objects.get_for_model(cls).id), AND)
w1.add((None, field.column, field.db_type(), 'exact', True,
[ContentType.objects.get_for_model(cls).id]), AND)
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
where = self.where_class()
where.add((None, f.m2m_column_name(), f, 'in',
where.add((None, f.m2m_column_name(), f.db_type(), 'in', True,
pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
AND)
if w1:
@ -81,7 +81,7 @@ class DeleteQuery(Query):
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
where = self.where_class()
field = self.model._meta.pk
where.add((None, field.column, field, 'in',
where.add((None, field.column, field.db_type(), 'in', True,
pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
self.do_query(self.model._meta.db_table, where)
@ -204,7 +204,7 @@ class UpdateQuery(Query):
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
self.where = self.where_class()
f = self.model._meta.pk
self.where.add((None, f.column, f, 'in',
self.where.add((None, f.column, f.db_type(), 'in', True,
pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
AND)
self.values = [(related_field.column, None, '%s')]

View File

@ -21,8 +21,9 @@ class WhereNode(tree.Node):
the correct SQL).
The children in this tree are usually either Q-like objects or lists of
[table_alias, field_name, field_class, lookup_type, value]. However, a
child could also be any class with as_sql() and relabel_aliases() methods.
[table_alias, field_name, db_type, lookup_type, value_annotation,
params]. However, a child could also be any class with as_sql() and
relabel_aliases() methods.
"""
default = AND
@ -88,29 +89,24 @@ class WhereNode(tree.Node):
def make_atom(self, child, qn):
"""
Turn a tuple (table_alias, field_name, field_class, lookup_type, value)
into valid SQL.
Turn a tuple (table_alias, field_name, db_type, lookup_type,
value_annot, params) into valid SQL.
Returns the string for the SQL fragment and the parameters to use for
it.
"""
table_alias, name, field, lookup_type, value = child
table_alias, name, db_type, lookup_type, value_annot, params = child
if table_alias:
lhs = '%s.%s' % (qn(table_alias), qn(name))
else:
lhs = qn(name)
db_type = field and field.db_type() or None
field_sql = connection.ops.field_cast_sql(db_type) % lhs
if isinstance(value, datetime.datetime):
if value_annot is datetime.datetime:
cast_sql = connection.ops.datetime_cast_sql()
else:
cast_sql = '%s'
if field:
params = field.get_db_prep_lookup(lookup_type, value)
else:
params = Field().get_db_prep_lookup(lookup_type, value)
if isinstance(params, QueryWrapper):
extra, params = params.data
else:
@ -123,11 +119,11 @@ class WhereNode(tree.Node):
connection.operators[lookup_type] % cast_sql), params)
if lookup_type == 'in':
if not value:
if not value_annot:
raise EmptyResultSet
if extra:
return ('%s IN %s' % (field_sql, extra), params)
return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(value))),
return ('%s IN (%s)' % (field_sql, ', '.join(['%s'] * len(params))),
params)
elif lookup_type in ('range', 'year'):
return ('%s BETWEEN %%s and %%s' % field_sql, params)
@ -135,8 +131,8 @@ class WhereNode(tree.Node):
return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type,
field_sql), params)
elif lookup_type == 'isnull':
return ('%s IS %sNULL' % (field_sql, (not value and 'NOT ' or '')),
params)
return ('%s IS %sNULL' % (field_sql,
(not value_annot and 'NOT ' or '')), ())
elif lookup_type == 'search':
return (connection.ops.fulltext_search_sql(field_sql), params)
elif lookup_type in ('regex', 'iregex'):

View File

@ -196,7 +196,10 @@ def commit_on_success(func):
managed(True)
try:
res = func(*args, **kw)
except Exception, e:
except (Exception, KeyboardInterrupt, SystemExit):
# (We handle KeyboardInterrupt and SystemExit specially, since
# they don't inherit from Exception in Python 2.5, but we
# should treat them uniformly here.)
if is_dirty():
rollback()
raise

View File

@ -19,14 +19,14 @@ class ConditionalGetMiddleware(object):
# Setting the status is enough here. The response handling path
# automatically removes content for this status code (in
# http.conditional_content_removal()).
response.status = 304
response.status_code = 304
if response.has_header('Last-Modified'):
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
if if_modified_since == response['Last-Modified']:
# Setting the status code is enough here (same reasons as
# above).
response.status = 304
response.status_code = 304
return response

View File

@ -535,13 +535,17 @@ class BooleanField(Field):
def clean(self, value):
"""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. Because bool("True") == True, we don't need to
# handle that explicitly.
if value == 'False':
return False
return bool(value)
value = False
else:
value = bool(value)
super(BooleanField, self).clean(value)
if not value and self.required:
raise ValidationError(self.error_messages['required'])
return value
class NullBooleanField(BooleanField):
"""

View File

@ -4,10 +4,12 @@ from urlparse import urlsplit, urlunsplit
from django.http import QueryDict
from django.db import transaction
from django.conf import settings
from django.core import mail
from django.core.management import call_command
from django.test import _doctest as doctest
from django.test.client import Client
from django.core.urlresolvers import clear_url_caches
normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
@ -54,6 +56,8 @@ class TestCase(unittest.TestCase):
* Flushing the database.
* If the Test Case class has a 'fixtures' member, installing the
named fixtures.
* If the Test Case class has a 'urls' member, replace the
ROOT_URLCONF with it.
* Clearing the mail test outbox.
"""
call_command('flush', verbosity=0, interactive=False)
@ -61,6 +65,10 @@ class TestCase(unittest.TestCase):
# We have to use this slightly awkward syntax due to the fact
# that we're using *args and **kwargs together.
call_command('loaddata', *self.fixtures, **{'verbosity': 0})
if hasattr(self, 'urls'):
self._old_root_urlconf = settings.ROOT_URLCONF
settings.ROOT_URLCONF = self.urls
clear_url_caches()
mail.outbox = []
def __call__(self, result=None):
@ -79,6 +87,23 @@ class TestCase(unittest.TestCase):
result.addError(self, sys.exc_info())
return
super(TestCase, self).__call__(result)
try:
self._post_teardown()
except (KeyboardInterrupt, SystemExit):
raise
except Exception:
import sys
result.addError(self, sys.exc_info())
return
def _post_teardown(self):
""" Performs any post-test things. This includes:
* Putting back the original ROOT_URLCONF if it was changed.
"""
if hasattr(self, '_old_root_urlconf'):
settings.ROOT_URLCONF = self._old_root_urlconf
clear_url_caches()
def assertRedirects(self, response, expected_url, status_code=302,
target_status_code=200, host=None):

View File

@ -2,7 +2,8 @@ import os
import sys
if os.name == 'posix':
def become_daemon(our_home_dir='.', out_log='/dev/null', err_log='/dev/null'):
def become_daemon(our_home_dir='.', out_log='/dev/null',
err_log='/dev/null', umask=022):
"Robustly turn into a UNIX daemon, running in our_home_dir."
# First fork
try:
@ -13,7 +14,7 @@ if os.name == 'posix':
sys.exit(1)
os.setsid()
os.chdir(our_home_dir)
os.umask(0)
os.umask(umask)
# Second fork
try:
@ -32,13 +33,13 @@ if os.name == 'posix':
# Set custom file descriptors so that they get proper buffering.
sys.stdout, sys.stderr = so, se
else:
def become_daemon(our_home_dir='.', out_log=None, err_log=None):
def become_daemon(our_home_dir='.', out_log=None, err_log=None, umask=022):
"""
If we're not running under a POSIX system, just simulate the daemon
mode by doing redirections and directory changing.
"""
os.chdir(our_home_dir)
os.umask(0)
os.umask(umask)
sys.stdin.close()
sys.stdout.close()
sys.stderr.close()

View File

@ -39,9 +39,10 @@ with the standard ``Auth*`` and ``Require`` directives::
example at the bottom of this note).
You'll also need to insert configuration directives that prevent Apache
from trying to use other authentication modules. Depending on which other
authentication modules you have loaded, you might need one or more of
the following directives::
from trying to use other authentication modules, as well as specifying
the ``AuthUserFile`` directive and pointing it to ``/dev/null``. Depending
on which other authentication modules you have loaded, you might need one
or more of the following directives::
AuthBasicAuthoritative Off
AuthDefaultAuthoritative Off
@ -65,6 +66,7 @@ with the standard ``Auth*`` and ``Require`` directives::
<Location /example/>
AuthType Basic
AuthName "example.com"
**AuthUserFile /dev/null**
**AuthBasicAuthoritative Off**
Require valid-user

View File

@ -443,6 +443,31 @@ This is roughly equivalent to::
Note, however, that the first of these will raise ``IndexError`` while the
second will raise ``DoesNotExist`` if no objects match the given criteria.
Combining QuerySets
-------------------
If you have two ``QuerySet`` instances that act on the same model, you can
combine them using ``&`` and ``|`` to get the items that are in both result
sets or in either results set, respectively. For example::
Entry.objects.filter(pubdate__gte=date1) & \
Entry.objects.filter(headline__startswith="What")
will combine the two queries into a single SQL query. Of course, in this case
you could have achieved the same result using multiple filters on the same
``QuerySet``, but sometimes the ability to combine individual ``QuerySet``
instance is useful.
Be careful, if you are using ``extra()`` to add custom handling to your
``QuerySet`` however. All the ``extra()`` components are merged and the result
may or may not make sense. If you are using custom SQL fragments in your
``extra()`` calls, Django will not inspect these fragments to see if they need
to be rewritten because of changes in the merged query. So test the effects
carefully. Also realise that if you are combining two ``QuerySets`` with
``|``, you cannot use ``extra(select=...)`` or ``extra(where=...)`` on *both*
``QuerySets``. You can only use those calls on one or the other (Django will
raise a ``ValueError`` if you try to use this incorrectly).
QuerySet methods that return new QuerySets
------------------------------------------

View File

@ -14,9 +14,14 @@ custom Django application.
A flatpage can use a custom template or a default, systemwide flatpage
template. It can be associated with one, or multiple, sites.
**New in Django development version**
The content field may optionally be left blank if you prefer to put your
content in a custom template.
Here are some examples of flatpages on Django-powered sites:
* http://www.chicagocrime.org/about/
* http://www.everyblock.com/about/
* http://www.lawrence.com/about/contact/
Installation

View File

@ -1677,7 +1677,7 @@ still only creating one database table per child model at the database level.
When an abstract base class is created, Django makes any ``Meta`` inner class
you declared on the base class available as an attribute. If a child class
does not declared its own ``Meta`` class, it will inherit the parent's
does not declare its own ``Meta`` class, it will inherit the parent's
``Meta``. If the child wants to extend the parent's ``Meta`` class, it can
subclass it. For example::

View File

@ -797,6 +797,37 @@ another test, or by the order of test execution.
.. _dumpdata documentation: ../django-admin/#dumpdata-appname-appname
.. _loaddata documentation: ../django-admin/#loaddata-fixture-fixture
URLconf configuration
~~~~~~~~~~~~~~~~~~~~~
**New in Django development version**
If your application provides views, you may want to include tests that
use the test client to exercise those views. However, an end user is free
to deploy the views in your application at any URL of their choosing.
This means that your tests can't rely upon the fact that your views will
be available at a particular URL.
In order to provide a reliable URL space for your test,
``django.test.TestCase`` provides the ability to customize the URLconf
configuration for the duration of the execution of a test suite.
If your ``TestCase`` instance defines an ``urls`` attribute, the
``TestCase`` will use the value of that attribute as the ``ROOT_URLCONF``
for the duration of that test.
For example::
from django.test import TestCase
class TestMyViews(TestCase):
urls = 'myapp.test_urls'
def testIndexPageView(self):
# Here you'd test your view using ``Client``.
This test case will use the contents of ``myapp.test_urls`` as the
URLconf for the duration of the test case.
Emptying the test outbox
~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,55 @@
import copy
from django.db import models
from django.db.models.query import Q
class RevisionableModel(models.Model):
base = models.ForeignKey('self', null=True)
title = models.CharField(blank=True, max_length=255)
def __unicode__(self):
return u"%s (%s, %s)" % (self.title, self.id, self.base.id)
def save(self):
super(RevisionableModel, self).save()
if not self.base:
self.base = self
super(RevisionableModel, self).save()
def new_revision(self):
new_revision = copy.copy(self)
new_revision.pk = None
return new_revision
__test__ = {"API_TESTS": """
### Regression tests for #7314 and #7372
>>> rm = RevisionableModel.objects.create(title='First Revision')
>>> rm.pk, rm.base.pk
(1, 1)
>>> rm2 = rm.new_revision()
>>> rm2.title = "Second Revision"
>>> rm2.save()
>>> print u"%s of %s" % (rm2.title, rm2.base.title)
Second Revision of First Revision
>>> rm2.pk, rm2.base.pk
(2, 1)
Queryset to match most recent revision:
>>> qs = RevisionableModel.objects.extra(where=["%(table)s.id IN (SELECT MAX(rev.id) FROM %(table)s AS rev GROUP BY rev.base_id)" % {'table': RevisionableModel._meta.db_table,}],)
>>> qs
[<RevisionableModel: Second Revision (2, 1)>]
Queryset to search for string in title:
>>> qs2 = RevisionableModel.objects.filter(title__contains="Revision")
>>> qs2
[<RevisionableModel: First Revision (1, 1)>, <RevisionableModel: Second Revision (2, 1)>]
Following queryset should return the most recent revision:
>>> qs & qs2
[<RevisionableModel: Second Revision (2, 1)>]
"""}

View File

@ -0,0 +1,83 @@
[
{
"pk": 6,
"model": "fixtures_regress.channel",
"fields": {
"name": "Business"
}
},
{
"pk": 1,
"model": "fixtures_regress.article",
"fields": {
"title": "Article Title 1",
"channels": [6]
}
},
{
"pk": 2,
"model": "fixtures_regress.article",
"fields": {
"title": "Article Title 2",
"channels": [6]
}
},
{
"pk": 3,
"model": "fixtures_regress.article",
"fields": {
"title": "Article Title 3",
"channels": [6]
}
},
{
"pk": 4,
"model": "fixtures_regress.article",
"fields": {
"title": "Article Title 4",
"channels": [6]
}
},
{
"pk": 5,
"model": "fixtures_regress.article",
"fields": {
"title": "Article Title 5",
"channels": [6]
}
},
{
"pk": 6,
"model": "fixtures_regress.article",
"fields": {
"title": "Article Title 6",
"channels": [6]
}
},
{
"pk": 7,
"model": "fixtures_regress.article",
"fields": {
"title": "Article Title 7",
"channels": [6]
}
},
{
"pk": 8,
"model": "fixtures_regress.article",
"fields": {
"title": "Article Title 8",
"channels": [6]
}
},
{
"pk": 9,
"model": "fixtures_regress.article",
"fields": {
"title": "Yet Another Article",
"channels": [6]
}
}
]

View File

@ -0,0 +1,4 @@
[
{"pk": 1, "model": "fixtures_regress.parent", "fields": {"name": "fred"}},
{"pk": 1, "model": "fixtures_regress.child", "fields": {"data": "apple"}}
]

View File

@ -20,7 +20,7 @@ class Plant(models.Model):
class Stuff(models.Model):
name = models.CharField(max_length=20, null=True)
owner = models.ForeignKey(User, null=True)
def __unicode__(self):
# Oracle doesn't distinguish between None and the empty string.
# This hack makes the test case pass using Oracle.
@ -38,13 +38,29 @@ class Absolute(models.Model):
super(Absolute, self).__init__(*args, **kwargs)
Absolute.load_count += 1
class Parent(models.Model):
name = models.CharField(max_length=10)
class Child(Parent):
data = models.CharField(max_length=10)
# Models to regresison check #7572
class Channel(models.Model):
name = models.CharField(max_length=255)
class Article(models.Model):
title = models.CharField(max_length=255)
channels = models.ManyToManyField(Channel)
class Meta:
ordering = ('id',)
__test__ = {'API_TESTS':"""
>>> from django.core import management
# Load a fixture that uses PK=1
>>> management.call_command('loaddata', 'sequence', verbosity=0)
# Create a new animal. Without a sequence reset, this new object
# will take a PK of 1 (on Postgres), and the save will fail.
# This is a regression test for ticket #3790.
@ -61,9 +77,9 @@ __test__ = {'API_TESTS':"""
[<Stuff: None is owned by None>]
###############################################
# Regression test for ticket #6436 --
# Regression test for ticket #6436 --
# os.path.join will throw away the initial parts of a path if it encounters
# an absolute path. This means that if a fixture is specified as an absolute path,
# an absolute path. This means that if a fixture is specified as an absolute path,
# we need to make sure we don't discover the absolute path in every fixture directory.
>>> load_absolute_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'absolute.json')
@ -94,4 +110,28 @@ No fixture data found for 'bad_fixture2'. (File format may be invalid.)
>>> sys.stderr = savestderr
###############################################
# Test for ticket #7565 -- PostgreSQL sequence resetting checks shouldn't
# ascend to parent models when inheritance is used (since they are treated
# individually).
>>> management.call_command('loaddata', 'model-inheritance.json', verbosity=0)
###############################################
# Test for ticket #7572 -- MySQL has a problem if the same connection is
# used to create tables, load data, and then query over that data.
# To compensate, we close the connection after running loaddata.
# This ensures that a new connection is opened when test queries are issued.
>>> management.call_command('loaddata', 'big-fixture.json', verbosity=0)
>>> articles = Article.objects.exclude(id=9)
>>> articles.values_list('id', flat=True)
[1, 2, 3, 4, 5, 6, 7, 8]
# Just for good measure, run the same query again. Under the influence of
# ticket #7572, this will give a different result to the previous call.
>>> articles.values_list('id', flat=True)
[1, 2, 3, 4, 5, 6, 7, 8]
"""}

View File

@ -237,7 +237,7 @@ ValidationError: [u'REQUIRED']
Traceback (most recent call last):
...
ValidationError: [u'INVALID']
>>> f.clean('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com')
>>> f.clean('http://www.broken.djangoproject.com')
Traceback (most recent call last):
...
ValidationError: [u'INVALID LINK']

View File

@ -887,7 +887,7 @@ u'http://www.google.com'
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid URL.']
>>> f.clean('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com') # bad domain
>>> f.clean('http://www.broken.djangoproject.com') # bad domain
Traceback (most recent call last):
...
ValidationError: [u'This URL appears to be a broken link.']
@ -937,18 +937,24 @@ ValidationError: [u'This field is required.']
>>> f.clean(True)
True
>>> f.clean(False)
False
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(1)
True
>>> f.clean(0)
False
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean('Django rocks')
True
>>> f.clean('True')
True
>>> f.clean('False')
False
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f = BooleanField(required=False)
>>> f.clean('')

View File

@ -28,6 +28,24 @@ class Child(models.Model):
parent = models.ForeignKey(Parent)
# Multiple paths to the same model (#7110, #7125)
class Category(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class Record(models.Model):
category = models.ForeignKey(Category)
class Relation(models.Model):
left = models.ForeignKey(Record, related_name='left_set')
right = models.ForeignKey(Record, related_name='right_set')
def __unicode__(self):
return u"%s - %s" % (self.left.category.name, self.right.category.name)
__test__ = {'API_TESTS':"""
>>> Third.objects.create(id='3', name='An example')
<Third: Third object>
@ -73,4 +91,26 @@ Traceback (most recent call last):
...
ValueError: Cannot assign "<First: First object>": "Child.parent" must be a "Parent" instance.
# Test of multiple ForeignKeys to the same model (bug #7125)
>>> c1 = Category.objects.create(name='First')
>>> c2 = Category.objects.create(name='Second')
>>> c3 = Category.objects.create(name='Third')
>>> r1 = Record.objects.create(category=c1)
>>> r2 = Record.objects.create(category=c1)
>>> r3 = Record.objects.create(category=c2)
>>> r4 = Record.objects.create(category=c2)
>>> r5 = Record.objects.create(category=c3)
>>> r = Relation.objects.create(left=r1, right=r2)
>>> r = Relation.objects.create(left=r3, right=r4)
>>> r = Relation.objects.create(left=r1, right=r3)
>>> r = Relation.objects.create(left=r5, right=r2)
>>> r = Relation.objects.create(left=r3, right=r2)
>>> Relation.objects.filter(left__category__name__in=['First'], right__category__name__in=['Second'])
[<Relation: First - Second>]
>>> Category.objects.filter(record__left_set__right__category__name='Second').order_by('name')
[<Category: First>, <Category: Second>]
"""}

View File

@ -15,4 +15,21 @@ Decimal("3.14")
Traceback (most recent call last):
...
ValidationError: [u'This value must be a decimal number.']
>>> f = DecimalField(max_digits=5, decimal_places=1)
>>> x = f.to_python(2)
>>> y = f.to_python('2.6')
>>> f.get_db_prep_save(x)
u'2.0'
>>> f.get_db_prep_save(y)
u'2.6'
>>> f.get_db_prep_save(None)
>>> f.get_db_prep_lookup('exact', x)
[u'2.0']
>>> f.get_db_prep_lookup('exact', y)
[u'2.6']
>>> f.get_db_prep_lookup('exact', None)
[None]
"""

View File

@ -131,4 +131,26 @@ __test__ = {'API_TESTS':"""
>>> Child.objects.dates('created', 'month')
[datetime.datetime(2008, 6, 1, 0, 0)]
# Regression test for #7276: calling delete() on a model with multi-table
# inheritance should delete the associated rows from any ancestor tables, as
# well as any descendent objects.
>>> ident = ItalianRestaurant.objects.all()[0].id
>>> Place.objects.get(pk=ident)
<Place: Guido's All New House of Pasta the place>
>>> xx = Restaurant.objects.create(name='a', address='xx', serves_hot_dogs=True, serves_pizza=False)
# This should delete both Restuarants, plus the related places, plus the ItalianRestaurant.
>>> Restaurant.objects.all().delete()
>>> Place.objects.get(pk=ident)
Traceback (most recent call last):
...
DoesNotExist: Place matching query does not exist.
>>> ItalianRestaurant.objects.get(pk=ident)
Traceback (most recent call last):
...
DoesNotExist: ItalianRestaurant matching query does not exist.
"""}

View File

@ -0,0 +1,47 @@
"""
Regression tests for the interaction between model inheritance and
select_related().
"""
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
class Meta:
ordering = ('name',)
def __unicode__(self):
return u"%s the place" % self.name
class Restaurant(Place):
serves_sushi = models.BooleanField()
serves_steak = models.BooleanField()
def __unicode__(self):
return u"%s the restaurant" % self.name
class Person(models.Model):
name = models.CharField(max_length=50)
favorite_restaurant = models.ForeignKey(Restaurant)
def __unicode__(self):
return self.name
__test__ = {'API_TESTS':"""
Regression test for #7246
>>> r1 = Restaurant.objects.create(name="Nobu", serves_sushi=True, serves_steak=False)
>>> r2 = Restaurant.objects.create(name="Craft", serves_sushi=False, serves_steak=True)
>>> p1 = Person.objects.create(name="John", favorite_restaurant=r1)
>>> p2 = Person.objects.create(name="Jane", favorite_restaurant=r2)
>>> Person.objects.order_by('name').select_related()
[<Person: Jane>, <Person: John>]
>>> jane = Person.objects.order_by('name').select_related('favorite_restaurant')[0]
>>> jane.favorite_restaurant.name
u'Craft'
"""}

View File

@ -3,13 +3,15 @@ Various complex queries that have been problematic in the past.
"""
import datetime
import pickle
from django.db import models
from django.db.models.query import Q
class Tag(models.Model):
name = models.CharField(max_length=10)
parent = models.ForeignKey('self', blank=True, null=True)
parent = models.ForeignKey('self', blank=True, null=True,
related_name='children')
def __unicode__(self):
return self.name
@ -24,6 +26,14 @@ class Note(models.Model):
def __unicode__(self):
return self.note
class Annotation(models.Model):
name = models.CharField(max_length=10)
tag = models.ForeignKey(Tag)
notes = models.ManyToManyField(Note)
def __unicode__(self):
return self.name
class ExtraInfo(models.Model):
info = models.CharField(max_length=100)
note = models.ForeignKey(Note)
@ -162,85 +172,67 @@ class Child(models.Model):
person = models.OneToOneField(Member, primary_key=True)
parent = models.ForeignKey(Member, related_name="children")
# Custom primary keys interfered with ordering in the past.
class CustomPk(models.Model):
name = models.CharField(max_length=10, primary_key=True)
extra = models.CharField(max_length=10)
class Meta:
ordering = ['name', 'extra']
class Related(models.Model):
custom = models.ForeignKey(CustomPk)
__test__ = {'API_TESTS':"""
>>> t1 = Tag(name='t1')
>>> t1.save()
>>> t2 = Tag(name='t2', parent=t1)
>>> t2.save()
>>> t3 = Tag(name='t3', parent=t1)
>>> t3.save()
>>> t4 = Tag(name='t4', parent=t3)
>>> t4.save()
>>> t5 = Tag(name='t5', parent=t3)
>>> t5.save()
>>> t1 = Tag.objects.create(name='t1')
>>> t2 = Tag.objects.create(name='t2', parent=t1)
>>> t3 = Tag.objects.create(name='t3', parent=t1)
>>> t4 = Tag.objects.create(name='t4', parent=t3)
>>> t5 = Tag.objects.create(name='t5', parent=t3)
>>> n1 = Note(note='n1', misc='foo')
>>> n1.save()
>>> n2 = Note(note='n2', misc='bar')
>>> n2.save()
>>> n3 = Note(note='n3', misc='foo')
>>> n3.save()
>>> n1 = Note.objects.create(note='n1', misc='foo')
>>> n2 = Note.objects.create(note='n2', misc='bar')
>>> n3 = Note.objects.create(note='n3', misc='foo')
Create these out of order so that sorting by 'id' will be different to sorting
by 'info'. Helps detect some problems later.
>>> e2 = ExtraInfo(info='e2', note=n2)
>>> e2.save()
>>> e1 = ExtraInfo(info='e1', note=n1)
>>> e1.save()
>>> e2 = ExtraInfo.objects.create(info='e2', note=n2)
>>> e1 = ExtraInfo.objects.create(info='e1', note=n1)
>>> a1 = Author(name='a1', num=1001, extra=e1)
>>> a1.save()
>>> a2 = Author(name='a2', num=2002, extra=e1)
>>> a2.save()
>>> a3 = Author(name='a3', num=3003, extra=e2)
>>> a3.save()
>>> a4 = Author(name='a4', num=4004, extra=e2)
>>> a4.save()
>>> a1 = Author.objects.create(name='a1', num=1001, extra=e1)
>>> a2 = Author.objects.create(name='a2', num=2002, extra=e1)
>>> a3 = Author.objects.create(name='a3', num=3003, extra=e2)
>>> a4 = Author.objects.create(name='a4', num=4004, extra=e2)
>>> time1 = datetime.datetime(2007, 12, 19, 22, 25, 0)
>>> time2 = datetime.datetime(2007, 12, 19, 21, 0, 0)
>>> time3 = datetime.datetime(2007, 12, 20, 22, 25, 0)
>>> time4 = datetime.datetime(2007, 12, 20, 21, 0, 0)
>>> i1 = Item(name='one', created=time1, modified=time1, creator=a1, note=n3)
>>> i1.save()
>>> i1 = Item.objects.create(name='one', created=time1, modified=time1, creator=a1, note=n3)
>>> i1.tags = [t1, t2]
>>> i2 = Item(name='two', created=time2, creator=a2, note=n2)
>>> i2.save()
>>> i2 = Item.objects.create(name='two', created=time2, creator=a2, note=n2)
>>> i2.tags = [t1, t3]
>>> i3 = Item(name='three', created=time3, creator=a2, note=n3)
>>> i3.save()
>>> i4 = Item(name='four', created=time4, creator=a4, note=n3)
>>> i4.save()
>>> i3 = Item.objects.create(name='three', created=time3, creator=a2, note=n3)
>>> i4 = Item.objects.create(name='four', created=time4, creator=a4, note=n3)
>>> i4.tags = [t4]
>>> r1 = Report(name='r1', creator=a1)
>>> r1.save()
>>> r2 = Report(name='r2', creator=a3)
>>> r2.save()
>>> r3 = Report(name='r3')
>>> r3.save()
>>> r1 = Report.objects.create(name='r1', creator=a1)
>>> r2 = Report.objects.create(name='r2', creator=a3)
>>> r3 = Report.objects.create(name='r3')
Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering
will be rank3, rank2, rank1.
>>> rank1 = Ranking(rank=2, author=a2)
>>> rank1.save()
>>> rank2 = Ranking(rank=1, author=a3)
>>> rank2.save()
>>> rank3 = Ranking(rank=3, author=a1)
>>> rank3.save()
>>> rank1 = Ranking.objects.create(rank=2, author=a2)
>>> rank2 = Ranking.objects.create(rank=1, author=a3)
>>> rank3 = Ranking.objects.create(rank=3, author=a1)
>>> c1 = Cover(title="first", item=i4)
>>> c1.save()
>>> c2 = Cover(title="second", item=i2)
>>> c2.save()
>>> c1 = Cover.objects.create(title="first", item=i4)
>>> c2 = Cover.objects.create(title="second", item=i2)
>>> n1 = Number(num=4)
>>> n1.save()
>>> n2 = Number(num=8)
>>> n2.save()
>>> n3 = Number(num=12)
>>> n3.save()
>>> num1 = Number.objects.create(num=4)
>>> num2 = Number.objects.create(num=8)
>>> num3 = Number.objects.create(num=12)
Bug #1050
>>> Item.objects.filter(tags__isnull=True)
@ -346,6 +338,10 @@ Bug #1878, #2939
4
>>> xx.delete()
Bug #7323
>>> Item.objects.values('creator', 'name').count()
4
Bug #2253
>>> q1 = Item.objects.order_by('name')
>>> q2 = Item.objects.filter(id=i1.id)
@ -387,6 +383,10 @@ Bug #4510
>>> Author.objects.filter(report__name='r1')
[<Author: a1>]
Bug #7378
>>> a1.report_set.all()
[<Report: r1>]
Bug #5324, #6704
>>> Item.objects.filter(tags__name='t4')
[<Item: four>]
@ -791,5 +791,19 @@ Empty querysets can be merged with others.
>>> Note.objects.all() & Note.objects.none()
[]
Bug #7204, #7506 -- make sure querysets with related fields can be pickled. If
this doesn't crash, it's a Good Thing.
>>> out = pickle.dumps(Item.objects.all())
Bug #7277
>>> ann1 = Annotation.objects.create(name='a1', tag=t1)
>>> ann1.notes.add(n1)
>>> n1.annotation_set.filter(Q(tag=t5) | Q(tag__children=t5) | Q(tag__children__children=t5))
[<Annotation: a1>]
Bug #7371
>>> Related.objects.order_by('custom')
[]
"""}

View File

@ -0,0 +1,60 @@
from django.db import models
class Building(models.Model):
name = models.CharField(max_length=10)
def __unicode__(self):
return u"Building: %s" % self.name
class Device(models.Model):
building = models.ForeignKey('Building')
name = models.CharField(max_length=10)
def __unicode__(self):
return u"device '%s' in building %s" % (self.name, self.building)
class Port(models.Model):
device = models.ForeignKey('Device')
number = models.CharField(max_length=10)
def __unicode__(self):
return u"%s/%s" % (self.device.name, self.number)
class Connection(models.Model):
start = models.ForeignKey(Port, related_name='connection_start',
unique=True)
end = models.ForeignKey(Port, related_name='connection_end', unique=True)
def __unicode__(self):
return u"%s to %s" % (self.start, self.end)
__test__ = {'API_TESTS': """
Regression test for bug #7110. When using select_related(), we must query the
Device and Building tables using two different aliases (each) in order to
differentiate the start and end Connection fields. The net result is that both
the "connections = ..." queries here should give the same results.
>>> b=Building.objects.create(name='101')
>>> dev1=Device.objects.create(name="router", building=b)
>>> dev2=Device.objects.create(name="switch", building=b)
>>> dev3=Device.objects.create(name="server", building=b)
>>> port1=Port.objects.create(number='4',device=dev1)
>>> port2=Port.objects.create(number='7',device=dev2)
>>> port3=Port.objects.create(number='1',device=dev3)
>>> c1=Connection.objects.create(start=port1, end=port2)
>>> c2=Connection.objects.create(start=port2, end=port3)
>>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).order_by('id')
>>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections]
[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]
>>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).select_related().order_by('id')
>>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections]
[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]
# This final query should only join seven tables (port, device and building
# twice each, plus connection once).
>>> connections.query.count_active_tables()
7
"""}

View File

@ -97,6 +97,12 @@ __test__ = {'API_TESTS': ur"""
>>> Article.objects.get(text__exact='The quick brown fox jumps over the lazy dog.')
<Article: Article Test>
# Regression tests for #2170: test case sensitiveness
>>> Article.objects.filter(text__exact='tHe qUick bRown fOx jUmps over tHe lazy dog.')
[]
>>> Article.objects.filter(text__iexact='tHe qUick bRown fOx jUmps over tHe lazy dog.')
[<Article: Article Test>]
>>> Article.objects.get(text__contains='quick brown fox')
<Article: Article Test>

View File

@ -318,3 +318,22 @@ class ExceptionTests(TestCase):
self.client.get("/test_client_regress/staff_only/")
except SuspiciousOperation:
self.fail("Staff should be able to visit this page")
# We need two different tests to check URLconf subsitution - one to check
# it was changed, and another one (without self.urls) to check it was reverted on
# teardown. This pair of tests relies upon the alphabetical ordering of test execution.
class UrlconfSubstitutionTests(TestCase):
urls = 'regressiontests.test_client_regress.urls'
def test_urlconf_was_changed(self):
"TestCase can enforce a custom URLConf on a per-test basis"
url = reverse('arg_view', args=['somename'])
self.assertEquals(url, '/arg_view/somename/')
# This test needs to run *after* UrlconfSubstitutionTests; the zz prefix in the
# name is to ensure alphabetical ordering.
class zzUrlconfSubstitutionTests(TestCase):
def test_urlconf_was_reverted(self):
"URLconf is reverted to original value after modification in a TestCase"
url = reverse('arg_view', args=['somename'])
self.assertEquals(url, '/test_client_regress/arg_view/somename/')