From 02365d3f38a64a5c2f3e932f23925a381d5bb151 Mon Sep 17 00:00:00 2001 From: Tim Martin Date: Mon, 18 Sep 2017 20:54:48 +0100 Subject: [PATCH] Fixed #28542 -- Fixed deletion of primary key constraint if the new field is unique. --- django/db/backends/base/schema.py | 9 +++-- tests/schema/tests.py | 56 ++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 4fb6174437..f1e8e66479 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -527,7 +527,7 @@ class BaseDatabaseSchemaEditor: # Has unique been removed? if old_field.unique and (not new_field.unique or (not old_field.primary_key and new_field.primary_key)): # Find the unique constraint for this field - constraint_names = self._constraint_names(model, [old_field.column], unique=True) + constraint_names = self._constraint_names(model, [old_field.column], unique=True, primary_key=False) if strict and len(constraint_names) != 1: raise ValueError("Found wrong number (%s) of unique constraints for %s.%s" % ( len(constraint_names), @@ -671,6 +671,9 @@ class BaseDatabaseSchemaEditor: if post_actions: for sql, params in post_actions: self.execute(sql, params) + # If primary_key changed to False, delete the primary key constraint. + if old_field.primary_key and not new_field.primary_key: + self._delete_primary_key(model, strict) # Added a unique? if (not old_field.unique and new_field.unique) or ( old_field.primary_key and not new_field.primary_key and new_field.unique @@ -693,11 +696,7 @@ class BaseDatabaseSchemaEditor: if old_field.primary_key and new_field.primary_key and old_type != new_type: rels_to_update.extend(_related_non_m2m_objects(old_field, new_field)) # Changed to become primary key? - # Note that we don't detect unsetting of a PK, as we assume another field - # will always come along and replace it. if not old_field.primary_key and new_field.primary_key: - # First, drop the old PK - self._delete_primary_key(model, strict) # Make the new one self.execute( self.sql_create_pk % { diff --git a/tests/schema/tests.py b/tests/schema/tests.py index cc2d5384f8..3a66c2ae7a 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -1098,6 +1098,42 @@ class SchemaTests(TransactionTestCase): Should be able to rename an IntegerField(primary_key=True) to IntegerField(unique=True). """ + with connection.schema_editor() as editor: + editor.create_model(IntegerPK) + # Delete the old PK + old_field = IntegerPK._meta.get_field('i') + new_field = IntegerField(unique=True) + new_field.model = IntegerPK + new_field.set_attributes_from_name('i') + with connection.schema_editor() as editor: + editor.alter_field(IntegerPK, old_field, new_field, strict=True) + # The primary key constraint is gone. Result depends on database: + # 'id' for SQLite, None for others (must not be 'i'). + self.assertIn(self.get_primary_key(IntegerPK._meta.db_table), ('id', None)) + + # Set up a model class as it currently stands. The original IntegerPK + # class is now out of date and some backends make use of the whole + # model class when modifying a field (such as sqlite3 when remaking a + # table) so an outdated model class leads to incorrect results. + class Transitional(Model): + i = IntegerField(unique=True) + j = IntegerField(unique=True) + + class Meta: + app_label = 'schema' + apps = new_apps + db_table = 'INTEGERPK' + + # model requires a new PK + old_field = Transitional._meta.get_field('j') + new_field = IntegerField(primary_key=True) + new_field.model = Transitional + new_field.set_attributes_from_name('j') + + with connection.schema_editor() as editor: + editor.alter_field(Transitional, old_field, new_field, strict=True) + + # Create a model class representing the updated model. class IntegerUnique(Model): i = IntegerField(unique=True) j = IntegerField(primary_key=True) @@ -1107,26 +1143,6 @@ class SchemaTests(TransactionTestCase): apps = new_apps db_table = 'INTEGERPK' - with connection.schema_editor() as editor: - editor.create_model(IntegerPK) - - # model requires a new PK - old_field = IntegerPK._meta.get_field('j') - new_field = IntegerField(primary_key=True) - new_field.model = IntegerPK - new_field.set_attributes_from_name('j') - - with connection.schema_editor() as editor: - editor.alter_field(IntegerPK, old_field, new_field, strict=True) - - old_field = IntegerPK._meta.get_field('i') - new_field = IntegerField(unique=True) - new_field.model = IntegerPK - new_field.set_attributes_from_name('i') - - with connection.schema_editor() as editor: - editor.alter_field(IntegerPK, old_field, new_field, strict=True) - # Ensure unique constraint works. IntegerUnique.objects.create(i=1, j=1) with self.assertRaises(IntegrityError):