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

后端 未结 11 1655
日久生厌
日久生厌 2020-11-28 17:35

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 start

相关标签:
11条回答
  • 2020-11-28 18:18

    Copied from my answer at 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.

    In this example I am passing 'MyModel' from old_app to 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))
                ],
            ),
        ]
    
    0 讨论(0)
  • 2020-11-28 18:19

    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.

    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)
        ]
    
    0 讨论(0)
  • 2020-11-28 18:20

    Lets say you are moving model TheModel from app_a to 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'.

    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.

    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.

    0 讨论(0)
  • 2020-11-28 18:20
    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

    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.

    0 讨论(0)
  • 2020-11-28 18:23

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

    For example, there are two apps: src_app and dst_app, we want to move model MoveMe from src_app to dst_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_new and XXX1_dst_app_new, previuos top migrations are XXX0_src_app_old and XXX0_dst_app_old.

    Add an operation that renames table for MoveMe model and renames its app_label in ProjectState to XXX1_dst_app_new. Do not forget to add dependency on XXX0_src_app_old migration. The resulting XXX1_dst_app_new migration is:

    # -*- 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_new to XXX1_src_app_new. XXX1_src_app_new is no-op migration that is needed to make sure that future src_app migrations will be executed after XXX1_dst_app_new.

    Move MoveMe from src_app/models.py to dst_app/models.py. Then run:

    python manage.py migrate
    

    That's all!

    0 讨论(0)
提交回复
热议问题