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

Backport of 80ad5472ce 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),
}, [effective_default])
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:
def _set_field_new_type_null_status(self, field, new_type):
"""
Keep the null property of the old field. If it has changed, it will be
handled separately.
"""
if field.null:
new_type += " NULL"
else:
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)
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))
# Have they renamed the column?
if old_field.column != new_field.column:
self.execute(self.sql_rename_column % {
"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,
})
self.execute(self._rename_field_sql(model._meta.db_table, old_field, new_field, new_type))
# Next, start accumulating actions to do
actions = []
null_actions = []
@ -841,6 +836,14 @@ class BaseDatabaseSchemaEditor(object):
output.append(self._create_index_sql(model, fields, suffix="_idx"))
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):
from_table = model._meta.db_table
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::
:maxdepth: 1
1.7.9
1.7.8
1.7.7
1.7.6

View File

@ -122,6 +122,14 @@ class BookWithSlug(models.Model):
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):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)

View File

@ -1,17 +1,27 @@
import datetime
import unittest
from django.test import TransactionTestCase, skipIfDBFeature
from django.db import connection, DatabaseError, IntegrityError, OperationalError
from django.db.models.fields import (BigIntegerField, BinaryField, BooleanField, CharField,
IntegerField, PositiveIntegerField, SlugField, TextField)
from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField
from django.db import (
DatabaseError, IntegrityError, OperationalError, connection,
)
from django.db.models.fields import (
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.test import TransactionTestCase, skipIfDBFeature
from .fields import CustomManyToManyField, InheritedManyToManyField
from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName,
BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename,
UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough,
AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O, BookWithoutFK)
from .models import (
Author, AuthorTag, AuthorWithDefaultHeight, AuthorWithEvenLongerName,
AuthorWithM2M, AuthorWithM2MThrough, Book, BookWeak, BookWithLongName,
BookWithM2M, BookWithM2MThrough, BookWithO2O, BookWithoutFK, BookWithSlug,
Note, NoteRename, Tag, TagIndexed, TagM2MTest, TagThrough, TagUniqueRename,
Thing, UniqueTest,
)
class SchemaTests(TransactionTestCase):
@ -754,6 +764,26 @@ class SchemaTests(TransactionTestCase):
self.assertEqual(columns['display_name'][0], "CharField")
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):
"""
Tests M2M fields on models during creation