diff --git a/django/db/__init__.py b/django/db/__init__.py
index 8395468..c6f28da 100644
a
|
b
|
if DEFAULT_DB_ALIAS not in settings.DATABASES:
|
12 | 12 | raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS) |
13 | 13 | |
14 | 14 | connections = ConnectionHandler(settings.DATABASES) |
| 15 | connections._ignore_num_queries = False |
15 | 16 | |
16 | 17 | router = ConnectionRouter(settings.DATABASE_ROUTERS) |
17 | 18 | |
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 23ddedb..c9a6d7a 100644
a
|
b
|
class BaseDatabaseWrapper(local):
|
34 | 34 | self.transaction_state = [] |
35 | 35 | self.savepoint_state = 0 |
36 | 36 | self._dirty = None |
| 37 | |
| 38 | self.constraint_checking_disabled = False |
37 | 39 | |
38 | 40 | def __eq__(self, other): |
39 | 41 | return self.alias == other.alias |
… |
… |
class BaseDatabaseWrapper(local):
|
238 | 240 | """ |
239 | 241 | if self.savepoint_state: |
240 | 242 | self._savepoint_commit(sid) |
241 | | |
| 243 | |
242 | 244 | @contextmanager |
243 | 245 | def constraint_checks_disabled(self): |
244 | | disabled = self.disable_constraint_checking() |
| 246 | self.disable_constraint_checking() |
245 | 247 | try: |
246 | 248 | yield |
247 | 249 | finally: |
248 | | if disabled: |
| 250 | if self.constraint_checking_disabled: |
249 | 251 | self.enable_constraint_checking() |
250 | | |
| 252 | |
| 253 | |
251 | 254 | def disable_constraint_checking(self): |
252 | 255 | """ |
253 | 256 | Backends can implement as needed to temporarily disable foreign key constraint |
254 | 257 | checking. |
255 | 258 | """ |
256 | | pass |
| 259 | self.constraint_checking_disabled = True |
257 | 260 | |
258 | 261 | def enable_constraint_checking(self): |
259 | 262 | """ |
260 | 263 | Backends can implement as needed to re-enable foreign key constraint checking. |
261 | 264 | """ |
262 | | pass |
263 | | |
| 265 | self.constraint_checking_disabled = False |
| 266 | |
264 | 267 | def check_constraints(self, table_names=None): |
265 | 268 | """ |
266 | 269 | Backends can override this method if they can apply constraint checking (e.g. via "SET CONSTRAINTS |
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index f4523e4..0d719b7 100644
a
|
b
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
356 | 356 | to indicate constraint checks need to be re-enabled. |
357 | 357 | """ |
358 | 358 | self.cursor().execute('SET foreign_key_checks=0') |
359 | | return True |
| 359 | self.constraint_checking_disabled = True |
360 | 360 | |
361 | 361 | def enable_constraint_checking(self): |
362 | 362 | """ |
363 | 363 | Re-enable foreign key checks after they have been disabled. |
364 | 364 | """ |
365 | 365 | self.cursor().execute('SET foreign_key_checks=1') |
366 | | |
| 366 | self.constraint_checking_disabled = False |
| 367 | |
367 | 368 | def check_constraints(self, table_names=None): |
368 | 369 | """ |
369 | 370 | Checks each table name in table-names for rows with invalid foreign key references. This method is |
… |
… |
class DatabaseWrapper(BaseDatabaseWrapper):
|
389 | 390 | SELECT REFERRING.`%s`, REFERRING.`%s` FROM `%s` as REFERRING |
390 | 391 | LEFT JOIN `%s` as REFERRED |
391 | 392 | ON (REFERRING.`%s` = REFERRED.`%s`) |
392 | | WHERE REFERRING.`%s` IS NOT NULL AND REFERRED.`%s` IS NULL""" |
| 393 | WHERE REFERRING.`%s` IS NOT NULL |
| 394 | AND REFERRED.`%s` IS NULL""" |
393 | 395 | % (primary_key_column_name, column_name, table_name, referenced_table_name, |
394 | | column_name, referenced_column_name, column_name, referenced_column_name)) |
| 396 | column_name, referenced_column_name, column_name, referenced_column_name)) |
395 | 397 | for bad_row in cursor.fetchall(): |
396 | | raise utils.IntegrityError("The row in table '%s' with primary key '%s' has an invalid " |
397 | | "foreign key: %s.%s contains a value '%s' that does not have a corresponding value in %s.%s." |
398 | | % (table_name, bad_row[0], |
399 | | table_name, column_name, bad_row[1], |
400 | | referenced_table_name, referenced_column_name)) |
| 398 | raise utils.IntegrityError("The row in table '%s' with primary key '%s' has an invalid \ |
| 399 | foreign key: %s.%s contains a value '%s' that does not have a corresponding value in %s.%s." |
| 400 | % (table_name, bad_row[0], table_name, column_name, bad_row[1], |
| 401 | referenced_table_name, referenced_column_name)) |
diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
index 37aa072..691c736 100644
a
|
b
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
105 | 105 | self.introspection = DatabaseIntrospection(self) |
106 | 106 | self.validation = BaseDatabaseValidation(self) |
107 | 107 | self._pg_version = None |
| 108 | |
| 109 | def check_constraints(self, table_names=None): |
| 110 | """ |
| 111 | To check constraints, we set constraints to immediate. Then, when, we're done we must ensure they |
| 112 | are returned to deferred. |
| 113 | """ |
| 114 | self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE') |
| 115 | self.cursor().execute('SET CONSTRAINTS ALL DEFERRED') |
108 | 116 | |
109 | 117 | def check_constraints(self, table_names=None): |
110 | 118 | """ |
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
index 9652a4d..b1427c0 100644
a
|
b
|
class DatabaseIntrospection(BaseDatabaseIntrospection):
|
156 | 156 | name = info[0][2] # seqno, cid, name |
157 | 157 | indexes[name]['unique'] = True |
158 | 158 | return indexes |
| 159 | |
| 160 | def get_primary_key_column(self, cursor, table_name): |
| 161 | """ |
| 162 | Get the column name of the primary key for the given table. |
| 163 | """ |
| 164 | # Don't use PRAGMA because that causes issues with some transactions |
| 165 | cursor.execute("SELECT sql FROM sqlite_master WHERE tbl_name = %s AND type = %s", [table_name, "table"]) |
| 166 | results = cursor.fetchone()[0].strip() |
| 167 | results = results[results.index('(')+1:results.rindex(')')] |
| 168 | for field_desc in results.split(','): |
| 169 | field_desc = field_desc.strip() |
| 170 | m = re.search('"(.*)".*PRIMARY KEY$', field_desc) |
| 171 | if m: |
| 172 | return m.groups()[0] |
| 173 | return None |
159 | 174 | |
160 | 175 | def get_primary_key_column(self, cursor, table_name): |
161 | 176 | """ |
diff --git a/django/db/backends/util.py b/django/db/backends/util.py
index 0766f87..e037a5a 100644
a
|
b
|
class CursorWrapper(object):
|
29 | 29 | class CursorDebugWrapper(CursorWrapper): |
30 | 30 | |
31 | 31 | def execute(self, sql, params=()): |
| 32 | from django.db import connections |
32 | 33 | start = time() |
33 | 34 | try: |
34 | 35 | return self.cursor.execute(sql, params) |
… |
… |
class CursorDebugWrapper(CursorWrapper):
|
36 | 37 | stop = time() |
37 | 38 | duration = stop - start |
38 | 39 | sql = self.db.ops.last_executed_query(self.cursor, sql, params) |
39 | | self.db.queries.append({ |
40 | | 'sql': sql, |
41 | | 'time': "%.3f" % duration, |
42 | | }) |
| 40 | if not connections._ignore_num_queries: |
| 41 | self.db.queries.append({ |
| 42 | 'sql': sql, |
| 43 | 'time': "%.3f" % duration, |
| 44 | }) |
43 | 45 | logger.debug('(%.3f) %s; args=%s' % (duration, sql, params), |
44 | 46 | extra={'duration':duration, 'sql':sql, 'params':params} |
45 | 47 | ) |
… |
… |
def format_number(value, max_digits, decimal_places):
|
143 | 145 | context.prec = max_digits |
144 | 146 | return u'%s' % str(value.quantize(decimal.Decimal(".1") ** decimal_places, context=context)) |
145 | 147 | else: |
146 | | return u"%.*f" % (decimal_places, value) |
| 148 | return u"%.*f" % (decimal_places, value) |
| 149 | No newline at end of file |
diff --git a/django/test/__init__.py b/django/test/__init__.py
index 68aea9a..04d985d 100644
a
|
b
|
Django Unit Test and Doctest framework.
|
3 | 3 | """ |
4 | 4 | |
5 | 5 | from django.test.client import Client, RequestFactory |
6 | | from django.test.testcases import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature |
| 6 | from django.test.testcases import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature, ignore_num_queries |
7 | 7 | from django.test.utils import Approximate |
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 5a06e3f..e93ead6 100644
a
|
b
|
from django.utils.encoding import smart_str
|
23 | 23 | __all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase', |
24 | 24 | 'skipIfDBFeature', 'skipUnlessDBFeature') |
25 | 25 | |
| 26 | |
| 27 | def ignore_num_queries(fn): |
| 28 | @wraps(fn) |
| 29 | def num_queries_ignored(*args, **kwargs): |
| 30 | connections._ignore_num_queries = True |
| 31 | try: |
| 32 | return fn(*args, **kwargs) |
| 33 | finally: |
| 34 | connections._ignore_num_queries = False |
| 35 | return num_queries_ignored |
| 36 | |
26 | 37 | normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s) |
27 | 38 | normalize_decimals = lambda s: re.sub(r"Decimal\('(\d+(\.\d*)?)'\)", lambda m: "Decimal(\"%s\")" % m.groups()[0], s) |
28 | 39 | |
… |
… |
def to_list(value):
|
39 | 50 | |
40 | 51 | real_commit = transaction.commit |
41 | 52 | real_rollback = transaction.rollback |
| 53 | real_commit_unless_managed = transaction.commit_unless_managed |
| 54 | real_rollback_unless_managed = transaction.rollback_unless_managed |
42 | 55 | real_enter_transaction_management = transaction.enter_transaction_management |
43 | 56 | real_leave_transaction_management = transaction.leave_transaction_management |
44 | 57 | real_managed = transaction.managed |
… |
… |
real_managed = transaction.managed
|
46 | 59 | def nop(*args, **kwargs): |
47 | 60 | return |
48 | 61 | |
| 62 | @ignore_num_queries |
| 63 | def check_constraints(using=None): |
| 64 | """ |
| 65 | Emulate the constraint check behavior that normally occurs when a transaction is rolled back or committed. |
| 66 | """ |
| 67 | if using is None: |
| 68 | using = DEFAULT_DB_ALIAS |
| 69 | connection = connections[using] |
| 70 | # Don't check constraints if they have been manually disabled |
| 71 | if not connection.constraint_checking_disabled: |
| 72 | connection.check_constraints() |
| 73 | |
49 | 74 | def disable_transaction_methods(): |
50 | | transaction.commit = nop |
| 75 | transaction.commit = check_constraints |
51 | 76 | transaction.rollback = nop |
| 77 | transaction.commit_unless_managed = check_constraints |
| 78 | transaction.rollback_unless_managed = nop |
52 | 79 | transaction.enter_transaction_management = nop |
53 | 80 | transaction.leave_transaction_management = nop |
54 | 81 | transaction.managed = nop |
… |
… |
def disable_transaction_methods():
|
56 | 83 | def restore_transaction_methods(): |
57 | 84 | transaction.commit = real_commit |
58 | 85 | transaction.rollback = real_rollback |
| 86 | transaction.commit_unless_managed = real_commit_unless_managed |
| 87 | transaction.rollback_unless_managed = real_rollback_unless_managed |
59 | 88 | transaction.enter_transaction_management = real_enter_transaction_management |
60 | 89 | transaction.leave_transaction_management = real_leave_transaction_management |
61 | 90 | transaction.managed = real_managed |
… |
… |
class TestCase(TransactionTestCase):
|
578 | 607 | |
579 | 608 | from django.contrib.sites.models import Site |
580 | 609 | Site.objects.clear_cache() |
| 610 | |
| 611 | from django.contrib.contenttypes.models import ContentType |
| 612 | ContentType.objects.clear_cache() |
581 | 613 | |
582 | 614 | for db in databases: |
583 | 615 | if hasattr(self, 'fixtures'): |
diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py
index 27d3dfd..be5d6f4 100644
a
|
b
|
class FkConstraintsTests(TransactionTestCase):
|
347 | 347 | except IntegrityError: |
348 | 348 | return |
349 | 349 | self.skipTest("This backend does not support integrity checks.") |
350 | | |
| 350 | |
351 | 351 | def test_disable_constraint_checks_manually(self): |
352 | 352 | """ |
353 | | When constraint checks are disabled, should be able to write bad data without IntegrityErrors. |
| 353 | When constraint checks are disabled, should be able to write bad data without IntegrityErrors. Also, |
| 354 | should set disabled flag. |
354 | 355 | """ |
355 | 356 | with transaction.commit_manually(): |
356 | 357 | # Create an Article. |
… |
… |
class FkConstraintsTests(TransactionTestCase):
|
360 | 361 | a.reporter_id = 30 |
361 | 362 | try: |
362 | 363 | connection.disable_constraint_checking() |
| 364 | self.assertTrue(connection.constraint_checking_disabled) |
363 | 365 | a.save() |
364 | 366 | connection.enable_constraint_checking() |
365 | 367 | except IntegrityError: |
366 | 368 | self.fail("IntegrityError should not have occurred.") |
367 | 369 | finally: |
368 | 370 | transaction.rollback() |
369 | | |
| 371 | |
370 | 372 | def test_disable_constraint_checks_context_manager(self): |
371 | 373 | """ |
372 | 374 | When constraint checks are disabled (using context manager), should be able to write bad data without IntegrityErrors. |
… |
… |
class FkConstraintsTests(TransactionTestCase):
|
379 | 381 | a.reporter_id = 30 |
380 | 382 | try: |
381 | 383 | with connection.constraint_checks_disabled(): |
| 384 | self.assertTrue(connection.constraint_checking_disabled) |
382 | 385 | a.save() |
383 | 386 | except IntegrityError: |
384 | 387 | self.fail("IntegrityError should not have occurred.") |
385 | 388 | finally: |
386 | 389 | transaction.rollback() |
387 | | |
| 390 | |
388 | 391 | def test_check_constraints(self): |
389 | 392 | """ |
390 | 393 | Constraint checks should raise an IntegrityError when bad data is in the DB. |
diff --git a/tests/regressiontests/comment_tests/tests/templatetag_tests.py b/tests/regressiontests/comment_tests/tests/templatetag_tests.py
index 0ee34ac..835017c 100644
a
|
b
|
class CommentTemplateTagTests(CommentTestCase):
|
42 | 42 | def testRenderCommentFormFromObjectWithQueryCount(self): |
43 | 43 | def test(): |
44 | 44 | self.testRenderCommentFormFromObject() |
45 | | self.assertNumQueries(1, test) |
| 45 | # 1 to select object |
| 46 | # 1 to get the contenttype |
| 47 | self.assertNumQueries(2, test) |
46 | 48 | |
47 | 49 | def testGetCommentCount(self, tag=None): |
48 | 50 | self.createSomeComments() |
diff --git a/tests/regressiontests/fixtures_regress/fixtures/forward_ref.json b/tests/regressiontests/fixtures_regress/fixtures/forward_ref.json
new file mode 100644
index 0000000..237b076
-
|
+
|
|
| 1 | [ |
| 2 | { |
| 3 | "pk": 1, |
| 4 | "model": "fixtures_regress.book", |
| 5 | "fields": { |
| 6 | "name": "Cryptonomicon", |
| 7 | "author": 4 |
| 8 | } |
| 9 | }, |
| 10 | { |
| 11 | "pk": "4", |
| 12 | "model": "fixtures_regress.person", |
| 13 | "fields": { |
| 14 | "name": "Neal Stephenson" |
| 15 | } |
| 16 | } |
| 17 | ] |
| 18 | No newline at end of file |
diff --git a/tests/regressiontests/fixtures_regress/fixtures/forward_ref_bad_data.json b/tests/regressiontests/fixtures_regress/fixtures/forward_ref_bad_data.json
new file mode 100644
index 0000000..3a3fb64
-
|
+
|
|
| 1 | [ |
| 2 | { |
| 3 | "pk": 1, |
| 4 | "model": "fixtures_regress.book", |
| 5 | "fields": { |
| 6 | "name": "Cryptonomicon", |
| 7 | "author": 3 |
| 8 | } |
| 9 | }, |
| 10 | { |
| 11 | "pk": "4", |
| 12 | "model": "fixtures_regress.person", |
| 13 | "fields": { |
| 14 | "name": "Neal Stephenson" |
| 15 | } |
| 16 | } |
| 17 | ] |
| 18 | No newline at end of file |
diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py
index b67155d..07eff1d 100644
a
|
b
|
class TestFixtures(TestCase):
|
361 | 361 | """[{"pk": %d, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]""" |
362 | 362 | % widget.pk |
363 | 363 | ) |
| 364 | |
| 365 | def test_loaddata_works_when_fixture_has_forward_refs(self): |
| 366 | """ |
| 367 | Regression for #3615 - Forward references cause fixtures not to load in MySQL (InnoDB) |
| 368 | """ |
| 369 | management.call_command( |
| 370 | 'loaddata', |
| 371 | 'forward_ref.json', |
| 372 | verbosity=0, |
| 373 | commit=False |
| 374 | ) |
| 375 | self.assertEqual(Book.objects.all()[0].id, 1) |
| 376 | self.assertEqual(Person.objects.all()[0].id, 4) |
| 377 | |
| 378 | def test_loaddata_raises_error_when_fixture_has_invalid_foreign_key(self): |
| 379 | """ |
| 380 | Regression for #3615 - Ensure data with nonexistent child key references raises error |
| 381 | """ |
| 382 | stderr = StringIO() |
| 383 | management.call_command( |
| 384 | 'loaddata', |
| 385 | 'forward_ref_bad_data.json', |
| 386 | verbosity=0, |
| 387 | commit=False, |
| 388 | stderr=stderr, |
| 389 | ) |
| 390 | self.assertTrue( |
| 391 | stderr.getvalue().startswith('Problem installing fixture') |
| 392 | ) |
364 | 393 | |
365 | 394 | def test_loaddata_works_when_fixture_has_forward_refs(self): |
366 | 395 | """ |
diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py
index 02ee34f..3a368bf 100644
a
|
b
|
class RouterTestCase(TestCase):
|
973 | 973 | # Make the 'other' database appear to be a slave of the 'default' |
974 | 974 | self.old_routers = router.routers |
975 | 975 | router.routers = [TestRouter()] |
| 976 | |
| 977 | # Disable constraint checking, for now |
| 978 | for connection_name in connections: |
| 979 | connection = connections[connection_name] |
| 980 | connection.disable_constraint_checking() |
976 | 981 | |
977 | 982 | def tearDown(self): |
978 | 983 | # Restore the 'other' database as an independent database |
979 | 984 | router.routers = self.old_routers |
| 985 | |
| 986 | # Re-enable constraint checking |
| 987 | for connection_name in connections: |
| 988 | connection = connections[connection_name] |
| 989 | connection.enable_constraint_checking() |
980 | 990 | |
981 | 991 | def test_db_selection(self): |
982 | 992 | "Check that querysets obey the router for db suggestions" |
… |
… |
class SignalTests(TestCase):
|
1692 | 1702 | |
1693 | 1703 | def setUp(self): |
1694 | 1704 | self.old_routers = router.routers |
| 1705 | |
| 1706 | # Disable constraint checking, for now |
| 1707 | for connection_name in connections: |
| 1708 | connection = connections[connection_name] |
| 1709 | connection.disable_constraint_checking() |
1695 | 1710 | |
1696 | 1711 | def tearDown(self): |
1697 | 1712 | router.routers = self.old_routers |
| 1713 | |
| 1714 | # Re-enable constraint checking |
| 1715 | for connection_name in connections: |
| 1716 | connection = connections[connection_name] |
| 1717 | connection.enable_constraint_checking() |
1698 | 1718 | |
1699 | 1719 | def _write_to_other(self): |
1700 | 1720 | "Sends all writes to 'other'." |
diff --git a/tests/regressiontests/test_utils/models.py b/tests/regressiontests/test_utils/models.py
index 4da7a07..7a7a94e 100644
a
|
b
|
from django.db import models
|
3 | 3 | |
4 | 4 | class Person(models.Model): |
5 | 5 | name = models.CharField(max_length=100) |
| 6 | |
| 7 | class Pet(models.Model): |
| 8 | name = models.CharField(max_length=100) |
| 9 | owner = models.ForeignKey(Person) |
| 10 | |
| 11 | def __unicode__(self): |
| 12 | return self.name |
| 13 | |
| 14 | class Meta: |
| 15 | ordering = ('name',) |
diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py
index a35b0bc..6e26b24 100644
a
|
b
|
|
1 | 1 | from __future__ import with_statement |
2 | 2 | |
3 | | from django.test import TestCase, skipUnlessDBFeature |
| 3 | from django.test import TestCase, skipUnlessDBFeature, ignore_num_queries |
4 | 4 | from django.utils.unittest import skip |
| 5 | from django.db import IntegrityError |
5 | 6 | |
6 | | from models import Person |
| 7 | from models import Person, Pet |
7 | 8 | |
8 | 9 | |
9 | 10 | class SkippingTestCase(TestCase): |
… |
… |
class AssertNumQueriesTests(TestCase):
|
47 | 48 | self.client.get("/test_utils/get_person/%s/" % person.pk) |
48 | 49 | self.client.get("/test_utils/get_person/%s/" % person.pk) |
49 | 50 | self.assertNumQueries(2, test_func) |
| 51 | |
| 52 | def test_assert_num_queries_ignore_decorator(self): |
| 53 | person = Person.objects.create(name='test') |
| 54 | |
| 55 | @ignore_num_queries |
| 56 | def test_func(): |
| 57 | self.client.get("/test_utils/get_person/%s/" % person.pk) |
| 58 | self.client.get("/test_utils/get_person/%s/" % person.pk) |
| 59 | self.assertNumQueries(0, test_func) |
50 | 60 | |
51 | 61 | class AssertNumQueriesContextManagerTests(TestCase): |
52 | 62 | urls = 'regressiontests.test_utils.urls' |
… |
… |
class AssertNumQueriesContextManagerTests(TestCase):
|
86 | 96 | self.client.get("/test_utils/get_person/%s/" % person.pk) |
87 | 97 | |
88 | 98 | |
| 99 | class TransactionPatchingTests(TestCase): |
| 100 | def test_bad_data_should_raise_data_integrity_error(self): |
| 101 | """ |
| 102 | Ensure bad data cannot be saved to DB during tests. |
| 103 | """ |
| 104 | bill = Person.objects.create(name="Bill") |
| 105 | dog = Pet.objects.create(name="Spot", owner=bill) |
| 106 | dog.owner_id = 20 # Does not exist |
| 107 | with self.assertRaises(IntegrityError): |
| 108 | dog.save() |
| 109 | |
89 | 110 | class SaveRestoreWarningState(TestCase): |
90 | 111 | def test_save_restore_warnings_state(self): |
91 | 112 | """ |