Ticket #3615: allow_forward_refs_in_fixtures_v3.diff
File allow_forward_refs_in_fixtures_v3.diff, 15.8 KB (added by , 13 years ago) |
---|
-
django/db/backends/mysql/base.py
25 25 from MySQLdb.converters import conversions 26 26 from MySQLdb.constants import FIELD_TYPE, CLIENT 27 27 28 from django.db import utils 28 from django.db import utils, IntegrityError 29 29 from django.db.backends import * 30 30 from django.db.backends.signals import connection_created 31 31 from django.db.backends.mysql.client import DatabaseClient … … 349 349 raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info()) 350 350 self.server_version = tuple([int(x) for x in m.groups()]) 351 351 return self.server_version 352 353 def disable_foreign_key_checks(self): 354 """ 355 Disable foreign key checks, primarily for us in adding rows with forward references. Always returns True, 356 to indicate checks have been disabled. 357 """ 358 self.foreign_key_checks_disabled = True 359 self.cursor().execute('SET foreign_key_checks=0') 360 return True 361 362 def enable_foreign_key_checks(self): 363 """ 364 Re-enable foreign key checks after they have been disabled. Always returns True, 365 to indicate checks have been re-enabled. 366 """ 367 self.foreign_key_checks_disabled = False 368 self.cursor().execute('SET foreign_key_checks=1') 369 return True 370 371 def check_for_invalid_foreign_keys(self, table_name): 372 """ 373 Checks given table for rows with invalid foreign key references. This method is intended to be used in 374 conjunction with `disable_foreign_keys()` and `enable_foreign_keys()`, to determine if rows with 375 invalid references were entered while foreign key checks were off. 376 377 Raises an IntegrityError on the first invalid foreign key reference encountered (if any) and provides 378 detailed information about the invalid reference in the error message. 379 """ 380 cursor = self.cursor() 381 key_columns = self.introspection.get_key_columns(cursor, table_name) 382 for column_name, referenced_table_name, referenced_column_name in key_columns: 383 primary_key_column_name = [c[0] for c in self.introspection.get_indexes(cursor, table_name).iteritems() if c[1]['primary_key']][0] 384 cursor.execute(""" 385 SELECT REFERRING.`%s`, REFERRING.`%s` FROM `%s` as REFERRING 386 LEFT JOIN `%s` as REFERRED 387 ON (REFERRING.`%s` = REFERRED.`%s`) 388 WHERE REFERRING.`%s` IS NOT NULL 389 AND REFERRED.`%s` IS NULL""" 390 % (primary_key_column_name, column_name, table_name, referenced_table_name, 391 column_name, referenced_column_name, column_name, referenced_column_name)) 392 for bad_row in cursor.fetchall(): 393 raise IntegrityError("The row in table '%s' with primary key '%s' has an invalid \ 394 foreign key: %s.%s contains a value '%s' that does not have a corresponding value in %s.%s." 395 % (table_name, bad_row[0], table_name, column_name, bad_row[1], 396 referenced_table_name, referenced_column_name)) -
django/db/backends/mysql/introspection.py
51 51 representing all relationships to the given table. Indexes are 0-based. 52 52 """ 53 53 my_field_dict = self._name_to_index(cursor, table_name) 54 constraints = []54 constraints = self.get_key_columns(cursor, table_name) 55 55 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 = [] 56 68 try: 57 # This should work for MySQL 5.0.58 69 cursor.execute(""" 59 70 SELECT column_name, referenced_table_name, referenced_column_name 60 71 FROM information_schema.key_column_usage … … 62 73 AND table_schema = DATABASE() 63 74 AND referenced_table_name IS NOT NULL 64 75 AND referenced_column_name IS NOT NULL""", [table_name]) 65 constraints.extend(cursor.fetchall())76 key_columns.extend(cursor.fetchall()) 66 77 except (ProgrammingError, OperationalError): 67 78 # Fall back to "SHOW CREATE TABLE", for previous MySQL versions. 68 79 # Go through all constraints and save the equal matches. … … 74 85 if match == None: 75 86 break 76 87 pos = match.end() 77 constraints.append(match.groups()) 88 key_columns.append(match.groups()) 89 return key_columns 78 90 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 relations85 86 91 def get_indexes(self, cursor, table_name): 87 92 """ 88 93 Returns a dictionary of fieldname -> infodict for the given table, -
django/db/backends/__init__.py
34 34 self.transaction_state = [] 35 35 self.savepoint_state = 0 36 36 self._dirty = None 37 38 self.foreign_key_checks_disabled = False 37 39 38 40 def __eq__(self, other): 39 41 return self.alias == other.alias … … 239 241 if self.savepoint_state: 240 242 self._savepoint_commit(sid) 241 243 244 def disable_foreign_key_checks(self): 245 """ 246 Backends can implement as needed to temporarily disable foreign key constraint 247 checking. 248 """ 249 pass 250 251 def enable_foreign_key_checks(self): 252 """ 253 Backends can implement as needed to re-enable foreign key constraint 254 checking. 255 """ 256 pass 257 258 def check_for_invalid_foreign_keys(self, table_name): 259 """ 260 Backends can implement as needed to raise IntegrityError if invalid foreign keys 261 are found in a table (for use when adding rows with foreign key checks disabled) 262 """ 263 pass 264 242 265 def close(self): 243 266 if self.connection is not None: 244 267 self.connection.close() … … 870 893 871 894 return sequence_list 872 895 896 def get_key_columns(self, cursor, table_name): 897 raise NotImplementedError 898 873 899 class BaseDatabaseClient(object): 874 900 """ 875 901 This class encapsulates all backend-specific methods for opening a -
django/db/backends/dummy/base.py
34 34 get_table_description = complain 35 35 get_relations = complain 36 36 get_indexes = complain 37 get_key_columns = complain 37 38 38 39 class DatabaseWrapper(BaseDatabaseWrapper): 39 40 operators = {} -
django/core/management/commands/loaddata.py
8 8 from django.core import serializers 9 9 from django.core.management.base import BaseCommand 10 10 from django.core.management.color import no_style 11 from django.db import connections, router, transaction, DEFAULT_DB_ALIAS 11 from django.db import connections, router, transaction, DEFAULT_DB_ALIAS, models as db_models 12 12 from django.db.models import get_apps 13 13 from django.utils.itercompat import product 14 14 … … 161 161 fixture_count += 1 162 162 objects_in_fixture = 0 163 163 loaded_objects_in_fixture = 0 164 saved_objects = [] 164 165 if verbosity >= 2: 165 166 self.stdout.write("Installing %s fixture '%s' from %s.\n" % \ 166 167 (format, fixture_name, humanize(fixture_dir))) 167 168 try: 168 169 objects = serializers.deserialize(format, fixture, using=using) 170 foreign_key_checks_disabled = connection.disable_foreign_key_checks() 169 171 for obj in objects: 170 172 objects_in_fixture += 1 171 173 if router.allow_syncdb(using, obj.object.__class__): 172 174 loaded_objects_in_fixture += 1 173 175 models.add(obj.object.__class__) 174 176 obj.save(using=using) 177 saved_objects.append(obj.object) 178 179 # If we disabled foreign key checks, then we should re-enable them and check for 180 # any invalid keys that might have been added 181 if foreign_key_checks_disabled: 182 connection.enable_foreign_key_checks() 183 for model in models: 184 connection.check_for_invalid_foreign_keys(table_name=model._meta.db_table) 185 175 186 loaded_object_count += loaded_objects_in_fixture 176 187 fixture_object_count += objects_in_fixture 177 188 label_found = True -
tests/modeltests/serializers/tests.py
5 5 6 6 from django.conf import settings 7 7 from django.core import serializers 8 from django.db import transaction 8 from django.db import transaction, connection 9 9 from django.test import TestCase, TransactionTestCase, Approximate 10 10 from django.utils import simplejson, unittest 11 11 … … 252 252 transaction.enter_transaction_management() 253 253 transaction.managed(True) 254 254 objs = serializers.deserialize(self.serializer_name, self.fwd_ref_str) 255 connection.disable_foreign_key_checks() 255 256 for obj in objs: 256 257 obj.save() 258 connection.enable_foreign_key_checks() 257 259 transaction.commit() 258 260 transaction.leave_transaction_management() 259 261 -
tests/regressiontests/serializers_regress/tests.py
383 383 objects = [] 384 384 instance_count = {} 385 385 for (func, pk, klass, datum) in test_data: 386 connection.disable_foreign_key_checks() 386 387 objects.extend(func[0](pk, klass, datum)) 388 connection.enable_foreign_key_checks 387 389 388 390 # Get a count of the number of objects created for each class 389 391 for klass in instance_count: -
tests/regressiontests/introspection/tests.py
96 96 # That's {field_index: (field_index_other_table, other_table)} 97 97 self.assertEqual(relations, {3: (0, Reporter._meta.db_table)}) 98 98 99 def test_get_key_columns(self): 100 cursor = connection.cursor() 101 key_columns = connection.introspection.get_key_columns(cursor, Article._meta.db_table) 102 self.assertEqual(key_columns, [(u'reporter_id', Reporter._meta.db_table, u'id')]) 103 99 104 def test_get_indexes(self): 100 105 cursor = connection.cursor() 101 106 indexes = connection.introspection.get_indexes(cursor, Article._meta.db_table) -
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
12 12 from django.core.management.commands.dumpdata import sort_dependencies 13 13 from django.core.management.base import CommandError 14 14 from django.db.models import signals 15 from django.db import transaction 15 from django.db import transaction, IntegrityError 16 16 from django.test import TestCase, TransactionTestCase, skipIfDBFeature, \ 17 17 skipUnlessDBFeature 18 18 … … 362 362 """[{"pk": %d, "model": "fixtures_regress.widget", "fields": {"name": "grommet"}}]""" 363 363 % widget.pk 364 364 ) 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 ) 365 394 366 395 367 396 class NaturalKeyFixtureTests(TestCase):