Python 如何在两个 Django 应用程序之间移动模型(Django 1.7)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25648393/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 23:25:55  来源:igfitidea点击:

How to move a model between two Django apps (Django 1.7)

pythonmysqldatabasedjangoschema-migration

提问by Sam Buckingham

So about a year ago I started a project and like all new developers I didn't really focus too much on the structure, however now I am further along with Django it has started to appear that my project layout mainly my models are horrible in structure.

所以大约一年前,我开始了一个项目,和所有新开发人员一样,我并没有过多关注结构,但是现在我和 Django 一起走得更远,它开始出现我的项目布局主要是我的模型在结构上很糟糕.

I have models mainly held in a single app and really most of these models should be in their own individual apps, I did try and resolve this and move them with south however I found it tricky and really difficult due to foreign keys ect.

我的模型主要保存在一个应用程序中,并且这些模型中的大多数应该在它们自己的单独应用程序中,我确实尝试解决了这个问题并将它们向南移动,但是由于外键等,我发现这很棘手且非常困难。

However due to Django 1.7 and built in support for migrations is there a better way to do this now?

但是,由于 Django 1.7 和内置的迁移支持,现在有更好的方法吗?

采纳答案by ChillarAnand

I am removing the old answer as may result in data loss. As ozan mentioned, we can create 2 migrations one in each app. The comments below this post refer to my old answer.

我正在删除旧答案,因为可能会导致数据丢失。正如ozan 提到的,我们可以在每个应用程序中创建 2 个迁移一个。这篇文章下面的评论是指我的旧答案。

First migration to remove model from 1st app.

从第一个应用程序中删除模型的第一次迁移。

$ python manage.py makemigrations old_app --empty

Edit migration file to include these operations.

编辑迁移文件以包含这些操作。

class Migration(migrations.Migration):

    database_operations = [migrations.AlterModelTable('TheModel', 'newapp_themodel')]

    state_operations = [migrations.DeleteModel('TheModel')]

    operations = [
      migrations.SeparateDatabaseAndState(
        database_operations=database_operations,
        state_operations=state_operations)
    ]

Second migration which depends on first migration and create the new table in 2nd app. After moving model code to 2nd app

第二次迁移取决于第一次迁移并在第二个应用程序中创建新表。将模型代码移至第二个应用程序后

$ python manage.py makemigrations new_app 

and edit migration file to something like this.

并将迁移文件编辑为这样的内容。

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]

回答by Sergey Fedoseev

This is tested roughly, so do not forget to backup your DB!!!

这是粗略的测试,所以不要忘记备份你的数据库!!!

For example, there are two apps: src_appand dst_app, we want to move model MoveMefrom src_appto dst_app.

例如,有两个应用程序:src_appdst_app,我们要将模型MoveMe从移动src_appdst_app

Create empty migrations for both apps:

为两个应用程序创建空迁移:

python manage.py makemigrations --empty src_app
python manage.py makemigrations --empty dst_app

Let's assume, that new migrations are XXX1_src_app_newand XXX1_dst_app_new, previuos top migrations are XXX0_src_app_oldand XXX0_dst_app_old.

让我们假设,新迁移是XXX1_src_app_newand XXX1_dst_app_new,之前的顶级迁移是XXX0_src_app_oldand XXX0_dst_app_old

Add an operation that renames table for MoveMemodel and renames its app_label in ProjectState to XXX1_dst_app_new. Do not forget to add dependency on XXX0_src_app_oldmigration. The resulting XXX1_dst_app_newmigration is:

添加一个重命名MoveMe模型表的操作,并将其在 ProjectState 中的 app_label 重命名为XXX1_dst_app_new. 不要忘记添加对XXX0_src_app_old迁移的依赖。由此产生的XXX1_dst_app_new迁移是:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

# this operations is almost the same as RenameModel
# https://github.com/django/django/blob/1.7/django/db/migrations/operations/models.py#L104
class MoveModelFromOtherApp(migrations.operations.base.Operation):

    def __init__(self, name, old_app_label):
        self.name = name
        self.old_app_label = old_app_label

    def state_forwards(self, app_label, state):

        # Get all of the related objects we need to repoint
        apps = state.render(skip_cache=True)
        model = apps.get_model(self.old_app_label, self.name)
        related_objects = model._meta.get_all_related_objects()
        related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
        # Rename the model
        state.models[app_label, self.name.lower()] = state.models.pop(
            (self.old_app_label, self.name.lower())
        )
        state.models[app_label, self.name.lower()].app_label = app_label
        for model_state in state.models.values():
            try:
                i = model_state.bases.index("%s.%s" % (self.old_app_label, self.name.lower()))
                model_state.bases = model_state.bases[:i] + ("%s.%s" % (app_label, self.name.lower()),) + model_state.bases[i+1:]
            except ValueError:
                pass
        # Repoint the FKs and M2Ms pointing to us
        for related_object in (related_objects + related_m2m_objects):
            # Use the new related key for self referential related objects.
            if related_object.model == model:
                related_key = (app_label, self.name.lower())
            else:
                related_key = (
                    related_object.model._meta.app_label,
                    related_object.model._meta.object_name.lower(),
                )
            new_fields = []
            for name, field in state.models[related_key].fields:
                if name == related_object.field.name:
                    field = field.clone()
                    field.rel.to = "%s.%s" % (app_label, self.name)
                new_fields.append((name, field))
            state.models[related_key].fields = new_fields

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        old_apps = from_state.render()
        new_apps = to_state.render()
        old_model = old_apps.get_model(self.old_app_label, self.name)
        new_model = new_apps.get_model(app_label, self.name)
        if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
            # Move the main table
            schema_editor.alter_db_table(
                new_model,
                old_model._meta.db_table,
                new_model._meta.db_table,
            )
            # Alter the fields pointing to us
            related_objects = old_model._meta.get_all_related_objects()
            related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
            for related_object in (related_objects + related_m2m_objects):
                if related_object.model == old_model:
                    model = new_model
                    related_key = (app_label, self.name.lower())
                else:
                    model = related_object.model
                    related_key = (
                        related_object.model._meta.app_label,
                        related_object.model._meta.object_name.lower(),
                    )
                to_field = new_apps.get_model(
                    *related_key
                )._meta.get_field_by_name(related_object.field.name)[0]
                schema_editor.alter_field(
                    model,
                    related_object.field,
                    to_field,
                )

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        self.old_app_label, app_label = app_label, self.old_app_label
        self.database_forwards(app_label, schema_editor, from_state, to_state)
        app_label, self.old_app_label = self.old_app_label, app_label

    def describe(self):
        return "Move %s from %s" % (self.name, self.old_app_label)


class Migration(migrations.Migration):

    dependencies = [
       ('dst_app', 'XXX0_dst_app_old'),
       ('src_app', 'XXX0_src_app_old'),
    ]

    operations = [
        MoveModelFromOtherApp('MoveMe', 'src_app'),
    ]

Add dependency on XXX1_dst_app_newto XXX1_src_app_new. XXX1_src_app_newis no-op migration that is needed to make sure that future src_appmigrations will be executed after XXX1_dst_app_new.

添加的依赖XXX1_dst_app_newXXX1_src_app_newXXX1_src_app_new是无操作迁移,需要确保将来的src_app迁移将在XXX1_dst_app_new.

Move MoveMefrom src_app/models.pyto dst_app/models.py. Then run:

MoveMe从移动src_app/models.pydst_app/models.py。然后运行:

python manage.py migrate

That's all!

就这样!

回答by Webthusiast

You can try the following (untested):

您可以尝试以下操作(未经测试):

  1. move the model from src_appto dest_app
  2. migrate dest_app; make sure the schema migration depends on the latest src_appmigration (https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files)
  3. add a data migration to dest_app, that copies all data from src_app
  4. migrate src_app; make sure the schema migration depends on the latest (data) migration of dest_app-- that is: the migration of step 3
  1. 将模型从 移动src_appdest_app
  2. 迁移dest_app;确保架构迁移取决于最新的src_app迁移(https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files
  3. 添加一个数据迁移到dest_app,复制所有数据src_app
  4. 迁移src_app;确保架构迁移依赖于最新的(数据)迁移dest_app——即:第 3 步的迁移

Note that you will be copyingthe whole table, instead of movingit, but that way both apps don't have to touch a table that belongs to the other app, which I think is more important.

请注意,您将复制整个表格,而不是移动它,但这样两个应用程序就不必接触属于另一个应用程序的表格,我认为这更重要。

回答by ozan

This can be done fairly easily using migrations.SeparateDatabaseAndState. Basically, we use a database operation to rename the table concurrently with two state operations to remove the model from one app's history and create it in another's.

使用migrations.SeparateDatabaseAndState. 基本上,我们使用数据库操作与两个状态操作同时重命名表,以从一个应用程序的历史记录中删除模型并在另一个应用程序的历史记录中创建它。

Remove from old app

从旧应用程序中删除

python manage.py makemigrations old_app --empty

In the migration:

在迁移中:

class Migration(migrations.Migration):

    dependencies = []

    database_operations = [
        migrations.AlterModelTable('TheModel', 'newapp_themodel')
    ]

    state_operations = [
        migrations.DeleteModel('TheModel')
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=database_operations,
            state_operations=state_operations)
    ]

Add to new app

添加到新应用

First, copy the model to the new app's model.py, then:

首先,将模型复制到新应用的model.py,然后:

python manage.py makemigrations new_app

This will generate a migration with a naive CreateModeloperation as the sole operation. Wrap that in a SeparateDatabaseAndStateoperation such that we don't try to recreate the table. Also include the prior migration as a dependency:

这将生成一个将简单CreateModel操作作为唯一操作的迁移。将其包装在一个SeparateDatabaseAndState操作中,这样我们就不会尝试重新创建表。还包括先前的迁移作为依赖项:

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]

回答by otranzer

I encountered the same problem. Ozan's answerhelped me a lot but unfortunately was not enough. Indeed I had several ForeignKey linking to the model I wanted to move. After some headache I found the solution so decided to post it to solve people time.

我遇到了同样的问题。 Ozan 的回答对我有很大帮助,但不幸的是还不够。事实上,我有几个 ForeignKey 链接到我想要移动的模型。经过一番头痛,我找到了解决方案,所以决定发布它来解决人们的时间。

You need 2 more steps:

您还需要 2 个步骤:

  1. Before doing anything, change all your ForeignKeylinking to TheModelinto Integerfield. Then run python manage.py makemigrations
  2. After doing Ozan's steps, re-convert your foreign keys: put back ForeignKey(TheModel)instead of IntegerField(). Then make the migrations again (python manage.py makemigrations). You can then migrate and it should work (python manage.py migrate)
  1. 在做任何事情之前,将所有ForeignKey链接更改TheModelIntegerfield. 然后运行python manage.py makemigrations
  2. 完成 Ozan 的步骤后,重新转换您的外键:放回ForeignKey(TheModel)而不是IntegerField(). 然后再次进行迁移 ( python manage.py makemigrations)。然后您可以迁移,它应该可以工作 ( python manage.py migrate)

Hope it helps. Of course test it in local before trying in production to avoid bad suprises :)

希望能帮助到你。当然,在尝试生产之前先在本地进行测试,以避免出现意外:)

回答by Michael van de Waeter

How I did it (tested on Django==1.8, with postgres, so probably also 1.7)

我是怎么做的(在 Django==1.8 上测试,使用 postgres,所以也可能是 1.7)

Situation

情况

app1.YourModel

app1.YourModel

but you want it to go to: app2.YourModel

但您希望它转到: app2.YourModel

  1. Copy YourModel (the code) from app1 to app2.
  2. add this to app2.YourModel:

    Class Meta:
        db_table = 'app1_yourmodel'
    
  3. $ python manage.py makemigrations app2

  4. A new migration (e.g. 0009_auto_something.py) is made in app2 with a migrations.CreateModel() statement, move this statement to the initial migration of app2 (e.g. 0001_initial.py) (it will be just like it always have been there). And now remove the created migration = 0009_auto_something.py

  5. Just as you act, like app2.YourModel always has been there, now remove the existence of app1.YourModel from your migrations. Meaning: comment out the CreateModel statements, and every adjustment or datamigration you used after that.

  6. And of course, every reference to app1.YourModel has to be changed to app2.YourModel through your project. Also, don't forget that all possible foreign keys to app1.YourModel in migrations have to be changed to app2.YourModel

  7. Now if you do $ python manage.py migrate, nothing has changed, also when you do $ python manage.py makemigrations, nothing new has been detected.

  8. Now the finishing touch: remove the Class Meta from app2.YourModel and do $ python manage.py makemigrations app2 && python manage.py migrate app2 (if you look into this migration you'll see something like this:)

        migrations.AlterModelTable(
        name='yourmodel',
        table=None,
    ),
    
  1. 将 YourModel(代码)从 app1 复制到 app2。
  2. 将此添加到 app2.YourModel:

    Class Meta:
        db_table = 'app1_yourmodel'
    
  3. $ python manage.py makemigrations app2

  4. 一个新的迁移(例如 0009_auto_something.py)是在 app2 中使用 migrations.CreateModel() 语句进行的,将此语句移动到 app2 的初始迁移(例如 0001_initial.py)(就像它一直在那里一样)。现在删除创建的迁移 = 0009_auto_something.py

  5. 就像您的行为一样,就像 app2.YourModel 一直存在一样,现在从您的迁移中删除 app1.YourModel 的存在。含义:注释掉 CreateModel 语句,以及之后使用的每个调整或数据迁移。

  6. 当然,每个对 app1.YourModel 的引用都必须通过您的项目更改为 app2.YourModel。另外,不要忘记迁移中 app1.YourModel 的所有可能的外键都必须更改为 app2.YourModel

  7. 现在,如果您执行 $ python manage.py migrate,则没有任何变化,而且当您执行 $ python manage.py makemigrations 时,也未检测到任何新内容。

  8. 现在画龙点睛:从 app2.YourModel 中删除 Class Meta 并执行 $ python manage.py makemigrations app2 && python manage.py migrate app2 (如果您查看此迁移,您将看到如下内容:)

        migrations.AlterModelTable(
        name='yourmodel',
        table=None,
    ),
    

table=None, means it will take the default table-name, which in this case will be app2_yourmodel.

table=None,意味着它将采用默认的表名,在这种情况下将是 app2_yourmodel。

  1. DONE, with data saved.
  1. 完成,数据已保存。

P.S during the migration it will see that that content_type app1.yourmodel has been removed and can be deleted. You can say yes to that but only if you don't use it. In case you heavily depend on it to have FKs to that content-type be intact, don't answer yes or no yet, but go into the db that time manually, and remove the contentype app2.yourmodel, and rename the contenttype app1.yourmodel to app2.yourmodel, and then continue by answering no.

PS 在迁移过程中,它会看到 content_type app1.yourmodel 已被删除并且可以删除。你可以说是,但前提是你不使用它。如果您严重依赖它使该内容类型的 FK 完好无损,请不要回答是或否,而是在那个时候手动进入数据库,并删除内容类型 app2.yourmodel,并重命名内容类型 app1。 yourmodel 到 app2.yourmodel,然后继续回答 no。

回答by akaariai

Lets say you are moving model TheModel from app_a to app_b.

假设您正在将模型 TheModel 从 app_a 移动到 app_b。

An alternate solution is to alter the existing migrations by hand. The idea is that each time you see an operation altering TheModel in app_a's migrations, you copy that operation to the end of app_b's initial migration. And each time you see a reference 'app_a.TheModel' in app_a's migrations, you change it to 'app_b.TheModel'.

另一种解决方案是手动更改现有迁移。这个想法是,每次您在 app_a 的迁移中看到更改 TheModel 的操作时,您都将该操作复制到 app_b 的初始迁移的末尾。每次您在 app_a 的迁移中看到引用“app_a.TheModel”时,将其更改为“app_b.TheModel”。

I just did this for an existing project, where I wanted to extract a certain model to an reusable app. The procedure went smoothly. I guess things would be much harder if there were references from app_b to app_a. Also, I had a manually defined Meta.db_table for my model which might have helped.

我只是为一个现有项目做了这个,我想将某个模型提取到一个可重用的应用程序中。程序进行得很顺利。我想如果有从 app_b 到 app_a 的引用,事情会困难得多。此外,我为我的模型手动定义了一个 Meta.db_table,这可能会有所帮助。

Notably you will end up with altered migration history. This doesn't matter, even if you have a database with the original migrations applied. If both the original and the rewritten migrations end up with the same database schema, then such rewrite should be OK.

值得注意的是,您最终会改变迁移历史。这无关紧要,即使您有一个应用了原始迁移的数据库。如果原始迁移和重写的迁移最终都具有相同的数据库架构,那么这种重写应该没问题。

回答by tomcounsell

  1. change the names of old models to ‘model_name_old'
  2. makemigrations
  3. make new models named ‘model_name_new' with identical relationships on the related models (eg. user model now has user.blog_old and user.blog_new)
  4. makemigrations
  5. write a custom migration that migrates all the data to the new model tables
  6. test the hell out of these migrations by comparing backups with new db copies before and after running the migrations
  7. when all is satisfactory, delete the old models
  8. makemigrations
  9. change the new models to the correct name ‘model_name_new' -> ‘model_name'
  10. test the whole slew of migrations on a staging server
  11. take your production site down for a few minutes in order to run all migrations without users interfering
  1. 将旧模型的名称更改为“model_name_old”
  2. 迁徙
  3. 在相关模型上创建具有相同关系的名为“model_name_new”的新模型(例如,用户模型现在具有 user.blog_old 和 user.blog_new)
  4. 迁徙
  5. 编写一个自定义迁移,将所有数据迁移到新的模型表
  6. 通过在运行迁移之前和之后将备份与新的数据库副本进行比较来测试这些迁移
  7. 当一切都满意时,删除旧模型
  8. 迁徙
  9. 将新模型更改为正确的名称 'model_name_new' -> 'model_name'
  10. 在临时服务器上测试整个迁移
  11. 关闭您的生产站点几分钟,以便在没有用户干扰的情况下运行所有​​迁移

Do this individually for each model that needs to be moved. I wouldn't suggest doing what the other answer says by changing to integers and back to foreign keys There is a chance that new foreign keys will be different and rows may have different IDs after the migrations and I didn't want to run any risk of mismatching ids when switching back to foreign keys.

为每个需要移动的模型单独执行此操作。我不建议通过更改为整数并返回外键来执行其他答案所说的操作新的外键可能会有所不同,并且迁移后行可能具有不同的 ID,我不想冒任何风险切换回外键时不匹配的 id。

回答by Wtower

Another hacky alternative if the data is not big or too complicated, but still important to maintain, is to:

如果数据不大或太复杂,但维护起来仍然很重要,另一种 hacky 选择是:

  • Get data fixtures using manage.py dumpdata
  • Proceed to model changes and migrations properly, without relating the changes
  • Global replace the fixtures from the old model and app names to the new
  • Load data using manage.py loaddata
  • 使用manage.py dumpdata获取数据装置
  • 继续对更改和迁移进行正确建模,而不将更改关联起来
  • 全局替换从旧模型和应用程序名称到新的装置
  • 使用manage.py loaddata加载数据

回答by Gal Singer

Copied from my answer at https://stackoverflow.com/a/47392970/8971048

复制自我在https://stackoverflow.com/a/47392970/8971048 上的回答

In case you need to move the model and you don't have access to the app anymore (or you don't want the access), you can create a new Operation and consider to create a new model only if the migrated model does not exist.

如果您需要移动模型并且您不再有权访问该应用程序(或者您不想访问该应用程序),您可以创建一个新的 Operation 并考虑仅在迁移的模型没有时才考虑创建一个新模型存在。

In this example I am passing 'MyModel' from old_app to myapp.

在这个例子中,我将“MyModel”从 old_app 传递给 myapp。

class MigrateOrCreateTable(migrations.CreateModel):
    def __init__(self, source_table, dst_table, *args, **kwargs):
        super(MigrateOrCreateTable, self).__init__(*args, **kwargs)
        self.source_table = source_table
        self.dst_table = dst_table

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        table_exists = self.source_table in schema_editor.connection.introspection.table_names()
        if table_exists:
            with schema_editor.connection.cursor() as cursor:
                cursor.execute("RENAME TABLE {} TO {};".format(self.source_table, self.dst_table))
        else:
            return super(MigrateOrCreateTable, self).database_forwards(app_label, schema_editor, from_state, to_state)


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_some_migration'),
    ]

    operations = [
        MigrateOrCreateTable(
            source_table='old_app_mymodel',
            dst_table='myapp_mymodel',
            name='MyModel',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=18))
            ],
        ),
    ]