如何使用自定义管理第三方应用迁移 WAGTAILIMAGES_IMAGE_MODEL

How to manage third party app migrations with custom WAGTAILIMAGES_IMAGE_MODEL

情况

我有一个自定义图像和再现模型,并已按照 the wagtail v2.4 guide 实施它们:

class AccreditedImage(AbstractImage):
    """
    AccreditedImage - Customised image model with optional caption and accreditation
    """
    caption = models.CharField(max_length=255, blank=True)
    accreditation = models.CharField(max_length=255, blank=True, null=True)
    admin_form_fields = Image.admin_form_fields + (
        'caption',
        'accreditation',
    )

    class Meta:
        verbose_name = 'Accredited image'
        verbose_name_plural = 'Accredited images'

    def __str__(self):
        credit = ' ({})'.format(self.accreditation) if (self.accreditation is not None) and (len(self.accreditation) > 0) else ''
        return '{}{}'.format(self.title, credit)


class AccreditedRendition(AbstractRendition):
    """
    AccreditedRendition - stores renditions for the AccreditedImage model
    """
    image = models.ForeignKey(AccreditedImage, on_delete=models.CASCADE, related_name='renditions')

    class Meta:
        unique_together = (('image', 'filter_spec', 'focal_point_key'),)
        verbose_name = 'Accredited Image Rendition'
        verbose_name_plural = 'Accredited Image Renditions'

settings 我有:

WAGTAILIMAGES_IMAGE_MODEL = 'cms.AccreditedImage'

但是,我安装了两个第三方插件:puput and wagtail_events,每个都使用外键来生成 wagtail 图像。

当我 运行 `manage.py makemigrations 时,会在 puput 和 wagtail_events site_packages 文件夹中创建额外的迁移以处理 FK 中的更改。迁移看起来像这样:

from django.db import migrations, models
import django.db.models.deletion

class Migration(migrations.Migration):

    dependencies = [
        ('puput', '0005_blogpage_main_color'),
    ]

    operations = [
        migrations.AlterField(
            model_name='blogpage',
            name='header_image',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='cms.AccreditedImage', verbose_name='Header image'),
        ),
        migrations.AlterField(
            model_name='entrypage',
            name='header_image',
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='cms.AccreditedImage', verbose_name='Header image'),
        ),
    ]

问题

如果我实际应用了这些迁移,然后 puput 或 wagtail_events 发布了一个新版本,那么迁移历史就会损坏 - 例如我在 puput 上自动生成的 0006* 迁移及其新的 0006* 迁移叉历史

问题

有没有办法克服这个问题?或者推荐的做法?

此时我处于非常早期的测试阶段,因此如果建议的策略是从一开始就设置它以避免出现问题,我可以转储整个数据库并重新开始。

感谢大家的帮助!

答案 1 - 如果您可以控制第三方库

第三方库中的初始迁移应该定义一个可交换的依赖项,例如: 从 wagtail.images 导入 get_image_model_string

dependencies = [
    migrations.swappable_dependency(get_image_model_string()),
]
operations = [
    migrations.CreateModel(
        name='ThirdPartyModel',
        fields=[
            ...
            ('image', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=get_image_model_string())),
        ],
        ...
    ),

这不是由 makemigrations 自动创建的。 get_image_model_string 在影响该 FK 的每个迁移中都需要像这样使用,通过库的整个迁移历史进行。

如果您在项目中的某个时刻更改了设置,您仍然需要进行数据迁移('Migrate an existing swappable dependency' 可能会有所帮助),但这解决了上述如果开始清理时出现的分叉问题。

缺点是需要控制第三方库。对于像 puput 这样的项目,我不会屏住呼吸返回并更改其早期迁移历史以允许可交换图像模型(puput 的初始迁移硬编码 wagtailimages.Image)。但是我已经为 wagtail_events(我自己的项目)实现了这个,以节省其他人的麻烦。

答案 2 - 如果您没有控制权

呃。我已经为此工作了一段时间,所有候选解决方案都非常糟糕。我考虑过让我的自定义图像 class 通过 db_table 元属性模拟 wagtail.images.model.Image,甚至通过创建另一个基本上复制鹡鸰图像的应用程序。这要么是大量的工作,要么是超级 hacky。

我已选择使用 MIGRATION_MODULES 设置手动接管迁移。

对于我的场景,我获取了 puput 的整个迁移历史并将所有文件复制到一个单独的文件夹中,root/custom_puput_migrations/。我设置

MIGRATION_MODULES = {
    'puput': 'custom_puput_migrations'
}
WAGTAILIMAGES_IMAGE_MODEL = 'otherapp.AccreditedImage'

然后我通过编辑该文件夹中的 0001_initial.py 来拉动 ol' switcharoo,以通过设置引用模型,而不是通过硬编码:

...
from wagtail.images import get_image_model_string

class Migration(migrations.Migration):

    dependencies = [
        ...
        migrations.swappable_dependency(get_image_model_string())
    ]

    operations = [
        migrations.CreateModel(
            name='BlogPage',
            fields=[
                ...
                ('header_image', models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.SET_NULL, verbose_name='Header image', blank=True, to=get_image_model_string(), null=True)),
            ],
     ...

缺点

1) 实际创建的table关系并不严格由迁移文件决定,而是由设置决定,可以独立改变。如果您希望避免这种情况,您可以在自定义迁移中简单地硬编码您引用的模型。

2) 这种方法让您很容易受到开发人员升级库版本要求的攻击,而没有意识到他们还必须手动复制迁移文件。我建议在允许应用程序启动之前进行检查(f/ex 确保默认迁移文件夹中的文件数量与自定义文件夹中的文件数量相同),以确保您的开发和生产数据库所有 运行 在同一组迁移上。