在Djang中自动生成自定义迁移

2024-10-04 05:20:51 发布

您现在位置:Python中文网/ 问答频道 /正文

Django提供了一个非常好的特性,称为makemigrations,它将根据模型中的更改创建迁移文件。我们正在开发一个模块,我们希望在其中生成自定义迁移。在

我在Django文档中没有找到很多关于创建自定义迁移的信息。有关于各种可以生成迁移的Operation类的文档,但是没有关于创建可以生成自定义迁移的Operation类的内容。在

用于生成新迁移的autodetector模块似乎也没有为添加定制的Operation类留下太多空间:https://github.com/django/django/blob/master/django/db/migrations/autodetector.py#L160

看来这是完全静止的。是否有其他方法生成自定义迁移,可能是通过使用带有自定义管理命令的现有类?在


Tags: 模块文件django文档https模型github信息
2条回答

如果您不愿意使用Django内部构件,另一种方法是使用下面的脚本通过调用一个随机方法生成一个迁移文件,该方法将生成您希望在迁移中运行的Python代码,并将其插入到一个有效的迁移文件中,该文件将成为标准Django迁移的一部分。 你有一个名为“xxx”的应用程序,你在一个文件xxx/scripts中有一个方法/测试.py看起来像这样:

def run(*args, **kwargs):
return "print(\"BINGO!!!!!!!!! {} :: {}\".format(args[0], kwargs['name']))"

。。。您调用了存储在xxx/scripts/custom中的本文底部显示的脚本_迁移.py使用以下命令

^{pr2}$

然后在xxx/migrations中得到一个迁移文件,该文件具有适当的编号顺序(比如0004_宾果)看起来像这样:

 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)

您可以创建一个自定义类来钩住makemigrations类,然后添加您的自定义迁移内容,然后使用“runscript”命令执行。下面是一个示例模块,其中文件名为custom_迁移.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)

相关问题 更多 >