[1.7.x] Fixed #24817 -- Prevented loss of null info in MySQL field renaming.

Backport of 80ad5472ce4b6ba6e94227422d0727371e97cdf0 from master
This commit is contained in:
Andriy Sokolovskiy 2015-05-27 01:18:21 +03:00 committed by Tim Graham
parent 8bb369ef63
commit 927d90ee1e
6 changed files with 81 additions and 18 deletions

View File

@ -49,10 +49,21 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
'column': self.quote_name(field.column), 'column': self.quote_name(field.column),
}, [effective_default]) }, [effective_default])
def _alter_column_type_sql(self, table, old_field, new_field, new_type): def _set_field_new_type_null_status(self, field, new_type):
# Keep null property of old field, if it has changed, it will be handled separately """
if old_field.null: Keep the null property of the old field. If it has changed, it will be
handled separately.
"""
if field.null:
new_type += " NULL" new_type += " NULL"
else: else:
new_type += " NOT NULL" new_type += " NOT NULL"
return new_type
def _alter_column_type_sql(self, table, old_field, new_field, new_type):
new_type = self._set_field_new_type_null_status(old_field, new_type)
return super(DatabaseSchemaEditor, self)._alter_column_type_sql(table, old_field, new_field, new_type) return super(DatabaseSchemaEditor, self)._alter_column_type_sql(table, old_field, new_field, new_type)
def _rename_field_sql(self, table, old_field, new_field, new_type):
new_type = self._set_field_new_type_null_status(old_field, new_type)
return super(DatabaseSchemaEditor, self)._rename_field_sql(table, old_field, new_field, new_type)

View File

@ -527,12 +527,7 @@ class BaseDatabaseSchemaEditor(object):
self.execute(self._delete_constraint_sql(self.sql_delete_check, model, constraint_name)) self.execute(self._delete_constraint_sql(self.sql_delete_check, model, constraint_name))
# Have they renamed the column? # Have they renamed the column?
if old_field.column != new_field.column: if old_field.column != new_field.column:
self.execute(self.sql_rename_column % { self.execute(self._rename_field_sql(model._meta.db_table, old_field, new_field, new_type))
"table": self.quote_name(model._meta.db_table),
"old_column": self.quote_name(old_field.column),
"new_column": self.quote_name(new_field.column),
"type": new_type,
})
# Next, start accumulating actions to do # Next, start accumulating actions to do
actions = [] actions = []
null_actions = [] null_actions = []
@ -841,6 +836,14 @@ class BaseDatabaseSchemaEditor(object):
output.append(self._create_index_sql(model, fields, suffix="_idx")) output.append(self._create_index_sql(model, fields, suffix="_idx"))
return output return output
def _rename_field_sql(self, table, old_field, new_field, new_type):
return self.sql_rename_column % {
"table": self.quote_name(table),
"old_column": self.quote_name(old_field.column),
"new_column": self.quote_name(new_field.column),
"type": new_type,
}
def _create_fk_sql(self, model, field, suffix): def _create_fk_sql(self, model, field, suffix):
from_table = model._meta.db_table from_table = model._meta.db_table
from_column = field.column from_column = field.column

10
docs/releases/1.7.9.txt Normal file
View File

@ -0,0 +1,10 @@
==========================
Django 1.7.9 release notes
==========================
*Under development*
Django 1.7.9 fixes several bugs in 1.7.8.
* Prevented the loss of ``null``/``not null`` column properties during field
renaming of MySQL databases (:ticket:`24817`).

View File

@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
1.7.9
1.7.8 1.7.8
1.7.7 1.7.7
1.7.6 1.7.6

View File

@ -122,6 +122,14 @@ class BookWithSlug(models.Model):
db_table = "schema_book" db_table = "schema_book"
class NoteRename(models.Model):
detail_info = models.TextField()
class Meta:
apps = new_apps
db_table = "schema_note"
class Tag(models.Model): class Tag(models.Model):
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
slug = models.SlugField(unique=True) slug = models.SlugField(unique=True)

View File

@ -1,17 +1,27 @@
import datetime import datetime
import unittest import unittest
from django.test import TransactionTestCase, skipIfDBFeature from django.db import (
from django.db import connection, DatabaseError, IntegrityError, OperationalError DatabaseError, IntegrityError, OperationalError, connection,
from django.db.models.fields import (BigIntegerField, BinaryField, BooleanField, CharField, )
IntegerField, PositiveIntegerField, SlugField, TextField) from django.db.models.fields import (
from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField BigIntegerField, BinaryField, BooleanField, CharField, IntegerField,
PositiveIntegerField, SlugField, TextField,
)
from django.db.models.fields.related import (
ForeignKey, ManyToManyField, OneToOneField,
)
from django.db.transaction import atomic from django.db.transaction import atomic
from django.test import TransactionTestCase, skipIfDBFeature
from .fields import CustomManyToManyField, InheritedManyToManyField from .fields import CustomManyToManyField, InheritedManyToManyField
from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName, from .models import (
BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, Author, AuthorTag, AuthorWithDefaultHeight, AuthorWithEvenLongerName,
UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough, AuthorWithM2M, AuthorWithM2MThrough, Book, BookWeak, BookWithLongName,
AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O, BookWithoutFK) BookWithM2M, BookWithM2MThrough, BookWithO2O, BookWithoutFK, BookWithSlug,
Note, NoteRename, Tag, TagIndexed, TagM2MTest, TagThrough, TagUniqueRename,
Thing, UniqueTest,
)
class SchemaTests(TransactionTestCase): class SchemaTests(TransactionTestCase):
@ -754,6 +764,26 @@ class SchemaTests(TransactionTestCase):
self.assertEqual(columns['display_name'][0], "CharField") self.assertEqual(columns['display_name'][0], "CharField")
self.assertNotIn("name", columns) self.assertNotIn("name", columns)
@skipIfDBFeature('interprets_empty_strings_as_nulls')
def test_rename_keep_null_status(self):
"""
Renaming a field shouldn't affect the not null status.
"""
with connection.schema_editor() as editor:
editor.create_model(Note)
with self.assertRaises(IntegrityError):
Note.objects.create(info=None)
old_field = Note._meta.get_field("info")
new_field = TextField()
new_field.set_attributes_from_name("detail_info")
with connection.schema_editor() as editor:
editor.alter_field(Note, old_field, new_field, strict=True)
columns = self.column_classes(Note)
self.assertEqual(columns['detail_info'][0], "TextField")
self.assertNotIn("info", columns)
with self.assertRaises(IntegrityError):
NoteRename.objects.create(detail_info=None)
def test_m2m_create(self): def test_m2m_create(self):
""" """
Tests M2M fields on models during creation Tests M2M fields on models during creation