简体   繁体   English

从多表 inheritance model 迁移到 Django 中的抽象基类

[英]Migrate from multi-table inheritance model to abstract base classes in Django

My current project uses multi-table inheritance models :我当前的项目使用多表 inheritance 型号

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

class Cinema(Place):
    sells_tickets = models.BooleanField(default=False)
    sells_popcorn = models.BooleanField(default=False)

I want to switch to abstract base classes instead.我想改用抽象基类 Since my model is already deployed I need to write some custom migrations to convert the above schema to this one:由于我的 model 已经部署,我需要编写一些自定义迁移来将上述模式转换为这个模式:

from django.db import models

class AbstractPlace(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

    class Meta:
        abstract = True

class Restaurant(AbstractPlace):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

class Cinema(AbstractPlace):
    sells_tickets = models.BooleanField(default=False)
    sells_popcorn = models.BooleanField(default=False)

Does anyone have any advice on the steps to take to achieve this?有人对实现这一目标的步骤有任何建议吗?

I recently tackled this exact problem, which I solved by writing and running the migration in the code block below - loosely translated to fit the models in your case.我最近解决了这个确切的问题,我通过在下面的代码块中编写和运行迁移解决了这个问题 - 松散地翻译以适合您的案例。

I'm pretty sure that it's not possible to alter the tables of the old Restaurant and Cinema models directly, as if you try to add fields to them, they will collide with the existing fields of the base model, and if you try to "decouple" the derived models from the base model by eg by manually setting abstract=True in the base model's options , Django reports that it's unable to find the base models of Restaurant and Cinema .我很确定直接更改旧的RestaurantCinema模型的表是不可能的,就像您尝试向它们添加字段一样,它们将与基础 model 的现有字段发生冲突,如果您尝试“将派生模型与基础 model 解耦,例如通过在基础模型的options中手动设置abstract=True , Django 报告它无法找到RestaurantCinema的基础模型。 (These issues might be caused by a bug, for all I know.) To circumvent this problem, I created new tables for the derived models, copied the data from the old tables to the new ones, deleted the old tables, and renamed the new tables to match the names of the old ones. (据我所知,这些问题可能是由错误引起的。)为了避免这个问题,我为派生模型创建了新表,将旧表中的数据复制到新表中,删除了旧表,并重命名了新表以匹配旧表的名称。

I got large parts of the code below from code generated by Django, which can be reproduced by creating a temporary migration (before creating one with the code below) which only deletes Restaurant , Cinema and Place , running makemigrations , and copying the CreateModel() s and AlterField() s (for related fields pointing to Restaurant or Cinema ) from the generated migration.我从 Django 生成的代码中获得了下面的大部分代码,可以通过创建一个临时迁移(在使用下面的代码创建一个之前)复制它,它只删除RestaurantCinemaPlace ,运行makemigrations并复制CreateModel() s 和AlterField() s(用于指向RestaurantCinema的相关字段)来自生成的迁移。

For the record, I'm using Django 3.1.4.作为记录,我使用的是 Django 3.1.4。

from django.db import migrations, models


def copy_objects_from_restaurant_and_cinema_to_restaurant_tmp_and_cinema_tmp(apps, schema_editor):
    Restaurant_Tmp = apps.get_model('<app name>', 'Restaurant_Tmp')
    Cinema_Tmp = apps.get_model('<app name>', 'Cinema_Tmp')
    Restaurant = apps.get_model('<app name>', 'Restaurant')
    Cinema = apps.get_model('<app name>', 'Cinema')
    # The `_meta.fields` list includes the PK
    copy_objects_from_old_model_to_new_model(Restaurant, Restaurant_Tmp, Restaurant_Tmp._meta.fields)
    copy_objects_from_old_model_to_new_model(Cinema, Cinema_Tmp, Cinema_Tmp._meta.fields)


def copy_objects_from_old_model_to_new_model(old_model, new_model, fields_to_copy):
    field_names = [field.name for field in fields_to_copy]
    for old_obj in old_model.objects.all():
        old_obj_field_dict = {
            field_name: getattr(old_obj, field_name)
            for field_name in field_names
        }
        new_model.objects.create(**old_obj_field_dict)


def copy_objects_from_restaurant_tmp_and_cinema_tmp_to_restaurant_and_cinema(apps, schema_editor):
    Restaurant_Tmp = apps.get_model('<app name>', 'Restaurant_Tmp')
    Cinema_Tmp = apps.get_model('<app name>', 'Cinema_Tmp')
    Restaurant = apps.get_model('<app name>', 'Restaurant')
    Cinema = apps.get_model('<app name>', 'Cinema')
    copy_objects_from_old_model_to_new_model(Restaurant_Tmp, Restaurant, Restaurant_Tmp._meta.fields)
    copy_objects_from_old_model_to_new_model(Cinema_Tmp, Cinema, Cinema_Tmp._meta.fields)


class Migration(migrations.Migration):

    dependencies = [
        ('<app name>', '<last migration>'),
    ]

    operations = [
        migrations.CreateModel(
            name='Restaurant_Tmp',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=50)),
                ('address', models.CharField(max_length=80)),
                ('serves_hot_dogs', models.BooleanField(default=False)),
                ('serves_pizza', models.BooleanField(default=False)),
            ],
            options={
                'abstract': False,
            },
        ),
        migrations.CreateModel(
            name='Cinema_Tmp',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=50)),
                ('address', models.CharField(max_length=80)),
                ('sells_tickets', models.BooleanField(default=False)),
                ('sells_popcorn', models.BooleanField(default=False)),
            ],
            options={
                'abstract': False,
            },
        ),
        migrations.RunPython(copy_objects_from_restaurant_and_cinema_to_restaurant_tmp_and_cinema_tmp, migrations.RunPython.noop),

        # Update foreign keys to reference the non-abstract models directly,
        # instead of through the (automatically generated) `place_ptr` field of the old models
        < 
          Run `migrations.AlterField()` here for each related field (like ForeignKey) of other models that point to Restaurant or Cinema,
          but change their `to` argument from e.g. `<app name>.restaurant` to `<app name>.restaurant_tmp`
        >
        migrations.RunPython(migrations.RunPython.noop, copy_objects_from_restaurant_tmp_and_cinema_tmp_to_restaurant_and_cinema),

        migrations.DeleteModel(
            name='Restaurant',
        ),
        migrations.DeleteModel(
            name='Cinema',
        ),
        migrations.DeleteModel(
            name='Place',
        ),
        migrations.RenameModel(
            old_name='Restaurant_Tmp',
            new_name='Restaurant',
        ),
        migrations.RenameModel(
            old_name='Cinema_Tmp',
            new_name='Cinema',
        ),
    ]

Note that the migration I originally wrote was only tested to work using SQLite;请注意,我最初编写的迁移仅使用 SQLite 进行了测试; other database management systems might not accept such a large variety of migration operations, and you might have to split it into multiple migrations.其他数据库管理系统可能不接受如此大量的迁移操作,您可能必须将其拆分为多个迁移。 (I'm somewhat unsure what exactly could cause this problem, but I can recall that I've experienced it with PostgreSQL.) (我有点不确定究竟是什么导致了这个问题,但我记得我曾在 PostgreSQL 上经历过。)

Please let me know if this solves your problem!如果这能解决您的问题,请告诉我!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM