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

所以大约一年前,我开始了一个项目,就像所有新开发者一样,我并没有把注意力放在结构上,但是现在我和Django一起开始出现,我的项目布局主要是我的模型在结构上很糟糕。

我有一个模型主要是在一个单一的应用程序,实际上大多数这些模型应该在他们自己的个人应用程序,我试图解决这个问题,并把它们向南移动,但我发现它很棘手,真的很难由于外键等。

然而,由于Django 1.7和内置的迁移支持,现在有更好的方法来做到这一点吗?

预先感谢您的任何帮助。

注意: ozan答案比我的好。 请使用该方法

创build应用程序和模型

1)创build两个应用程序, app1app2

 $ python manage.py startapp app1 $ python manage.py startapp app2 

2)创build两个模型model1model2

APP1 / model1.py

 from django.db import models class model1(models.Model): name = models.CharField(max_length=100) 

APP 2 / model2.py

 from django.db import models class model1(models.Model): name = models.CharField(max_length=100) 

3)应用迁移。

 $ python manage.py makemigrations app1 && python migrate app1 $ python manage.py makemigrations app2 && python migrate app2 

Django将创build2个表app1_modelapp2_model2 。 您可以将数据添加到模型。

让我们将less量数据添加到我们的模型中,以确保我们在移动模型时保留数据。

 In [1]: from apps.app1.models import model1 In [2]: model1.objects.create() Out[2]: <model1: model1 object> In [3]: model1.objects.create() Out[3]: <model1: model1 object> 

迁移模型

让我们看看如何执行实际的迁移。

4)重命名表和移动源代码转到您的数据库,并将app1_model重命名为app2_model1 。 将model1源代码从app1移动到app2并添加元选项。

APP1 / model1.py

 from django.db import models 

APP 2 / model2.py

 from django.db import models class model2(models.Model): name = models.CharField(max_length=100) class model1(models.Model): name = models.CharField(max_length=100) class Meta: managed = False db_table = 'app2_model1' 

5)应用迁移。 为两个应用程序应用迁移。

 $ python manage.py makemigrations app2 && python manage.py migrate app2 $ python manage.py makemigrations app1 && python manage.py migrate --fake app1 

迁移完成后,您可以检查模型中的数据。

 In [1]: from apps.app2.models import model1 In [2]: model1.objects.all() Out[2]: [<model1: model1 object>, <model1: model1 object>] 

这可以使用migrations.SeparateDatabaseAndState相当容易地完成。 基本上,我们使用数据库操作来同时为两个状态操作重命名表,以从一个应用程序的历史logging中删除模型,并在另一个应用程序的历史logging中创build它。

从旧应用中移除

 python manage.py makemigrations old_app --empty 

在迁移中:

 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) ] 

添加到新的应用程序

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

 python manage.py makemigrations new_app 

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

 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) ] 

我遇到了同样的问题。 奥赞的回答帮了我很多,但不幸的是还不够。 事实上,我有几个ForeignKey链接到我想移动的模型。 经过一番头痛后,我发现解决scheme决定发布解决人们的时间。

你还需要2个步骤:

  1. 在做任何事之前,将所有的ForeignKey链接TheModelIntegerfield 。 然后运行python manage.py makemigrations
  2. 在做Ozan的步骤后,重新转换你的外键:放回ForeignKey(TheModel)而不是IntegerField() 。 然后再次进行迁移( python manage.py makemigrations )。 然后你可以迁移,它应该工作( python manage.py migrate

希望能帮助到你。 当然,在尝试在生产之前testing它在本地,以避免不好的惊喜:)

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

情况

app1.YourModel

但是你想要它去: app2.YourModel

  1. 将您的模型(代码)从app1复制到app2。
  2. 将此添加到app2.YourModel:

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

  4. 使用migrations.CreateModel()语句在app2中创build新的迁移(例如0009_auto_something.py),将此语句移至app2的初始迁移(例如0001_initial.py)(就像它总是在那里一样)。 现在删除创build的迁移= 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意味着它将采用默认的表名,在这种情况下将是app2_yourmodel。

  1. 完成,保存数据。

PS在迁移过程中将会看到那个content_type app1.yourmodel已经被删除并且可以被删除。 你可以肯定地说,但只有当你不使用它。 如果你严重依赖它来使FK的内容types完整,不要回答是或否,但手动进入数据库,并删除内容typesapp2.yourmodel,并重命名内容typesapp1。 yourmodel到app2.yourmodel,然后继续回答no。

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

例如,有两个应用程序: src_appdst_app ,我们希望将模型MoveMesrc_app移动到dst_app

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

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

我们假设,新的迁移是XXX1_src_app_newXXX1_dst_app_newXXX0_src_app_old迁移是XXX0_src_app_oldXXX0_dst_app_old

添加一个为MoveMe模型重命名表的操作,并将其在ProjectState中的XXX1_dst_app_new重命名为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'), ] 

将对XXX1_dst_app_new依赖关系添加到XXX1_src_app_newXXX1_src_app_new是无操作迁移,需要确保未来的src_app迁移将在XXX1_dst_app_new之后执行。

MoveMesrc_app/models.py移动到dst_app/models.py 。 然后运行:

 python manage.py migrate 

就这样!

您可以尝试以下(未经testing):

  1. 将模型从src_app移到dest_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步

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

比方说你正在将模型TheModel从app_a移动到app_b。

另一种解决scheme是手动更改现有的迁移。 我们的想法是,每次在app_a的迁移中看到一个操作改变TheModel的操作时,都会将该操作复制到app_b的初始迁移结束。 每当你在app_a的迁移中看到一个引用“app_a.TheModel”时,你就把它改为“app_b.TheModel”。

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

值得注意的是,你将最终以改变的移民历史。 这并不重要,即使您拥有应用了原始迁移的数据库。 如果原来的和重写的迁移都以相同的数据库模式结束,那么这样的重写应该是可以的。

  1. 将旧模型的名称更改为“model_name_old”
  2. makemigrations
  3. 在相关模型上创build名为“model_name_new”的新模型具有相同的关系(例如,用户模型现在具有user.blog_old和user.blog_new)
  4. makemigrations
  5. 编写一个将所有数据迁移到新模型表的自定义迁移
  6. 通过在运行迁移之前和之后将备份与新数据库副本进行比较来testing这些迁移
  7. 当所有的都满意的时候,删掉旧的机型
  8. makemigrations
  9. 将新模型更改为正确的名称'model_name_new' – >'model_name'
  10. 在暂存服务器上testing整个迁移
  11. 将您的生产站点停下来几分钟,以便在没有用户干扰的情况下运行所有​​迁移

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

如果数据不是太大或太复杂,但仍然很重要的另一个方法是:

  • 使用manage.py dumpdata获取数据装置
  • 继续正确模型更改和迁移,而不涉及更改
  • 全局将旧模型和应用程序名称中的灯具replace为新的
  • 使用manage.py loaddata加载数据