我很好奇其他django开发人员是如何通过迁移管理多个代码分支(例如在git中)的。
我的问题如下:我们在git中有多个特性分支,其中一些是django迁移的(其中一些是修改字段,或者完全删除字段)-当我切换分支(与git checkout some_other_branch
)数据库并不总是反映新的代码,所以我遇到"随机"错误,其中一个db表列不存在了,等等…
现在,我只是删除数据库并重新创建它,但这意味着我必须重新创建一堆虚拟数据来重新启动工作。我可以使用fixture,但是它需要跟踪哪些数据去了哪里,这有点麻烦。
是否有一个好的/干净的方法来处理这个用例?我认为post-checkout
git钩子脚本可以运行必要的迁移,但我甚至不知道是否迁移回滚是可能的。
迁移回滚是可能的,通常由django自动处理。
考虑以下模型:
class MyModel(models.Model):
pass
如果执行python manage.py makemigrations myapp
,将生成初始迁移脚本。然后可以运行python manage.py migrate myapp 0001
来应用这个初始迁移。
如果之后你添加一个字段到你的模型:
class MyModel(models.Model):
my_field = models.CharField()
然后重新生成一个新的迁移,并应用它,您仍然可以回到初始状态。你就跑python manage.py migrate myapp 0001
和ORM将向后移动,删除新字段。
处理数据迁移时更棘手,因为必须编写向前和向后代码。考虑通过python manage.py makemigrations myapp --empty
创建的空迁移,你会得到这样的结果:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def forward(apps, schema_editor):
# load some data
MyModel = apps.get_model('myapp', 'MyModel')
while condition:
instance = MyModel()
instance.save()
def backward(apps, schema_editor):
# delete previously loaded data
MyModel = apps.get_model('myapp', 'MyModel')
while condition:
instance = MyModel.objects.get(myargs)
instance.delete()
class Migration(migrations.Migration):
dependencies = [
('myapp', '0003_auto_20150918_1153'),
]
operations = [
migrations.RunPython(forward, backward),
]
对于纯数据加载迁移,通常不需要向后迁移。但是,当您更改模式并更新现有行的时候(就像将列中的所有值转换为段符一样),您通常必须编写反向步骤。
在我们的团队中,我们尽量避免在同一时间处理相同的模型,以避免冲突。如果不可能,并且创建了两个具有相同编号(例如0002)的迁移,您仍然可以重命名其中的一个以更改应用它们的顺序(还记得更新)将迁移类的dependencies
属性设置为您的新订单)。
如果你最终在不同的特征中同时处理相同的模型字段,你仍然会遇到麻烦,但这可能意味着这些特性是相关的,应该加以处理在一个分支中。
对于git-hooks部分,可能可以写一些东西,假设您在分支mybranch
上并想检查另一个功能分支myfeature
:
- 在切换之前,将当前应用的迁移列表转储到临时文件
mybranch_database_state.txt
然后,应用
myfeature
分支迁移,如果有的话然后,当检查回mybranch
时,您重新应用先前的数据库状态查看转储文件然而,这对我来说似乎有点粗俗,并且可能很难正确处理所有场景:重基、合并、挑选等
在迁移冲突发生时处理它们对我来说似乎更容易。
我没有一个好的解决方案,但我感到痛苦。
结帐后的钩子太迟了。如果您在分支A上,并且签出分支B,并且B的迁移比A少,则回滚信息仅在A中,并且需要在签出之前运行。
我在几个提交之间跳转试图找到一个bug的起源时遇到了这个问题。我们的数据库(甚至在开发阶段)是巨大的,所以删除和重新创建是不现实的。
我正在想象一个用于git-checkout的包装器:
- 注意您的INSTALLED_APPS的最新迁移
- 查看所请求的分支并在那里记录最新的迁移
- 对于#1中的迁移比#2中的迁移更远的每个应用,迁移回#2中最高的迁移
- 查看新的分支
- 对于每个在#2中迁移领先于#1的应用程序,向前迁移
一个简单的编程问题!
到目前为止,我已经找到了两个Github项目(django-south-compass
和django_nomad
),试图解决开发分支之间迁移的问题,Stack Overflow上有几个答案。
引用Medium上的一篇文章,大多数解决方案可以归结为以下概念之一:
- 删除所有的表,并从头开始在目标分支中重新应用迁移。当从头创建表时,所有数据都将丢失,并且还需要重新创建。这可以通过fixture和数据迁移来处理,但反过来管理它们将成为一场噩梦,更不用说它将花费一些时间(…)
- 为每个分支创建一个单独的数据库,并在每次使用sed等工具切换分支时使用目标分支的设置更改设置文件。这可以用
post_checkout
钩子来完成。为每个分支维护一个大型数据库将非常占用存储资源。此外,检出单个提交id可能会产生相同的错误。 - 查找源分支和目标分支之间迁移的差异,并应用差异。我们可以这样做与
post_checkout
脚本,但有一个小问题。这篇文章详细解释了这个问题。总而言之,post_checkout
是在签出目标分支中的所有文件(包括迁移文件)之后运行的。当我们运行python manage.py migrate app1
时,如果目标分支不包含源分支中的所有迁移,Django将无法找到应用反向迁移所需的缺失迁移。我们必须暂时签出源分支中的迁移文件,运行python manage.py migrate
并签出目标分支中的迁移文件。django-south-compass
做了非常类似的事情,但只适用于python 2.6。 - 使用管理命令(使用python git模块),查找源分支和源分支和目标分支的merge-base之间的所有迁移操作差异,并通知用户这些更改。如果这些更改不影响分支更改的原因,则用户可以继续更改分支。否则,使用另一个管理命令,取消应用所有迁移,直到合并基础,切换分支,并在目标分支中应用迁移。会有小的数据丢失,如果两个分支没有很大的分歧,是可以管理的。django_nomad会做一些这样的工作。
- 在文件中跟踪已应用和未应用的迁移,并在切换分支时使用此数据填充表。
对于简单的更改,我依赖于迁移回滚,正如Agate所讨论的。
然而,如果我知道一个特性分支将涉及高度侵入性的数据库更改,或者如果它将涉及大量数据迁移,我喜欢在启动新分支时立即创建本地(或远程开发)数据库的克隆。这可能并不总是很方便,但特别是对于使用sqlite的本地开发,它只是复制一个文件(不受源代码控制)的问题。
在新分支上的第一次提交会更新我的Django设置(local/dev)以使用克隆的数据库。这样,当我切换分支时,就会自动选择正确的数据库。无需担心回滚模式更改、丢失数据等问题。没有复杂的东西。
特性分支完全合并后,克隆数据库可以被移除