Ticket #11665: better_constraint_checks_during_testing.v2.diff

File better_constraint_checks_during_testing.v2.diff, 38.6 KB (added by Jim Dalton, 13 years ago)
  • django/core/management/commands/loaddata.py

    diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py
    index 34f3543..0aa07f4 100644
    a b  
     1# This is necessary in Python 2.5 to enable the with statement, in 2.6
     2# and up it is no longer necessary.
     3from __future__ import with_statement
     4
    15import sys
    26import os
    37import gzip
    class Command(BaseCommand):  
    166170                                    (format, fixture_name, humanize(fixture_dir)))
    167171                            try:
    168172                                objects = serializers.deserialize(format, fixture, using=using)
    169                                 for obj in objects:
    170                                     objects_in_fixture += 1
    171                                     if router.allow_syncdb(using, obj.object.__class__):
    172                                         loaded_objects_in_fixture += 1
    173                                         models.add(obj.object.__class__)
    174                                         obj.save(using=using)
     173                               
     174                                with connection.constraint_checks_disabled():
     175                                    for obj in objects:
     176                                        objects_in_fixture += 1
     177                                        if router.allow_syncdb(using, obj.object.__class__):
     178                                            loaded_objects_in_fixture += 1
     179                                            models.add(obj.object.__class__)
     180                                            obj.save(using=using)
     181                                       
     182                                # Since we disabled constraint checks, we must manually check for
     183                                # any invalid keys that might have been added
     184                                table_names = [model._meta.db_table for model in models]
     185                                connection.check_constraints(table_names=table_names)
     186                                   
    175187                                loaded_object_count += loaded_objects_in_fixture
    176188                                fixture_object_count += objects_in_fixture
    177189                                label_found = True
  • django/db/__init__.py

    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:  
    1212    raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS)
    1313
    1414connections = ConnectionHandler(settings.DATABASES)
     15connections._ignore_num_queries = False
    1516
    1617router = ConnectionRouter(settings.DATABASE_ROUTERS)
    1718
  • django/db/backends/__init__.py

    diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
    index 1c3bc7e..d0e997f 100644
    a b try:  
    33except ImportError:
    44    import dummy_thread as thread
    55from threading import local
     6from contextlib import contextmanager
    67
    78from django.conf import settings
    89from django.db import DEFAULT_DB_ALIAS
    class BaseDatabaseWrapper(local):  
    3334        self.transaction_state = []
    3435        self.savepoint_state = 0
    3536        self._dirty = None
     37       
     38        self.constraint_checking_disabled = False
    3639
    3740    def __eq__(self, other):
    3841        return self.alias == other.alias
    class BaseDatabaseWrapper(local):  
    237240        """
    238241        if self.savepoint_state:
    239242            self._savepoint_commit(sid)
     243   
     244    @contextmanager
     245    def constraint_checks_disabled(self):
     246        self.disable_constraint_checking()
     247        try:
     248            yield
     249        finally:
     250            if self.constraint_checking_disabled:
     251                self.enable_constraint_checking()
     252       
     253   
     254    def disable_constraint_checking(self):
     255        """
     256        Backends can implement as needed to temporarily disable foreign key constraint
     257        checking.
     258        """
     259        self.constraint_checking_disabled = True
     260
     261    def enable_constraint_checking(self):
     262        """
     263        Backends can implement as needed to re-enable foreign key constraint checking.
     264        """
     265        self.constraint_checking_disabled = False
     266   
     267    def check_constraints(self, table_names=None):
     268        """
     269        Backends can override this method if they can apply constraint checking (e.g. via "SET CONSTRAINTS
     270        ALL IMMEDIATE"). Should raise an IntegrityError if any invalid foreign key references are encountered.
     271        """
     272        pass
    240273
    241274    def close(self):
    242275        if self.connection is not None:
    class BaseDatabaseIntrospection(object):  
    869902
    870903        return sequence_list
    871904
     905    def get_key_columns(self, cursor, table_name):
     906        """
     907        Backends can override this to return a list of (column_name, referenced_table_name,
     908        referenced_column_name) for all key columns in given table.
     909        """
     910        raise NotImplementedError
     911   
     912    def get_primary_key_column(self, cursor, table_name):
     913        """
     914        Backends can override this to return the column name of the primary key for the given table.
     915        """
     916        raise NotImplementedError
     917
    872918class BaseDatabaseClient(object):
    873919    """
    874920    This class encapsulates all backend-specific methods for opening a
  • django/db/backends/dummy/base.py

    diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py
    index 7de48c8..746f26b 100644
    a b class DatabaseIntrospection(BaseDatabaseIntrospection):  
    3434    get_table_description = complain
    3535    get_relations = complain
    3636    get_indexes = complain
     37    get_key_columns = complain
    3738
    3839class DatabaseWrapper(BaseDatabaseWrapper):
    3940    operators = {}
  • django/db/backends/mysql/base.py

    diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
    index 6d02aa7..0d719b7 100644
    a b class DatabaseWrapper(BaseDatabaseWrapper):  
    349349                raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info())
    350350            self.server_version = tuple([int(x) for x in m.groups()])
    351351        return self.server_version
     352
     353    def disable_constraint_checking(self):
     354        """
     355        Disables foreign key checks, primarily for use in adding rows with forward references. Always returns True,
     356        to indicate constraint checks need to be re-enabled.
     357        """
     358        self.cursor().execute('SET foreign_key_checks=0')
     359        self.constraint_checking_disabled = True
     360
     361    def enable_constraint_checking(self):
     362        """
     363        Re-enable foreign key checks after they have been disabled.
     364        """
     365        self.cursor().execute('SET foreign_key_checks=1')
     366        self.constraint_checking_disabled = False
     367   
     368    def check_constraints(self, table_names=None):
     369        """
     370        Checks each table name in table-names for rows with invalid foreign key references. This method is
     371        intended to be used in conjunction with `disable_constraint_checking()` and `enable_constraint_checking()`, to
     372        determine if rows with invalid references were entered while constraint checks were off.
     373
     374        Raises an IntegrityError on the first invalid foreign key reference encountered (if any) and provides
     375        detailed information about the invalid reference in the error message.
     376
     377        Backends can override this method if they can more directly apply constraint checking (e.g. via "SET CONSTRAINTS
     378        ALL IMMEDIATE")
     379        """
     380        cursor = self.cursor()
     381        if table_names is None:
     382            table_names = self.introspection.get_table_list(cursor)
     383        for table_name in table_names:
     384            primary_key_column_name = self.introspection.get_primary_key_column(cursor, table_name)
     385            if not primary_key_column_name:
     386                continue
     387            key_columns = self.introspection.get_key_columns(cursor, table_name)
     388            for column_name, referenced_table_name, referenced_column_name in key_columns:
     389                cursor.execute("""
     390                    SELECT REFERRING.`%s`, REFERRING.`%s` FROM `%s` as REFERRING
     391                    LEFT JOIN `%s` as REFERRED
     392                    ON (REFERRING.`%s` = REFERRED.`%s`)
     393                    WHERE REFERRING.`%s` IS NOT NULL
     394                        AND REFERRED.`%s` IS NULL"""
     395                    % (primary_key_column_name, column_name, table_name, referenced_table_name,
     396                       column_name, referenced_column_name, column_name, referenced_column_name))
     397                for bad_row in cursor.fetchall():
     398                    raise utils.IntegrityError("The row in table '%s' with primary key '%s' has an invalid \
     399foreign 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))
  • django/db/backends/mysql/introspection.py

    diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
    index 9e1518b..4612221 100644
    a b class DatabaseIntrospection(BaseDatabaseIntrospection):  
    5151        representing all relationships to the given table. Indexes are 0-based.
    5252        """
    5353        my_field_dict = self._name_to_index(cursor, table_name)
    54         constraints = []
     54        constraints = self.get_key_columns(cursor, table_name)
    5555        relations = {}
     56        for my_fieldname, other_table, other_field in constraints:
     57            other_field_index = self._name_to_index(cursor, other_table)[other_field]
     58            my_field_index = my_field_dict[my_fieldname]
     59            relations[my_field_index] = (other_field_index, other_table)
     60        return relations
     61
     62    def get_key_columns(self, cursor, table_name):
     63        """
     64        Returns a list of (column_name, referenced_table_name, referenced_column_name) for all
     65        key columns in given table.
     66        """
     67        key_columns = []
    5668        try:
    57             # This should work for MySQL 5.0.
    5869            cursor.execute("""
    5970                SELECT column_name, referenced_table_name, referenced_column_name
    6071                FROM information_schema.key_column_usage
    class DatabaseIntrospection(BaseDatabaseIntrospection):  
    6273                    AND table_schema = DATABASE()
    6374                    AND referenced_table_name IS NOT NULL
    6475                    AND referenced_column_name IS NOT NULL""", [table_name])
    65             constraints.extend(cursor.fetchall())
     76            key_columns.extend(cursor.fetchall())
    6677        except (ProgrammingError, OperationalError):
    6778            # Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
    6879            # Go through all constraints and save the equal matches.
    class DatabaseIntrospection(BaseDatabaseIntrospection):  
    7485                    if match == None:
    7586                        break
    7687                    pos = match.end()
    77                     constraints.append(match.groups())
    78 
    79         for my_fieldname, other_table, other_field in constraints:
    80             other_field_index = self._name_to_index(cursor, other_table)[other_field]
    81             my_field_index = my_field_dict[my_fieldname]
    82             relations[my_field_index] = (other_field_index, other_table)
    83 
    84         return relations
     88                    key_columns.append(match.groups())
     89        return key_columns
     90   
     91    def get_primary_key_column(self, cursor, table_name):
     92        """
     93        Returns the name of the primary key column for the given table
     94        """
     95        for column in self.get_indexes(cursor, table_name).iteritems():
     96            if column[1]['primary_key']:
     97                return column[0]
     98        return None
    8599
    86100    def get_indexes(self, cursor, table_name):
    87101        """
  • django/db/backends/oracle/base.py

    diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
    index 930b1bb..3cadb66 100644
    a b class DatabaseWrapper(BaseDatabaseWrapper):  
    428428        self.introspection = DatabaseIntrospection(self)
    429429        self.validation = BaseDatabaseValidation(self)
    430430
     431    def check_constraints(self, table_names=None):
     432        """
     433        To check constraints, we set constraints to immediate. Then, when, we're done we must ensure they
     434        are returned to deferred.
     435        """
     436        self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE')
     437        self.cursor().execute('SET CONSTRAINTS ALL DEFERRED')
     438
    431439    def _valid_connection(self):
    432440        return self.connection is not None
    433441
  • django/db/backends/postgresql_psycopg2/base.py

    diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
    index 6ed59a6..4136d4f 100644
    a b class DatabaseWrapper(BaseDatabaseWrapper):  
    105105        self.introspection = DatabaseIntrospection(self)
    106106        self.validation = BaseDatabaseValidation(self)
    107107        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')
    108116
    109117    def _get_pg_version(self):
    110118        if self._pg_version is None:
  • django/db/backends/sqlite3/base.py

    diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
    index 79c5ede..bc16d9a 100644
    a b class DatabaseWrapper(BaseDatabaseWrapper):  
    206206            connection_created.send(sender=self.__class__, connection=self)
    207207        return self.connection.cursor(factory=SQLiteCursorWrapper)
    208208
     209    def check_constraints(self, table_names=None):
     210        """
     211        Checks each table name in table-names for rows with invalid foreign key references. This method is
     212        intended to be used in conjunction with `disable_constraint_checking()` and `enable_constraint_checking()`, to
     213        determine if rows with invalid references were entered while constraint checks were off.
     214
     215        Raises an IntegrityError on the first invalid foreign key reference encountered (if any) and provides
     216        detailed information about the invalid reference in the error message.
     217
     218        Backends can override this method if they can more directly apply constraint checking (e.g. via "SET CONSTRAINTS
     219        ALL IMMEDIATE")
     220        """
     221        cursor = self.cursor()
     222        if table_names is None:
     223            table_names = self.introspection.get_table_list(cursor)
     224        for table_name in table_names:
     225            primary_key_column_name = self.introspection.get_primary_key_column(cursor, table_name)
     226            if not primary_key_column_name:
     227                continue
     228            key_columns = self.introspection.get_key_columns(cursor, table_name)
     229            for column_name, referenced_table_name, referenced_column_name in key_columns:
     230                cursor.execute("""
     231                    SELECT REFERRING.`%s`, REFERRING.`%s` FROM `%s` as REFERRING
     232                    LEFT JOIN `%s` as REFERRED
     233                    ON (REFERRING.`%s` = REFERRED.`%s`)
     234                    WHERE REFERRING.`%s` IS NOT NULL
     235                        AND REFERRED.`%s` IS NULL"""
     236                    % (primary_key_column_name, column_name, table_name, referenced_table_name,
     237                       column_name, referenced_column_name, column_name, referenced_column_name))
     238                for bad_row in cursor.fetchall():
     239                    raise utils.IntegrityError("The row in table '%s' with primary key '%s' has an invalid \
     240foreign key: %s.%s contains a value '%s' that does not have a corresponding value in %s.%s."
     241                                         % (table_name, bad_row[0], table_name, column_name, bad_row[1],
     242                                            referenced_table_name, referenced_column_name))
     243
    209244    def close(self):
    210245        # If database is in memory, closing the connection destroys the
    211246        # database. To prevent accidental data loss, ignore close requests on
  • django/db/backends/sqlite3/introspection.py

    diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py
    index 5ee7b64..27de913 100644
    a b class DatabaseIntrospection(BaseDatabaseIntrospection):  
    103103
    104104        return relations
    105105
     106    def get_key_columns(self, cursor, table_name):
     107        """
     108        Returns a list of (column_name, referenced_table_name, referenced_column_name) for all
     109        key columns in given table.
     110        """
     111        key_columns = []
     112
     113        # Schema for this table
     114        cursor.execute("SELECT sql FROM sqlite_master WHERE tbl_name = %s AND type = %s", [table_name, "table"])
     115        results = cursor.fetchone()[0].strip()
     116        results = results[results.index('(')+1:results.rindex(')')]
     117
     118        # Walk through and look for references to other tables. SQLite doesn't
     119        # really have enforced references, but since it echoes out the SQL used
     120        # to create the table we can look for REFERENCES statements used there.
     121        for field_index, field_desc in enumerate(results.split(',')):
     122            field_desc = field_desc.strip()
     123            if field_desc.startswith("UNIQUE"):
     124                continue
     125
     126            m = re.search('"(.*)".*references (.*) \(["|](.*)["|]\)', field_desc, re.I)
     127            if not m:
     128                continue
     129
     130            # This will append (column_name, referenced_table_name, referenced_column_name) to key_columns
     131            key_columns.append(tuple([s.strip('"') for s in m.groups()]))
     132
     133        return key_columns
     134       
    106135    def get_indexes(self, cursor, table_name):
    107136        """
    108137        Returns a dictionary of fieldname -> infodict for the given table,
    class DatabaseIntrospection(BaseDatabaseIntrospection):  
    127156            name = info[0][2] # seqno, cid, name
    128157            indexes[name]['unique'] = True
    129158        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
    130174
    131175    def _table_info(self, cursor, name):
    132176        cursor.execute('PRAGMA table_info(%s)' % self.connection.ops.quote_name(name))
  • django/db/backends/util.py

    diff --git a/django/db/backends/util.py b/django/db/backends/util.py
    index 0766f87..e037a5a 100644
    a b class CursorWrapper(object):  
    2929class CursorDebugWrapper(CursorWrapper):
    3030
    3131    def execute(self, sql, params=()):
     32        from django.db import connections
    3233        start = time()
    3334        try:
    3435            return self.cursor.execute(sql, params)
    class CursorDebugWrapper(CursorWrapper):  
    3637            stop = time()
    3738            duration = stop - start
    3839            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                })
    4345            logger.debug('(%.3f) %s; args=%s' % (duration, sql, params),
    4446                extra={'duration':duration, 'sql':sql, 'params':params}
    4547            )
    def format_number(value, max_digits, decimal_places):  
    143145        context.prec = max_digits
    144146        return u'%s' % str(value.quantize(decimal.Decimal(".1") ** decimal_places, context=context))
    145147    else:
    146         return u"%.*f" % (decimal_places, value)
     148        return u"%.*f" % (decimal_places, value)
     149 No newline at end of file
  • django/test/__init__.py

    diff --git a/django/test/__init__.py b/django/test/__init__.py
    index 68aea9a..04d985d 100644
    a b Django Unit Test and Doctest framework.  
    33"""
    44
    55from django.test.client import Client, RequestFactory
    6 from django.test.testcases import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
     6from django.test.testcases import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature, ignore_num_queries
    77from django.test.utils import Approximate
  • django/test/testcases.py

    diff --git a/django/test/testcases.py b/django/test/testcases.py
    index e767712..98c6887 100644
    a b from django.utils.encoding import smart_str  
    2323__all__ = ('DocTestRunner', 'OutputChecker', 'TestCase', 'TransactionTestCase',
    2424           'skipIfDBFeature', 'skipUnlessDBFeature')
    2525
     26
     27def 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
    2637normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
    2738normalize_decimals = lambda s: re.sub(r"Decimal\('(\d+(\.\d*)?)'\)", lambda m: "Decimal(\"%s\")" % m.groups()[0], s)
    2839
    def to_list(value):  
    3950
    4051real_commit = transaction.commit
    4152real_rollback = transaction.rollback
     53real_commit_unless_managed = transaction.commit_unless_managed
     54real_rollback_unless_managed = transaction.rollback_unless_managed
    4255real_enter_transaction_management = transaction.enter_transaction_management
    4356real_leave_transaction_management = transaction.leave_transaction_management
    4457real_managed = transaction.managed
    real_managed = transaction.managed  
    4659def nop(*args, **kwargs):
    4760    return
    4861
     62@ignore_num_queries
     63def 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
    4974def disable_transaction_methods():
    50     transaction.commit = nop
     75    transaction.commit = check_constraints
    5176    transaction.rollback = nop
     77    transaction.commit_unless_managed = check_constraints
     78    transaction.rollback_unless_managed = nop
    5279    transaction.enter_transaction_management = nop
    5380    transaction.leave_transaction_management = nop
    5481    transaction.managed = nop
    def disable_transaction_methods():  
    5683def restore_transaction_methods():
    5784    transaction.commit = real_commit
    5885    transaction.rollback = real_rollback
     86    transaction.commit_unless_managed = real_commit_unless_managed
     87    transaction.rollback_unless_managed = real_rollback_unless_managed
    5988    transaction.enter_transaction_management = real_enter_transaction_management
    6089    transaction.leave_transaction_management = real_leave_transaction_management
    6190    transaction.managed = real_managed
    class TestCase(TransactionTestCase):  
    580609
    581610        from django.contrib.sites.models import Site
    582611        Site.objects.clear_cache()
     612       
     613        from django.contrib.contenttypes.models import ContentType
     614        ContentType.objects.clear_cache()
    583615
    584616        for db in databases:
    585617            if hasattr(self, 'fixtures'):
  • docs/ref/databases.txt

    diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt
    index 2f55b9c..5a2042a 100644
    a b currently the only engine that supports full-text indexing and searching.  
    142142The InnoDB_ engine is fully transactional and supports foreign key references
    143143and is probably the best choice at this point in time.
    144144
     145.. versionchanged:: 1.4
     146
     147In previous versions of Django, fixtures with forward references (i.e.
     148relations to rows that have not yet been inserted into the database) would fail
     149to load when using the InnoDB storage engine. This was due to the fact that InnoDB
     150deviates from the SQL standard by checking foreign key constraints immediately
     151instead of deferring the check until the transaction is committed. This
     152problem has been resolved in Django 1.4. Fixture data is now loaded with foreign key
     153checks turned off; foreign key checks are then re-enabled when the data has
     154finished loading, at which point the entire table is checked for invalid foreign
     155key references and an `IntegrityError` is raised if any are found.
     156
    145157.. _storage engines: http://dev.mysql.com/doc/refman/5.5/en/storage-engines.html
    146158.. _MyISAM: http://dev.mysql.com/doc/refman/5.5/en/myisam-storage-engine.html
    147159.. _InnoDB: http://dev.mysql.com/doc/refman/5.5/en/innodb.html
  • tests/modeltests/serializers/tests.py

    diff --git a/tests/modeltests/serializers/tests.py b/tests/modeltests/serializers/tests.py
    index 4a7e0a2..def0254 100644
    a b  
     1# This is necessary in Python 2.5 to enable the with statement, in 2.6
     2# and up it is no longer necessary.
     3from __future__ import with_statement
     4
    15# -*- coding: utf-8 -*-
    26from datetime import datetime
    37from StringIO import StringIO
    from xml.dom import minidom  
    59
    610from django.conf import settings
    711from django.core import serializers
    8 from django.db import transaction
     12from django.db import transaction, connection
    913from django.test import TestCase, TransactionTestCase, Approximate
    1014from django.utils import simplejson, unittest
    1115
    class SerializersTransactionTestBase(object):  
    252256        transaction.enter_transaction_management()
    253257        transaction.managed(True)
    254258        objs = serializers.deserialize(self.serializer_name, self.fwd_ref_str)
    255         for obj in objs:
    256             obj.save()
     259        with connection.constraint_checks_disabled():
     260            for obj in objs:
     261                obj.save()
    257262        transaction.commit()
    258263        transaction.leave_transaction_management()
    259264
  • tests/regressiontests/backends/tests.py

    diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py
    index 29db6a7..be5d6f4 100644
    a b  
    11# -*- coding: utf-8 -*-
    22# Unit and doctests for specific database backends.
     3from __future__ import with_statement
    34import datetime
    45
    56from django.conf import settings
    67from django.core.management.color import no_style
    7 from django.db import backend, connection, connections, DEFAULT_DB_ALIAS, IntegrityError
     8from django.db import backend, connection, connections, DEFAULT_DB_ALIAS, IntegrityError, transaction
    89from django.db.backends.signals import connection_created
    910from django.db.backends.postgresql_psycopg2 import version as pg_version
    1011from django.test import TestCase, skipUnlessDBFeature, TransactionTestCase
    class FkConstraintsTests(TransactionTestCase):  
    328329        try:
    329330            a.save()
    330331        except IntegrityError:
    331             pass
     332            return
     333        self.skipTest("This backend does not support integrity checks.")
    332334
    333335    def test_integrity_checks_on_update(self):
    334336        """
    class FkConstraintsTests(TransactionTestCase):  
    343345        try:
    344346            a.save()
    345347        except IntegrityError:
    346             pass
     348            return
     349        self.skipTest("This backend does not support integrity checks.")
     350   
     351    def test_disable_constraint_checks_manually(self):
     352        """
     353        When constraint checks are disabled, should be able to write bad data without IntegrityErrors. Also,
     354        should set disabled flag.
     355        """
     356        with transaction.commit_manually():
     357            # Create an Article.
     358            models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
     359            # Retrive it from the DB
     360            a = models.Article.objects.get(headline="Test article")
     361            a.reporter_id = 30
     362            try:
     363                connection.disable_constraint_checking()
     364                self.assertTrue(connection.constraint_checking_disabled)
     365                a.save()
     366                connection.enable_constraint_checking()
     367            except IntegrityError:
     368                self.fail("IntegrityError should not have occurred.")
     369            finally:
     370                transaction.rollback()
     371   
     372    def test_disable_constraint_checks_context_manager(self):
     373        """
     374        When constraint checks are disabled (using context manager), should be able to write bad data without IntegrityErrors.
     375        """
     376        with transaction.commit_manually():
     377            # Create an Article.
     378            models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
     379            # Retrive it from the DB
     380            a = models.Article.objects.get(headline="Test article")
     381            a.reporter_id = 30
     382            try:
     383                with connection.constraint_checks_disabled():
     384                    self.assertTrue(connection.constraint_checking_disabled)
     385                    a.save()
     386            except IntegrityError:
     387                self.fail("IntegrityError should not have occurred.")
     388            finally:
     389                transaction.rollback()
     390   
     391    def test_check_constraints(self):
     392        """
     393        Constraint checks should raise an IntegrityError when bad data is in the DB.
     394        """
     395        with transaction.commit_manually():
     396            # Create an Article.
     397            models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
     398            # Retrive it from the DB
     399            a = models.Article.objects.get(headline="Test article")
     400            a.reporter_id = 30
     401            try:
     402                with connection.constraint_checks_disabled():
     403                    a.save()
     404                    with self.assertRaises(IntegrityError):
     405                        connection.check_constraints()
     406            finally:
     407                transaction.rollback()
  • tests/regressiontests/comment_tests/tests/templatetag_tests.py

    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):  
    4242    def testRenderCommentFormFromObjectWithQueryCount(self):
    4343        def test():
    4444            self.testRenderCommentFormFromObject()
    45         self.assertNumQueries(1, test)
     45        # 1 to select object
     46        # 1 to get the contenttype
     47        self.assertNumQueries(2, test)
    4648
    4749    def testGetCommentCount(self, tag=None):
    4850        self.createSomeComments()
  • new file tests/regressiontests/fixtures_regress/fixtures/forward_ref.json

    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
  • new file tests/regressiontests/fixtures_regress/fixtures/forward_ref_bad_data.json

    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
  • tests/regressiontests/fixtures_regress/tests.py

    diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py
    index a565ec9..344bc04 100644
    a b class TestFixtures(TestCase):  
    361361            """[{"pk": %d, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]"""
    362362            % widget.pk
    363363            )
     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        )
    364393
    365394
    366395class NaturalKeyFixtureTests(TestCase):
  • tests/regressiontests/introspection/tests.py

    diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py
    index 4f5fb09..a7c5bf2 100644
    a b class IntrospectionTests(TestCase):  
    9595            # That's {field_index: (field_index_other_table, other_table)}
    9696            self.assertEqual(relations, {3: (0, Reporter._meta.db_table)})
    9797
     98    def test_get_key_columns(self):
     99        cursor = connection.cursor()
     100        key_columns = connection.introspection.get_key_columns(cursor, Article._meta.db_table)
     101        self.assertEqual(key_columns, [(u'reporter_id', Reporter._meta.db_table, u'id')])
     102   
     103    def test_get_primary_key_column(self):
     104        cursor = connection.cursor()
     105        primary_key_column = connection.introspection.get_primary_key_column(cursor, Article._meta.db_table)
     106        self.assertEqual(primary_key_column, u'id')
     107
    98108    def test_get_indexes(self):
    99109        cursor = connection.cursor()
    100110        indexes = connection.introspection.get_indexes(cursor, Article._meta.db_table)
  • tests/regressiontests/serializers_regress/tests.py

    diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
    index cd2ce3c..bb6f598 100644
    a b test case that is capable of testing the capabilities of  
    66the serializers. This includes all valid data values, plus
    77forward, backwards and self references.
    88"""
     9# This is necessary in Python 2.5 to enable the with statement, in 2.6
     10# and up it is no longer necessary.
    911from __future__ import with_statement
    1012
    1113import datetime
    def serializerTest(format, self):  
    382384    objects = []
    383385    instance_count = {}
    384386    for (func, pk, klass, datum) in test_data:
    385         objects.extend(func[0](pk, klass, datum))
     387        with connection.constraint_checks_disabled():
     388            objects.extend(func[0](pk, klass, datum))
    386389
    387390    # Get a count of the number of objects created for each class
    388391    for klass in instance_count:
  • tests/regressiontests/test_utils/models.py

    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  
    33
    44class Person(models.Model):
    55    name = models.CharField(max_length=100)
     6
     7class 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',)
  • tests/regressiontests/test_utils/tests.py

    diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py
    index a35b0bc..6e26b24 100644
    a b  
    11from __future__ import with_statement
    22
    3 from django.test import TestCase, skipUnlessDBFeature
     3from django.test import TestCase, skipUnlessDBFeature, ignore_num_queries
    44from django.utils.unittest import skip
     5from django.db import IntegrityError
    56
    6 from models import Person
     7from models import Person, Pet
    78
    89
    910class SkippingTestCase(TestCase):
    class AssertNumQueriesTests(TestCase):  
    4748            self.client.get("/test_utils/get_person/%s/" % person.pk)
    4849            self.client.get("/test_utils/get_person/%s/" % person.pk)
    4950        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)
    5060
    5161class AssertNumQueriesContextManagerTests(TestCase):
    5262    urls = 'regressiontests.test_utils.urls'
    class AssertNumQueriesContextManagerTests(TestCase):  
    8696            self.client.get("/test_utils/get_person/%s/" % person.pk)
    8797
    8898
     99class 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
    89110class SaveRestoreWarningState(TestCase):
    90111    def test_save_restore_warnings_state(self):
    91112        """
Back to Top