Ticket #3615: allow_forward_refs_in_fixtures_v2.diff

File allow_forward_refs_in_fixtures_v2.diff, 10.6 KB (added by Jim Dalton, 10 years ago)

Still needs a bit of work, but this is basically a solution

  • tests/regressiontests/fixtures_regress/fixtures/forward_ref_bad_data.json

     
     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/fixtures/forward_ref.json

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

     
    1212from django.core.management.commands.dumpdata import sort_dependencies
    1313from django.core.management.base import CommandError
    1414from django.db.models import signals
    15 from django.db import transaction
     15from django.db import transaction, IntegrityError
    1616from django.test import TestCase, TransactionTestCase, skipIfDBFeature, \
    1717    skipUnlessDBFeature
    1818
     
    362362            """[{"pk": %d, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]"""
    363363            % widget.pk
    364364            )
     365   
     366    def test_loaddata_works_when_fixture_has_forward_refs(self):
     367        """
     368        Regression for #3615 - Forward references cause fixtures not to load in MySQL (InnoDB)
     369        """
     370        management.call_command(
     371            'loaddata',
     372            'forward_ref.json',
     373            verbosity=0,
     374            commit=False
     375        )
     376        self.assertEqual(Book.objects.all()[0].id, 1)
     377        self.assertEqual(Person.objects.all()[0].id, 4)
     378   
     379    def test_loaddata_raise_when_fixture_has_forward_refs(self):
     380        """
     381        Regression for #3615 - Ensure data with nonexistent child key references raises error
     382        """
     383        stderr = StringIO()
     384        management.call_command(
     385            'loaddata',
     386            'forward_ref_bad_data.json',
     387            verbosity=0,
     388            commit=False,
     389            stderr=stderr,
     390        )
     391        self.assertTrue(
     392            stderr.getvalue().startswith('Problem installing fixture')
     393        )
    365394
    366395
    367396class NaturalKeyFixtureTests(TestCase):
  • django/db/backends/mysql/base.py

     
    2525from MySQLdb.converters import conversions
    2626from MySQLdb.constants import FIELD_TYPE, CLIENT
    2727
    28 from django.db import utils
     28from django.db import utils, IntegrityError
    2929from django.db.backends import *
    3030from django.db.backends.signals import connection_created
    3131from django.db.backends.mysql.client import DatabaseClient
     
    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 begin_defer_constraint_checks(self):
     354        self.foreign_key_checks_disabled = True
     355        self.cursor().execute('SET foreign_key_checks=0')
     356
     357    def end_defer_constraint_checks(self):
     358        self.foreign_key_checks_disabled = False
     359        self.cursor().execute('SET foreign_key_checks=1')
     360   
     361    def check_foreign_key_constraints_for_table(self, table_name):
     362        cursor = self.cursor()
     363        bad_rows = []
     364        key_columns = self.introspection.get_key_columns(self.cursor(), table_name)
     365        for column_name, referenced_table_name, referenced_column_name in key_columns:
     366            cursor.execute("""
     367                SELECT * FROM `%s` as REFERRING
     368                LEFT JOIN `%s` as REFERRED
     369                ON (REFERRING.`%s` = REFERRED.`%s`)
     370                WHERE REFERRING.`%s` IS NOT NULL
     371                    AND REFERRED.`%s` IS NULL
     372                """ % (table_name, referenced_table_name, column_name, referenced_column_name, column_name, referenced_column_name))
     373            bad_rows.extend(cursor.fetchall())
     374        if bad_rows:
     375            raise IntegrityError("Bad rows found.")
  • django/db/backends/mysql/introspection.py

     
    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        key_columns = []
    5664        try:
    5765            # This should work for MySQL 5.0.
    5866            cursor.execute("""
     
    6270                    AND table_schema = DATABASE()
    6371                    AND referenced_table_name IS NOT NULL
    6472                    AND referenced_column_name IS NOT NULL""", [table_name])
    65             constraints.extend(cursor.fetchall())
     73            key_columns.extend(cursor.fetchall())
    6674        except (ProgrammingError, OperationalError):
    6775            # Fall back to "SHOW CREATE TABLE", for previous MySQL versions.
    6876            # Go through all constraints and save the equal matches.
     
    7482                    if match == None:
    7583                        break
    7684                    pos = match.end()
    77                     constraints.append(match.groups())
     85                    key_columns.append(match.groups())
     86        return key_columns
    7887
    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
    85 
    8688    def get_indexes(self, cursor, table_name):
    8789        """
    8890        Returns a dictionary of fieldname -> infodict for the given table,
  • django/db/backends/__init__.py

     
    3434        self.transaction_state = []
    3535        self.savepoint_state = 0
    3636        self._dirty = None
     37       
     38        self.foreign_key_checks_disabled = False
    3739
    3840    def __eq__(self, other):
    3941        return self.alias == other.alias
     
    239241        if self.savepoint_state:
    240242            self._savepoint_commit(sid)
    241243
     244    def begin_defer_constraint_checks(self):
     245        return None
     246
     247    def end_defer_constraint_checks(self):
     248        return None
     249
    242250    def close(self):
    243251        if self.connection is not None:
    244252            self.connection.close()
  • django/core/management/commands/loaddata.py

     
    88from django.core import serializers
    99from django.core.management.base import BaseCommand
    1010from django.core.management.color import no_style
    11 from django.db import connections, router, transaction, DEFAULT_DB_ALIAS
     11from django.db import connections, router, transaction, DEFAULT_DB_ALIAS, models as db_models
    1212from django.db.models import get_apps
    1313from django.utils.itercompat import product
    1414
     
    161161                            fixture_count += 1
    162162                            objects_in_fixture = 0
    163163                            loaded_objects_in_fixture = 0
     164                            saved_objects = []
    164165                            if verbosity >= 2:
    165166                                self.stdout.write("Installing %s fixture '%s' from %s.\n" % \
    166167                                    (format, fixture_name, humanize(fixture_dir)))
    167168                            try:
    168169                                objects = serializers.deserialize(format, fixture, using=using)
     170                                connection.begin_defer_constraint_checks()
    169171                                for obj in objects:
    170172                                    objects_in_fixture += 1
    171173                                    if router.allow_syncdb(using, obj.object.__class__):
    172174                                        loaded_objects_in_fixture += 1
    173175                                        models.add(obj.object.__class__)
    174176                                        obj.save(using=using)
     177                                        saved_objects.append(obj.object)
     178                                       
     179                                # If we disabled foreign_key_checks, then re-enable them and save them all
     180                                # again. This will expose any referential integrity errors.
     181                                if connection.foreign_key_checks_disabled:
     182                                    connection.end_defer_constraint_checks()
     183                                    for model in models:
     184                                        connection.check_foreign_key_constraints_for_table(table_name=model._meta.db_table)
     185                                   
    175186                                loaded_object_count += loaded_objects_in_fixture
    176187                                fixture_object_count += objects_in_fixture
    177188                                label_found = True
Back to Top