在使用和學習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
的一些使用心得和遇到的一些問題。
咱們如今有一個Book
的模型,我想在migrate
以後初始化一些數據。
class Book(models.Model):
name = models.CharField(max_length=32)複製代碼
例如:生成三本名稱分別爲Hamlet
、Tempest
、The Little Prince
的書。
在執行了python manage.py makemigrations
以後migrations
文件夾會生成0001_initial.py
的文件。
文件中包含了Book
這個模型初始化的一些代碼。
在介紹如何利用migrations
初始化數據時,先介紹一下migrations
經常使用的兩個操做:
RunSQL
、RunPython
顧名思義分別是執行SQL語句
和Python函數
。
下面我用migrations
中的RunPython
來初始化數據。
在相應app下的migrations
文件新建0002_init_book_data.py
migrations/.
├── 0001_initial.py.
└── 0002_init_book_data.py.
而後增長Migration
類繼承django.db.migrations.Migration
,並在operations
中增長鬚要執行的代碼。
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)
]複製代碼
python manage.py migrate
,能夠看到數據已經在數據庫中生成了。咱們經常遇到這種狀況,例如我須要給Book
模型增長一個外鍵字段,並且這個字段不能爲空,因此舊的數據就要進行處理修復,咱們能夠這樣處理。
null
屬性設置爲True
,而後執行makemigrations
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)複製代碼
migrations
文件新建0004_fix_book_data.py
migrations/. 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)
]複製代碼
最後再將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
,會發現數據庫中的數據會按照咱們的預期處理完成。
爲了模擬多人多分支開發,新建一個master-2
的分支,而且版本在建立Author
類以前,而且在Book
模型中增長remark
字段。
model.py
文件中的內容以下:
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
文件
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
就能夠了。
假設在Book
模型中定義了兩個函數print_name
和類函數print_class_name
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
類初始化時只把字段初始化了。
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)
]複製代碼
在migrations
中全部重寫的save
方法都不會運行,例如:
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
字段的值仍然爲空。
雖然給Book
模型註冊了signal
,可是在migrations
中仍然不會起做用
@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
文件中,例如:
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/…