Skip to content

Commit

Permalink
[1.7.x] Fixed #24817 -- Prevented loss of null info in MySQL field re…
Browse files Browse the repository at this point in the history
…naming.

Backport of 80ad547 from master
  • Loading branch information
coldmind authored and timgraham committed May 28, 2015
1 parent 8bb369e commit 927d90e
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 18 deletions.
17 changes: 14 additions & 3 deletions django/db/backends/mysql/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,21 @@ def add_field(self, model, field):
'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)
15 changes: 9 additions & 6 deletions django/db/backends/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,12 +527,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, old_db_p
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 = []
Expand Down Expand Up @@ -841,6 +836,14 @@ def _model_indexes_sql(self, model):
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
Expand Down
10 changes: 10 additions & 0 deletions docs/releases/1.7.9.txt
Original file line number Diff line number Diff line change
@@ -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`).
1 change: 1 addition & 0 deletions docs/releases/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions tests/schema/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ class Meta:
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)
Expand Down
48 changes: 39 additions & 9 deletions tests/schema/tests.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -754,6 +764,26 @@ def test_rename(self):
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
Expand Down

0 comments on commit 927d90e

Please sign in to comment.