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

后端 未结 11 1654
日久生厌
日久生厌 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 17:58

    I encountered the same problem. Ozan's answer helped 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.

    You need 2 more steps:

    1. Before doing anything, change all your ForeignKey linking to TheModel into 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)

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

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

    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.

    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:

    python manage.py makemigrations new_app
    

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

    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:01

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

    Situation

    app1.YourModel

    but you want it to go to: 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,
      ),
      

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

    1. DONE, with data saved.

    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.

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

    I get nervous hand-coding migrations (as is required by Ozan's answer) so the following combines Ozan's and Michael's strategies to minimize the amount of hand-coding required:

    1. Before moving any models, make sure you're working with a clean baseline by running makemigrations.
    2. Move the code for the Model from app1 to app2
    3. As recommended by @Michael, we point the new model to the old database table using the db_table Meta option on the "new" model:

      class Meta:
          db_table = 'app1_yourmodel'
      
    4. Run makemigrations. This will generate CreateModel in app2 and DeleteModel in app1. Technically, these migrations refer to the exact same table and would remove (including all data) and re-create the table.

    5. In reality, we don't want (or need) to do anything to the table. We just need Django to believe that the change has been made. Per @Ozan's answer, the state_operations flag in SeparateDatabaseAndState does this. So we wrap all of the migrations entries IN BOTH MIGRATIONS FILES with SeparateDatabaseAndState(state_operations=[...]). For example,

      operations = [
          ...
          migrations.DeleteModel(
              name='YourModel',
          ),
          ...
      ]
      

      becomes

      operations = [
          migrations.SeparateDatabaseAndState(state_operations=[
              ...
              migrations.DeleteModel(
                  name='YourModel',
              ),
              ...
          ])
      ]
      
    6. You also need to make sure the new "virtual" CreateModel migration depends on any migration that actually created or altered the original table. For example, if your new migrations are app2.migrations.0004_auto_<date> (for the Create) and app1.migrations.0007_auto_<date> (for the Delete), the simplest thing to do is:

      • Open app1.migrations.0007_auto_<date> and copy its app1 dependency (e.g. ('app1', '0006...'),). This is the "immediately prior" migration in app1 and should include dependencies on all of the actual model building logic.
      • Open app2.migrations.0004_auto_<date> and add the dependency you just copied to its dependencies list.

    If you have ForeignKey relationship(s) to the model you're moving, the above may not work. This happens because:

    • Dependencies are not automatically created for the ForeignKey changes
    • We do not want to wrap the ForeignKey changes in state_operations so we need to ensure they are separate from the table operations.

    NOTE: Django 2.2 added a warning (models.E028) that breaks this method. You may be able to work around it with managed=False but I have not tested it.

    The "minimum" set of operations differ depending on the situation, but the following procedure should work for most/all ForeignKey migrations:

    1. COPY the model from app1 to app2, set db_table, but DON'T change any FK references.
    2. Run makemigrations and wrap all app2 migration in state_operations (see above)
      • As above, add a dependency in the app2 CreateTable to the latest app1 migration
    3. Point all of the FK references to the new model. If you aren't using string references, move the old model to the bottom of models.py (DON'T remove it) so it doesn't compete with the imported class.
    4. Run makemigrations but DON'T wrap anything in state_operations (the FK changes should actually happen). Add a dependency in all the ForeignKey migrations (i.e. AlterField) to the CreateTable migration in app2 (you'll need this list for the next step so keep track of them). For example:

      • Find the migration that includes the CreateModel e.g. app2.migrations.0002_auto_<date> and copy the name of that migration.
      • Find all migrations that have a ForeignKey to that model (e.g. by searching app2.YourModel to find migrations like:

        class Migration(migrations.Migration):
        
            dependencies = [
                ('otherapp', '0001_initial'),
            ]
        
            operations = [
                migrations.AlterField(
                    model_name='relatedmodel',
                    name='fieldname',
                    field=models.ForeignKey(... to='app2.YourModel'),
                ),
            ]
        
      • Add the CreateModel migration as as a dependency:

        class Migration(migrations.Migration):
        
            dependencies = [
                ('otherapp', '0001_initial'),
                ('app2', '0002_auto_<date>'),
            ]  
        
    5. Remove the models from app1

    6. Run makemigrations and wrap the app1 migration in state_operations.
      • Add a dependency to all of the ForeignKey migrations (i.e. AlterField) from the previous step (may include migrations in app1 and app2).
      • When I built these migrations, the DeleteTable already depended on the AlterField migrations so I didn't need to manually enforce it (i.e. Alter before Delete).

    At this point, Django is good to go. The new model points to the old table and Django's migrations have convinced it that everything has been relocated appropriately. The big caveat (from @Michael's answer) is that a new ContentType is created for the new model. If you link (e.g. by ForeignKey) to content types, you'll need to create a migration to update the ContentType table.

    I wanted to cleanup after myself (Meta options and table names) so I used the following procedure (from @Michael):

    1. Remove the db_table Meta entry
    2. Run makemigrations again to generate the database rename
    3. Edit this last migration and make sure it depends on the DeleteTable migration. It doesn't seem like it should be necessary as the Delete should be purely logical, but I've run into errors (e.g. app1_yourmodel doesn't exist) if I don't.
    0 讨论(0)
  • 2020-11-28 18:07

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

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

    You can try the following (untested):

    1. move the model from src_app to dest_app
    2. migrate dest_app; make sure the schema migration depends on the latest src_app migration (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

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

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