【Django】makemigration錯誤:自動產生script的錯誤(含database還原教學)

使用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_typecustomer_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_type1customer_type2,轉換後都變成預設值01

仔細研究發現,他居然是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

  1. 會先修改的是db_column,把CUSTOMER_TYPE1變成CUSTOMER_TYPECUSTOMER_TYPE2變成CUSTOMER_SUB_TYPE
  2. 接著是python程式碼內的namecustomer_type1變成customer_typecustomer_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_typecustomer_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來把我們這兩個00080009.py檔改變MySQL的欄位名稱。

當然要檢查一下結果囉!

看起來很棒!資料都有順利過去了,這樣就大功告成!!

 


心得:

Django可以讓我們很方便,但是在真的執行前,需要看一下改變DB的script是否正確,不然會跟我一樣,還好有備份,不然就欲哭無淚....

 

~Copyright by Eyelash500~

技術文章:EY*研究院
iT邦幫忙:eyelash*睫毛
Blog:睫毛*Relax
Facebook:睫毛*Relax