在使用和学习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/…