mirror of
				https://github.com/django/django.git
				synced 2025-10-28 08:06:09 +00:00 
			
		
		
		
	git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@7809 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
		
							
								
								
									
										2
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -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 | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -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): | ||||
|   | ||||
							
								
								
									
										56
									
								
								django/contrib/auth/fixtures/authtestdata.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								django/contrib/auth/fixtures/authtestdata.json
									
									
									
									
									
										Normal 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" | ||||
|         } | ||||
|     } | ||||
| ] | ||||
| @@ -96,7 +96,6 @@ class Group(models.Model): | ||||
|     class Meta: | ||||
|         verbose_name = _('group') | ||||
|         verbose_name_plural = _('groups') | ||||
|         ordering = ('name',) | ||||
|          | ||||
|     def __unicode__(self): | ||||
|         return self.name | ||||
| @@ -150,7 +149,6 @@ class User(models.Model): | ||||
|     class Meta: | ||||
|         verbose_name = _('user') | ||||
|         verbose_name_plural = _('users') | ||||
|         ordering = ('username',) | ||||
|          | ||||
|     def __unicode__(self): | ||||
|         return self.username | ||||
|   | ||||
| @@ -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, | ||||
| } | ||||
|   | ||||
| @@ -54,3 +54,24 @@ 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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										13
									
								
								django/contrib/auth/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								django/contrib/auth/urls.py
									
									
									
									
									
										Normal 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') | ||||
| ) | ||||
|  | ||||
| @@ -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'.")) | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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);" % \ | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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)) + ' ' + \ | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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', | ||||
|   | ||||
| @@ -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', | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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)', | ||||
|   | ||||
| @@ -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'), | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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,7 +701,6 @@ 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) | ||||
|         return super(DecimalField, self).get_db_prep_save(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)] | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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] | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 = [] | ||||
|         if opts is None: | ||||
|             opts = self.model._meta | ||||
|         if start_alias: | ||||
|             table_alias = start_alias | ||||
|         else: | ||||
|             table_alias = self.tables[0] | ||||
|         root_pk = self.model._meta.pk.column | ||||
|         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 | ||||
|   | ||||
| @@ -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')] | ||||
|   | ||||
| @@ -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'): | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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): | ||||
|     """ | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| ------------------------------------------ | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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:: | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
							
								
								
									
										0
									
								
								tests/regressiontests/extra_regress/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/regressiontests/extra_regress/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										55
									
								
								tests/regressiontests/extra_regress/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								tests/regressiontests/extra_regress/models.py
									
									
									
									
									
										Normal 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)>] | ||||
|  | ||||
| """} | ||||
| @@ -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] | ||||
|         }     | ||||
|     } | ||||
| ] | ||||
| @@ -0,0 +1,4 @@ | ||||
| [ | ||||
|     {"pk": 1, "model": "fixtures_regress.parent", "fields": {"name": "fred"}}, | ||||
|     {"pk": 1, "model": "fixtures_regress.child", "fields": {"data": "apple"}} | ||||
| ] | ||||
| @@ -38,6 +38,22 @@ 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 | ||||
| @@ -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] | ||||
|  | ||||
| """} | ||||
|   | ||||
| @@ -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'] | ||||
|   | ||||
| @@ -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('') | ||||
|   | ||||
| @@ -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>] | ||||
|  | ||||
| """} | ||||
|   | ||||
| @@ -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] | ||||
|  | ||||
| """ | ||||
|   | ||||
| @@ -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. | ||||
|  | ||||
| """} | ||||
|   | ||||
| @@ -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' | ||||
|  | ||||
| """} | ||||
|  | ||||
| @@ -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') | ||||
| [] | ||||
|  | ||||
| """} | ||||
|  | ||||
|   | ||||
							
								
								
									
										60
									
								
								tests/regressiontests/select_related_regress/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								tests/regressiontests/select_related_regress/models.py
									
									
									
									
									
										Normal 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 | ||||
|  | ||||
| """} | ||||
| @@ -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> | ||||
|  | ||||
|   | ||||
| @@ -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/') | ||||
|   | ||||
		Reference in New Issue
	
	Block a user