使用makemigration,使Django自動產生修改DB的script,意外發現有個坑,會讓Django誤判,導致原本只是改名稱,變成新增欄位。
在用Django,可以很快速使用manage.py去產生相對應的.py檔,讓我們可以不用動SQL script就可以修改DB的資料結構。
就用的很開心的情況下,他背叛了我的期待 Q口Q
遇到的情況是:
我修改了兩個欄位名稱,包含在程式碼和MySQL Database內的欄位名稱(因為當初沒有想好,所以欄位名稱就改動了XD),結果發現,資料都不見了,只有預設的資料存在DB裡面。到底為啥會這樣呢?
細查發現,原來是「name」的問題!
解答:
先說結果:因為同時修改models.py內的變數名稱,以及Table的欄位名稱,所以導致Django認為這是要移除舊的欄位,新增欄位。
 
狀況是這樣,因為我一開始的直接修改models.py裡面的資料,把customer_type1改成customer_type,customer_type2改成customer_sub_type,而且也改了db_column。
class Customer(models.Model):
    customer_type = models.CharField(
        max_length=2, default="1", db_column="CUSTOMER_TYPE"
    )
    customer_type2 = models.CharField(
        max_length=1, default="0", db_column="CUSTOMER_TYPE2"
    )
然後自動產生的.py檔如下:
from django.db import migrations, models
class Migration(migrations.Migration):
    dependencies = [
        ('account', '0007_customer_customer_order_no'),
    ]
    operations = [
        migrations.RemoveField(
            model_name='customer',
            name='customer_type1',
        ),
        migrations.AddField(
            model_name='customer',
            name='customer_type',
            field=models.CharField(db_column='CUSTOMER_TYPE', default='1', max_length=2),
        ),
        migrations.RemoveField(
            model_name='customer',
            name='customer_type2',
        ),
        migrations.AddField(
            model_name='customer',
            name='customer_sub_type',
            field=models.CharField(db_column='CUSTOMER_SUB_TYPE', default='0', max_length=1),
        ),
    ]
當初就是對他太信任,沒有注意到奇怪的地方,就直接進行:python manage.py migrate,之後我的資料就都不見了,原本的customer_type1和customer_type2,轉換後都變成預設值0和1。

仔細研究發現,他居然是RemoveField舊的欄位,然後再AddField成新的欄位,所以並不是改名字,而是新增欄位,因此舊的資料都不見,全部變成預設值。
所以我這邊後續,要做的就是把資料結構和資料還原,重新製作步驟八(0008)。
重新製作
首先,進行還原作業,包含格式和資料。
用manage.py把資料結構回到上個migration。會需要這樣做的原因是,Django會在MySQL中記錄現在執行到哪一步,他會把缺的部份自己補齊,所以我們要經由manage.py回復到我們需要的版本,並且跟MySQL說要更換記錄點。程式碼格式為:python + manage.py + migrate + {app_name} + {還原點},我的案例的話就會如下:
python manage.py migrate account 0007_customer_customer_order_no
Database的資料結構回去後,再從備份資料的地方把資料回倒,確定資料都是齊全的(還好我先上測試環境,不然直接上正式,資料都不見了XD)。
接著重新製作我們的script。這邊的作法就是一次修改一個部分的name。
- 會先修改的是
db_column,把CUSTOMER_TYPE1變成CUSTOMER_TYPE,CUSTOMER_TYPE2變成CUSTOMER_SUB_TYPE - 接著是python程式碼內的
name。customer_type1變成customer_type,customer_type2變成customer_sub_type 
然後執行:python manage.py makemigrations
可以看出來Django製作出這樣的指令:
account/migrations/0008_auto_20201118_1644.py
    - Alter field customer_type1 on customer
    - Alter field customer_type2 on customer
接著檢查我們的.py檔,沒錯,就是只是改欄位而已:
from django.db import migrations, models
class Migration(migrations.Migration):
    dependencies = [
        ('account', '0007_customer_customer_order_no'),
    ]
    operations = [
        migrations.AlterField(
            model_name='customer',
            name='customer_type1',
            field=models.CharField(db_column='CUSTOMER_TYPE', default='1', max_length=2),
        ),
        migrations.AlterField(
            model_name='customer',
            name='customer_type2',
            field=models.CharField(db_column='CUSTOMER_SUB_TYPE', default='0', max_length=1),
        )
    ]
接下來,改我們程式內的名稱,customer_type1變成customer_type,customer_type2變成customer_sub_type:
class Customer(models.Model):
    ...
    customer_type = models.CharField(
        max_length=2, default="1", db_column="CUSTOMER_TYPE"
    )
    customer_sub_type = models.CharField(
        max_length=1, default="0", db_column="CUSTOMER_SUB_TYPE"
    )
   ...
接著再執行:python manage.py makemigrations,就會顯示兩個改名的提示:(為啥之前沒注意到沒有跳出來呢?QQ)
Did you rename customer.customer_type2 to customer.customer_sub_type (a CharField)? [y/N] 
Did you rename customer.customer_type1 to customer.customer_type (a CharField)? [y/N] 
回答完後,Django就會自行作業囉~
account/migrations/0009_auto_20201118_1649.py
    - Rename field customer_type2 on customer to customer_sub_type
    - Rename field customer_type1 on customer to customer_type
作業完畢後,檢查一下.py檔
from django.db import migrations, models
class Migration(migrations.Migration):
    dependencies = [
        ('account', '0008_auto_20201118_1644'),
    ]
    operations = [
        migrations.RenameField(
            model_name='customer',
            old_name='customer_type2',
            new_name='customer_sub_type',
        ),
        migrations.RenameField(
            model_name='customer',
            old_name='customer_type1',
            new_name='customer_type',
        ),    
    ]
就會有:old_name='customer_type2', new_name='customer_sub_type'等等的字樣,就表示真的是改名了。接著跑:python manage.py migrate來把我們這兩個0008和0009的.py檔改變MySQL的欄位名稱。
當然要檢查一下結果囉!

看起來很棒!資料都有順利過去了,這樣就大功告成!!
心得:
Django可以讓我們很方便,但是在真的執行前,需要看一下改變DB的script是否正確,不然會跟我一樣,還好有備份,不然就欲哭無淚....
~Copyright by Eyelash500~
IT技術文章:EY*研究院
iT邦幫忙:eyelash*睫毛
Blog:睫毛*Relax
Facebook:睫毛*Relax
