From 7765d3ba9b02d612f7c466f5ca0ea8fba1405372 Mon Sep 17 00:00:00 2001 From: Josh Schneier Date: Sun, 11 Jun 2017 20:05:54 -0400 Subject: [PATCH] [1.11.x] Fixed #28298 -- Prevented a primary key alteration from adding a foreign key constraint if db_constraint=False. Backport of fba0eaa5d60603721d7b4653e3efacbfb3613bd2 from master --- django/db/backends/base/schema.py | 2 +- docs/releases/1.11.3.txt | 3 +++ tests/schema/tests.py | 39 +++++++++++++++++++++++++++---- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index e36c46e593..f4b8b02049 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -774,7 +774,7 @@ class BaseDatabaseSchemaEditor(object): # Rebuild FKs that pointed to us if we previously had to drop them if old_field.primary_key and new_field.primary_key and old_type != new_type: for rel in new_field.model._meta.related_objects: - if not rel.many_to_many: + if not rel.many_to_many and rel.field.db_constraint: self.execute(self._create_fk_sql(rel.related_model, rel.field, "_fk")) # Does it have check constraints we need to add? if old_db_params['check'] != new_db_params['check'] and new_db_params['check']: diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index 9aab4fbfb7..1c716bf5a3 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -51,3 +51,6 @@ Bugfixes 1.11.1, casting was added in Python to avoid localization of numeric values in Django templates, but this made some use cases more difficult. Casting is now done in the template using the ``|stringformat:'s'`` filter. + +* Prevented a primary key alteration from adding a foreign key constraint if + ``db_constraint=False`` (:ticket:`28298`). diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 0423c2ff13..671e1234ff 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -9,9 +9,9 @@ from django.db import ( from django.db.models import Model from django.db.models.deletion import CASCADE, PROTECT from django.db.models.fields import ( - AutoField, BigIntegerField, BinaryField, BooleanField, CharField, - DateField, DateTimeField, IntegerField, PositiveIntegerField, SlugField, - TextField, TimeField, + AutoField, BigAutoField, BigIntegerField, BinaryField, BooleanField, + CharField, DateField, DateTimeField, IntegerField, PositiveIntegerField, + SlugField, TextField, TimeField, ) from django.db.models.fields.related import ( ForeignKey, ForeignObject, ManyToManyField, OneToOneField, @@ -21,7 +21,7 @@ from django.db.transaction import TransactionManagementError, atomic from django.test import ( TransactionTestCase, mock, skipIfDBFeature, skipUnlessDBFeature, ) -from django.test.utils import CaptureQueriesContext +from django.test.utils import CaptureQueriesContext, isolate_apps from django.utils import timezone from .fields import ( @@ -287,6 +287,37 @@ class SchemaTests(TransactionTestCase): editor.alter_field(Author, new_field2, new_field, strict=True) self.assertForeignKeyNotExists(Author, 'tag_id', 'schema_tag') + @isolate_apps('schema') + def test_no_db_constraint_added_during_primary_key_change(self): + """ + When a primary key that's pointed to by a ForeignKey with + db_constraint=False is altered, a foreign key constraint isn't added. + """ + class Author(Model): + class Meta: + app_label = 'schema' + + class BookWeak(Model): + author = ForeignKey(Author, CASCADE, db_constraint=False) + + class Meta: + app_label = 'schema' + + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(BookWeak) + self.assertForeignKeyNotExists(BookWeak, 'author_id', 'schema_author') + old_field = Author._meta.get_field('id') + new_field = BigAutoField(primary_key=True) + new_field.model = Author + new_field.set_attributes_from_name('id') + # @isolate_apps() and inner models are needed to have the model + # relations populated, otherwise this doesn't act as a regression test. + self.assertEqual(len(new_field.model._meta.related_objects), 1) + with connection.schema_editor() as editor: + editor.alter_field(Author, old_field, new_field, strict=True) + self.assertForeignKeyNotExists(BookWeak, 'author_id', 'schema_author') + def _test_m2m_db_constraint(self, M2MFieldClass): class LocalAuthorWithM2M(Model): name = CharField(max_length=255)