Django使用心得(一) 善用migrations


在使用和學習Django框架時,發現不少人包括我本身在對Django項目進行版本管理時,一般把migrations文件添加到了.gitignore中。html

筆者也一直有疑問這種作法是否正確,因而去查看官方文檔,找到如下這段。python

原文:git

You should think of migrations as a version control system for your database schema. makemigrations is responsible for packaging up your model changes into individual migration files - analogous to commits - and migrate is responsible for applying those to your database.github

The migration files for each app live in a 「migrations」 directory inside of that app, and are designed to be committed to, and distributed as part of, its codebase. You should be making them once on your development machine and then running the same migrations on your colleagues’ machines, your staging machines, and eventually your production machines.數據庫

中文翻譯:django

你能夠想象 migrations 至關一個你的數據庫的一個版本控制系統。makemigrations 命令負責保存你的模型變化到一個遷移文件 - 和 commits 很相似 - 同時 migrate負責將改變提交到數據庫。bash

每一個 app 的遷移文件會保存到每一個相應 app 的「migrations」文件夾裏面,而且準備如何去執行它, 做爲一個分佈式代碼庫。 每當在你的開發機器或是你同事的機器而且最終在你的生產機器上運行一樣的遷移,你應當再建立這些文件。app

根據官方文檔的說法,不將migrations提交到倉庫的作法時錯誤的。框架

並且若是要使用django自帶的封裝好的TestCase進行單元測試,migrations也必須保留。分佈式

下一篇文章筆者也會介紹一下django中的TestCase的使用心得。

下面介紹一下,在項目中migrations的一些使用心得和遇到的一些問題。

利用migrations初始化數據

咱們如今有一個Book的模型,我想在migrate以後初始化一些數據。

make_good_use_of_migrations/models.py view raw
class Book(models.Model):
    name = models.CharField(max_length=32)複製代碼

例如:生成三本名稱分別爲HamletTempestThe Little Prince的書。

在執行了python manage.py makemigrations以後migrations文件夾會生成0001_initial.py的文件。

文件中包含了Book這個模型初始化的一些代碼。

在介紹如何利用migrations初始化數據時,先介紹一下migrations經常使用的兩個操做:

RunSQLRunPython

顧名思義分別是執行SQL語句Python函數

下面我用migrations中的RunPython來初始化數據。

  1. 在相應app下的migrations文件新建0002_init_book_data.py migrations/.
    ​ ├── 0001_initial.py.
    ​ └── 0002_init_book_data.py.

  2. 而後增長Migration類繼承django.db.migrations.Migration,並在operations中增長鬚要執行的代碼。

make_good_use_of_migrations/migrations/0002_init_book_data.py view raw
from django.db import migrations

""" make_good_use_of_migrations 是App的名字 """


def init_book_data(apps, schema_editor):
    Book = apps.get_model('make_good_use_of_migrations', 'Book')
    init_data = ['Hamlet', 'Tempest', 'The Little Prince']
    for name in init_data:
        book = Book(name=name)
        book.save()


class Migration(migrations.Migration):
    dependencies = [
        ('make_good_use_of_migrations', '0001_initial'),
    ]

    # 這裏要注意dependencies爲上一次migrations的文件名稱

    operations = [
        migrations.RunPython(init_book_data)
    ]複製代碼
  1. 運行python manage.py migrate,能夠看到數據已經在數據庫中生成了。

利用migrations修復數據

咱們經常遇到這種狀況,例如我須要給Book模型增長一個外鍵字段,並且這個字段不能爲空,因此舊的數據就要進行處理修復,咱們能夠這樣處理。

  1. 先將須要增長的字段null屬性設置爲True,而後執行makemigrations
make_good_use_of_migrations/models.py view raw
class Author(models.Model):
    name = models.CharField(max_length=32)


class Book(models.Model):
    name = models.CharField(max_length=32)
    author = models.ForeignKey(to=Author, on_delete=models.CASCADE, null=False)複製代碼
  1. 在相應app下的migrations文件新建0004_fix_book_data.py migrations/.
    ├── 0001_initial.py.
    ├── 0002_init_book_data.py.
    ├── 0003_auto_20181204_0533.py.
    └── 0004_fix_book_data.py.
make_good_use_of_migrations/migrations/0004_fix_book_data.py view raw
from django.db import migrations


def fix_book_data(apps, schema_editor):
    Book = apps.get_model('make_good_use_of_migrations', 'Book')
    Author = apps.get_model('make_good_use_of_migrations', 'Author')
    for book in Book.objects.all():
        author, _ = Author.objects.get_or_create(name='%s author' % book.name)
        book.author = author
        book.save()


class Migration(migrations.Migration):
    dependencies = [
        ('make_good_use_of_migrations', '0003_auto_20181204_0533'),
    ]

    operations = [
        migrations.RunPython(fix_book_data)
    ]複製代碼
  1. 最後再將Book模型中的author字段屬性null設爲False,並執行makemigrations。執行後會出現,

    You are trying to change the nullable field 'author' on book to non-nullable without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration) 3) Quit, and let me add a default in models.py Select an option: 複製代碼

    這裏選擇第2項,意思是忽略該字段已經爲空的數據,使用RunPython或者RunSQL自行處理。

    選擇完成後在執行python manage.py migrate,會發現數據庫中的數據會按照咱們的預期處理完成。

解決多人開發時migrations產生的衝突

爲了模擬多人多分支開發,新建一個master-2的分支,而且版本在建立Author類以前,而且在Book模型中增長remark字段。

model.py文件中的內容以下:

make_good_use_of_migrations/models.py view raw
class Book(models.Model):
    name = models.CharField(max_length=32)
    remark = models.CharField(max_length=32, null=True)複製代碼

migrations文件目錄以下:

migrations/.
├── 0001_initial.py.
├── 0002_init_book_data.py.
└──0003_book_remark.py.

當咱們把master-2的代碼合併到master時,會發現migrations中出現了重複的編號0003而且他們共同依賴於0002_init_book_data

migrations/.
├── 0001_initial.py.
├── 0002_init_book_data.py.
├── 0003_auto_20181204_0533.py
├── 0003_book_remark.py
├── 0004_fix_book_data.py
└──0005_auto_20181204_0610.py.

這時候就須要用到命令:

python manage.py makemigrations --merge
複製代碼

而後就會在migrations目錄生成一個0006_merge_20181204_0622.py文件

make_good_use_of_migrations/migrations/0006_merge_20181204_0622.py view raw
from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('make_good_use_of_migrations', '0005_auto_20181204_0610'),
        ('make_good_use_of_migrations', '0003_book_remark'),
    ]

    operations = [
    ]複製代碼

這時候在執行python manage.py migrate就能夠了。

使用migrations.RunPython須要注意的問題

在函數中是沒法調用模型類的函數的

假設在Book模型中定義了兩個函數print_name和類函數print_class_name

make_good_use_of_migrations/models.py view raw
class Book(models.Model):
    name = models.CharField(max_length=32)
    author = models.ForeignKey(to=Author, on_delete=models.CASCADE, null=False)
    remark = models.CharField(max_length=32, null=True)

    def print_name(self):
        print(self.name)

    @classmethod
    def print_class_name(cls):
        print(cls.__name__)複製代碼

migrations中是沒法調用的,筆者也沒有仔細研究,推測是Book類初始化時只把字段初始化了。

make_good_use_of_migrations/migrations/0004_fix_book_data.py view raw
from django.db import migrations


def fix_book_data(apps, schema_editor):
    Book = apps.get_model('make_good_use_of_migrations', 'Book')
    Author = apps.get_model('make_good_use_of_migrations', 'Author')
    for book in Book.objects.all():
        author, _ = Author.objects.get_or_create(name='%s author' % book.name)
        book.author = author
        """ book.print_name() book.print_class_name() 這樣調用會報錯 """
        book.save()


class Migration(migrations.Migration):
    dependencies = [
        ('make_good_use_of_migrations', '0003_auto_20181204_0533'),
    ]

    operations = [
        migrations.RunPython(fix_book_data)
    ]複製代碼

在函數中模型的類所重寫的save方法無效,包括繼承的save方法

migrations中全部重寫的save方法都不會運行,例如:

make_good_use_of_migrations/models.py view raw
class Book(models.Model):
    name = models.CharField(max_length=32)
    author = models.ForeignKey(to=Author, on_delete=models.CASCADE, null=False)
    remark = models.CharField(max_length=32, null=True)

    def print_name(self):
        print(self.name)

    @classmethod
    def print_class_name(cls):
        print(cls.__name__)

    def save(self, *args, **kwargs):
        if not self.remark:
            self.remark = 'This is a book.'複製代碼

最後初始化生成的數據的remark字段的值仍然爲空。

在函數中模型註冊的全部signal無效

雖然給Book模型註冊了signal,可是在migrations中仍然不會起做用

make_good_use_of_migrations/models.py view raw
@receiver(pre_save, sender=Book)
def generate_book_remark(sender, instance, *args, **kwargs):
    print(instance)
    if not instance.remark:
        instance.remark = 'This is a book.'複製代碼

不要將數據處理放到模型變動的migrations文件中

在作數據修復或者生成初始化數據時,不要將處理函數放到自動生成的變動或生成字段、模型的migrations文件中,例如:

make_good_use_of_migrations/migrations/0005_auto_20181204_0610.py view raw
class Migration(migrations.Migration):
    dependencies = [
        ('make_good_use_of_migrations', '0004_fix_book_data'),
    ]

    operations = [
        migrations.AlterField(
            model_name='book',
            name='author',
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
                                    to='make_good_use_of_migrations.Author'),
        ),
        """ migrations.RunPython(xxx) 不要把數據處理放到模型變動中 """
    ]複製代碼

不要放在一塊兒的主要緣由是,當RunPython中函數的處理邏輯一旦出現異常沒法向下執行,

django_migrations將不會記錄這一次處理,可是表結構的變動已經執行了!

這也是Django migrations作的很差的地方,正確應該是出現異常須要作數據庫回滾。

一旦出現這種狀況,只能手動將migrations的名稱如0005_auto_20181204_0610,寫入到數據庫表django_migrations中,而後將RunPython中的邏輯單獨剝離出來。

總結

以上就是筆者在項目中使用Django框架的migrations的心得,下一篇會介紹Django框架的TestCase

本文的源碼會放到github上,github.com/elfgzp/djan…

本人博客原文地址:elfgzp.cn/2018/12/04/…

相關文章
相關標籤/搜索