Django提供了一个名为makemigrations
的非常好的功能,它将根据模型的变化创建迁移文件。我们正在开发一个模块,我们希望生成自定义迁移。
我没有找到有关在Django文档中创建自定义迁移的更多信息。有关于可以产生迁移的各种Operation
类的文档,但是没有关于创建可以产生自定义迁移的自定义Operation
类的文档。
用于生成新迁移的autodetector
模块似乎也没有留下太多空间来添加自定义Operation
类:https://github.com/django/django/blob/master/django/db/migrations/autodetector.py#L160
这似乎完全是静态的。是否有其他方法可以生成自定义迁移,可能是通过使用具有自定义管理命令的现有类?
答案 0 :(得分:0)
您可以创建一个自定义类以连接到makemigrations类并添加您的自定义迁移内容,然后使用“ runscript”命令执行。以下是一个示例模块,其中文件名为custom_migrations.py,位于其中一个应用程序的“脚本”文件夹中:
from django.core.management.commands.makemigrations import Command
"""
To invoke this script use:
manage.py runscript custom_migrations --script-args [app_label [app_label ...]] name=my_special_migration verbosity=1
"""
class MyMigrationMaker(Command):
'''
Override the write method to add more stuff before finishing
'''
def write_migration_files(self, changes):
print("Do some stuff to \"changes\" object here...")
super().write_migration_files(changes)
def run(*args):
nargs = []
kwargs = {}
# Preload some options with defaults and then can be overridden in the args parsing
kwargs['empty'] = True
kwargs['verbosity'] = 1
kwargs['interactive'] = True
kwargs['dry_run'] = False
kwargs['merge'] = False
kwargs['name'] = 'custom_migration'
kwargs['check_changes'] = False
for arg in args:
kwarg = arg.split('=', 1)
if len(kwarg) > 1:
val = kwarg[1]
if val == "True":
arg_val = True
elif val == "False":
arg_val = False
elif val.isdigits():
arg_val = int(val)
else:
arg_val = val
the_kwargs[kwarg[0]] = arg_val
else:
nargs.append(arg)
MyMigrationMaker().handle(*nargs, **kwargs)
答案 1 :(得分:0)
如果您不愿意使用Django内部组件进行探戈,则可以使用以下脚本通过调用随机方法来生成迁移文件,该方法将生成要在迁移中运行的Python代码并将其插入有效的代码中迁移文件,它将成为标准Django迁移的一部分。 如果您有一个名为“ xxx”的应用,并且在文件xxx / scripts / test.py中有一个方法,如下所示:
def run(*args, **kwargs):
return "print(\"BINGO!!!!!!!!! {} :: {}\".format(args[0], kwargs['name']))"
...,您使用以下命令调用了存储在xxx / scripts / custom_migrations.py中的此帖子底部显示的脚本
manage.py runscript custom_migrations --script-args xxx name=say_bingo callable=xxx.scripts.test.run
然后,您将得到一个xxx / migrations中的迁移文件,并带有适当的数字序列(类似于0004_say_bingo.py之类):
Generated by Django 2.1.2 on 2018-12-14 08:54
from django.db import migrations
def run(*args, **kwargs):
print("BINGO!!!!!!!!! {} :: {}".format(args[0], kwargs['name']))
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.RunPython(run,)
]
脚本如下:
from django.core.management.base import no_translations
from django.core.management.commands.makemigrations import Command
from django.db.migrations import writer
from django import get_version
from django.utils.timezone import now
import os
import sys
"""
To invoke this script use:
manage.py runscript custom_migrations --script-args [app_label [app_label ...]] callable=my_migration_code_gen name=my_special_migration
-- the "name" argument will be set as part of the migration file generated
-- the "callable" argument will be the function that is invoked to generate the python code you want to execute in the migration
the callable will be passed all the args and kwargs passed in to this script from the command line as part of --script-args
Only app names are allowed in args so use kwargs for custom arguments
"""
class LazyCallable(object):
def __init__(self, name):
self.n, self.f = name, None
def __call__(self, *a, **k):
if self.f is None:
modn, funcn = self.n.rsplit('.', 1)
if modn not in sys.modules:
__import__(modn)
self.f = getattr(sys.modules[modn], funcn)
return self.f(*a, **k)
class MyMigrationMaker(Command):
'''
Override the write method to provide access to script arguments
'''
@no_translations
def handle(self, *app_labels, **options):
self.in_args = app_labels
self.in_kwargs = options
super().handle(*app_labels, **options)
'''
Override the write method to add more stuff before finishing
'''
def write_migration_files(self, changes):
code = LazyCallable(self.in_kwargs['callable'])(self.in_args, self.in_kwargs)
items = {
"replaces_str": "",
"initial_str": "",
}
items.update(
version=get_version(),
timestamp=now().strftime("%Y-%m-%d %H:%M"),
)
items["imports"] = "from django.db import migrations\n\ndef run(*args, **kwargs):\n "
items["imports"] += code.replace("\n", "\n ") + "\n\n"
items["operations"] = " migrations.RunPython(run,)\n"
directory_created = {}
for app_label, app_migrations in changes.items():
for migration in app_migrations:
# Describe the migration
my_writer = writer.MigrationWriter(migration)
dependencies = []
for dependency in my_writer.migration.dependencies:
dependencies.append(" %s," % my_writer.serialize(dependency)[0])
items["dependencies"] = "\n".join(dependencies) + "\n" if dependencies else ""
# Write the migrations file to the disk.
migrations_directory = os.path.dirname(my_writer.path)
if not directory_created.get(app_label):
if not os.path.isdir(migrations_directory):
os.mkdir(migrations_directory)
init_path = os.path.join(migrations_directory, "__init__.py")
if not os.path.isfile(init_path):
open(init_path, "w").close()
# We just do this once per app
directory_created[app_label] = True
migration_string = writer.MIGRATION_TEMPLATE % items
with open(my_writer.path, "w", encoding='utf-8') as fh:
fh.write(migration_string)
if self.verbosity >= 1:
self.stdout.write("Migration file: %s\n" % my_writer.filename)
def run(*args):
glob_args = []
glob_kwargs = {}
# Preload some options with defaults and then can be overridden in the args parsing
glob_kwargs['empty'] = True
glob_kwargs['verbosity'] = 1
glob_kwargs['interactive'] = True
glob_kwargs['dry_run'] = False
glob_kwargs['merge'] = False
glob_kwargs['name'] = 'custom_migration'
glob_kwargs['check_changes'] = False
for arg in args:
kwarg = arg.split('=', 1)
if len(kwarg) > 1:
glob_kwargs[kwarg[0]] = kwarg[1]
else:
glob_args.append(arg)
MyMigrationMaker().handle(*glob_args, **glob_kwargs)