summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2015-04-11 16:10:31 +0200
committerClaude Paroz <claude@2xlibre.net>2015-04-17 11:01:36 +0200
commitbbfcd9618b06556a0ae2c38456ef38bf3bea9e9d (patch)
treebbfadf2f0cb3fb8ef91830b98fb1a27452d3b9e4 /django
parentd0b18542f740b358fb776e2b158ac69ff154896e (diff)
[1.8.x] Fixed #24595 -- Prevented loss of null info in MySQL field alteration
Thanks Simon Percivall for the report, and Simon Charette and Tim Graham for the reviews. Backport of 02260ea3f6 from master.
Diffstat (limited to 'django')
-rw-r--r--django/db/backends/base/schema.py54
-rw-r--r--django/db/backends/mysql/schema.py8
-rw-r--r--django/db/backends/postgresql_psycopg2/schema.py9
3 files changed, 47 insertions, 24 deletions
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index d53836e62e..041519bd00 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -9,9 +9,13 @@ from django.utils.log import getLogger
logger = getLogger('django.db.backends.schema')
-def _related_non_m2m_objects(opts):
- # filters out m2m objects from reverse relations.
- return (obj for obj in opts.related_objects if not obj.field.many_to_many)
+def _related_non_m2m_objects(old_field, new_field):
+ # Filters out m2m objects from reverse relations.
+ # Returns (old_relation, new_relation) tuples.
+ return zip(
+ (obj for obj in old_field.model._meta.related_objects if not obj.field.many_to_many),
+ (obj for obj in new_field.model._meta.related_objects if not obj.field.many_to_many)
+ )
class BaseDatabaseSchemaEditor(object):
@@ -456,7 +460,8 @@ class BaseDatabaseSchemaEditor(object):
old_type = old_db_params['type']
new_db_params = new_field.db_parameters(connection=self.connection)
new_type = new_db_params['type']
- if (old_type is None and old_field.rel is None) or (new_type is None and new_field.rel is None):
+ if ((old_type is None and old_field.rel is None) or
+ (new_type is None and new_field.rel is None)):
raise ValueError(
"Cannot alter field %s into %s - they do not properly define "
"db_type (are you using PostGIS 1.5 or badly-written custom "
@@ -517,10 +522,12 @@ class BaseDatabaseSchemaEditor(object):
if old_field.primary_key and new_field.primary_key and old_type != new_type:
# '_meta.related_field' also contains M2M reverse fields, these
# will be filtered out
- for rel in _related_non_m2m_objects(new_field.model._meta):
- rel_fk_names = self._constraint_names(rel.related_model, [rel.field.column], foreign_key=True)
+ for _old_rel, new_rel in _related_non_m2m_objects(old_field, new_field):
+ rel_fk_names = self._constraint_names(
+ new_rel.related_model, [new_rel.field.column], foreign_key=True
+ )
for fk_name in rel_fk_names:
- self.execute(self._delete_constraint_sql(self.sql_delete_fk, rel.related_model, fk_name))
+ self.execute(self._delete_constraint_sql(self.sql_delete_fk, new_rel.related_model, fk_name))
# Removed an index? (no strict check, as multiple indexes are possible)
if (old_field.db_index and not new_field.db_index and
not old_field.unique and not
@@ -554,7 +561,9 @@ class BaseDatabaseSchemaEditor(object):
post_actions = []
# Type change?
if old_type != new_type:
- fragment, other_actions = self._alter_column_type_sql(model._meta.db_table, new_field.column, new_type)
+ fragment, other_actions = self._alter_column_type_sql(
+ model._meta.db_table, old_field, new_field, new_type
+ )
actions.append(fragment)
post_actions.extend(other_actions)
# When changing a column NULL constraint to NOT NULL with a given
@@ -671,7 +680,7 @@ class BaseDatabaseSchemaEditor(object):
# referring to us.
rels_to_update = []
if old_field.primary_key and new_field.primary_key and old_type != new_type:
- rels_to_update.extend(_related_non_m2m_objects(new_field.model._meta))
+ 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.
@@ -694,20 +703,23 @@ class BaseDatabaseSchemaEditor(object):
}
)
# Update all referencing columns
- rels_to_update.extend(_related_non_m2m_objects(new_field.model._meta))
+ rels_to_update.extend(_related_non_m2m_objects(old_field, new_field))
# Handle our type alters on the other end of rels from the PK stuff above
- for rel in rels_to_update:
- rel_db_params = rel.field.db_parameters(connection=self.connection)
+ for old_rel, new_rel in rels_to_update:
+ rel_db_params = new_rel.field.db_parameters(connection=self.connection)
rel_type = rel_db_params['type']
+ fragment, other_actions = self._alter_column_type_sql(
+ new_rel.related_model._meta.db_table, old_rel.field, new_rel.field, rel_type
+ )
self.execute(
self.sql_alter_column % {
- "table": self.quote_name(rel.related_model._meta.db_table),
- "changes": self.sql_alter_column_type % {
- "column": self.quote_name(rel.field.column),
- "type": rel_type,
- }
- }
+ "table": self.quote_name(new_rel.related_model._meta.db_table),
+ "changes": fragment[0],
+ },
+ fragment[1],
)
+ for sql, params in other_actions:
+ self.execute(sql, params)
# Does it have a foreign key?
if (new_field.rel and
(fks_dropped or not old_field.rel or not old_field.db_constraint) and
@@ -742,7 +754,7 @@ class BaseDatabaseSchemaEditor(object):
if self.connection.features.connection_persists_old_columns:
self.connection.close()
- def _alter_column_type_sql(self, table, column, type):
+ def _alter_column_type_sql(self, table, old_field, new_field, new_type):
"""
Hook to specialize column type alteration for different backends,
for cases when a creation type is different to an alteration type
@@ -755,8 +767,8 @@ class BaseDatabaseSchemaEditor(object):
return (
(
self.sql_alter_column_type % {
- "column": self.quote_name(column),
- "type": type,
+ "column": self.quote_name(new_field.column),
+ "type": new_type,
},
[],
),
diff --git a/django/db/backends/mysql/schema.py b/django/db/backends/mysql/schema.py
index 7f3a2d357b..f2f3f0a4ed 100644
--- a/django/db/backends/mysql/schema.py
+++ b/django/db/backends/mysql/schema.py
@@ -61,3 +61,11 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
# index creation for FKs (index automatically created by MySQL)
field.db_index = False
return super(DatabaseSchemaEditor, self)._model_indexes_sql(model)
+
+ def _alter_column_type_sql(self, table, old_field, new_field, new_type):
+ # Keep null property of old field, if it has changed, it will be handled separately
+ if old_field.null:
+ new_type += " NULL"
+ else:
+ new_type += " NOT NULL"
+ return super(DatabaseSchemaEditor, self)._alter_column_type_sql(table, old_field, new_field, new_type)
diff --git a/django/db/backends/postgresql_psycopg2/schema.py b/django/db/backends/postgresql_psycopg2/schema.py
index 8340059b26..8889b3fbfb 100644
--- a/django/db/backends/postgresql_psycopg2/schema.py
+++ b/django/db/backends/postgresql_psycopg2/schema.py
@@ -34,11 +34,12 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
model, [field], suffix='_like', sql=self.sql_create_text_index))
return output
- def _alter_column_type_sql(self, table, column, type):
+ def _alter_column_type_sql(self, table, old_field, new_field, new_type):
"""
Makes ALTER TYPE with SERIAL make sense.
"""
- if type.lower() == "serial":
+ if new_type.lower() == "serial":
+ column = new_field.column
sequence_name = "%s_%s_seq" % (table, column)
return (
(
@@ -82,4 +83,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
],
)
else:
- return super(DatabaseSchemaEditor, self)._alter_column_type_sql(table, column, type)
+ return super(DatabaseSchemaEditor, self)._alter_column_type_sql(
+ table, old_field, new_field, new_type
+ )