migrations makemigrations inconsistent force exceptions error detected delete changes python mysql database django schema-migration

python - makemigrations - Cómo mover un modelo entre dos aplicaciones de Django(Django 1.7)



error migrate django (11)

Entonces, hace aproximadamente un año, comencé un proyecto y, como todos los nuevos desarrolladores, realmente no me enfoqué demasiado en la estructura, sin embargo, ahora que estoy más junto con Django, comenzó a parecer que el diseño de mi proyecto, principalmente mis modelos, tienen una estructura horrible. .

Tengo modelos mantenidos principalmente en una sola aplicación y realmente la mayoría de estos modelos deberían estar en sus propias aplicaciones individuales, intenté resolver esto y moverlos hacia el sur, sin embargo, me pareció difícil y realmente difícil debido a las claves externas, etc.

Sin embargo, debido a Django 1.7 y al soporte integrado para las migraciones, ¿hay una mejor manera de hacerlo ahora?


  1. cambiar los nombres de modelos antiguos a ''model_name_old''
  2. hacer migraciones
  3. crear nuevos modelos llamados ''model_name_new'' con relaciones idénticas en los modelos relacionados (por ejemplo, el modelo de usuario ahora tiene user.blog_old y user.blog_new)
  4. hacer migraciones
  5. escribir una migración personalizada que migre todos los datos a las nuevas tablas del modelo
  6. pruebe al máximo estas migraciones comparando las copias de seguridad con nuevas copias de db antes y después de ejecutar las migraciones
  7. cuando todo sea satisfactorio, elimine los modelos antiguos
  8. hacer migraciones
  9. cambie los nuevos modelos al nombre correcto ''model_name_new'' -> ''model_name''
  10. probar toda la gran cantidad de migraciones en un servidor provisional
  11. desconecte su sitio de producción durante unos minutos para ejecutar todas las migraciones sin que los usuarios interfieran

Haga esto individualmente para cada modelo que necesite ser movido. No sugeriría hacer lo que dice la otra respuesta al cambiar a números enteros y volver a claves foráneas. Existe la posibilidad de que las nuevas claves foráneas sean diferentes y que las filas tengan ID diferentes después de las migraciones y no quisiera correr ningún riesgo. de identificadores no coincidentes al volver a las claves externas.


Cómo lo hice (probado en Django == 1.8, con postgres, así que probablemente también 1.7)

Situación

app1.YourModel

pero quieres que vaya a: app2.YourModel

  1. Copie YourModel (el código) de app1 a app2.
  2. agregue esto a app2.YourModel:

    Class Meta: db_table = ''app1_yourmodel''

  3. $ python manage.py makemigrations app2

  4. Se realiza una nueva migración (por ejemplo, 0009_auto_something.py) en app2 con una declaración migrations.CreateModel (), mueva esta declaración a la migración inicial de app2 (por ejemplo, 0001_initial.py) (será como siempre ha estado allí). Y ahora elimine la migración creada = 0009_auto_something.py

  5. Así como actúas, como app2.YourModel siempre ha estado allí, ahora elimina la existencia de app1.YourModel de tus migraciones. Significado: comente las declaraciones CreateModel y cada ajuste o migración de datos que utilizó después de eso.

  6. Y, por supuesto, cada referencia a app1.YourModel debe cambiarse a app2.YourModel a través de su proyecto. Además, no olvide que todas las claves externas posibles para app1.YourModel en las migraciones deben cambiarse a app2.YourModel

  7. Ahora, si hace $ python manage.py migrate, nada ha cambiado, también cuando hace $ python manage.py makemigrations, no se ha detectado nada nuevo.

  8. Ahora el toque final: elimine Class Meta de app2.YourModel y haga $ python manage.py makemigrations app2 && python manage.py migrate app2 (si observa esta migración verá algo como esto :)

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

table = None, significa que tomará el nombre de tabla predeterminado, que en este caso será app2_yourmodel.

  1. HECHO, con los datos guardados.

PS durante la migración verá que esa content_type app1.yourmodel se ha eliminado y se puede eliminar. Puedes decir sí a eso, pero solo si no lo usas. En caso de que dependa en gran medida de que tenga FK para ese tipo de contenido intacto, no responda sí o no todavía, pero vaya a db esa vez manualmente y elimine el tipo de contenido app2.yourmodel y cambie el nombre de la aplicación contenttype1. yourmodel a app2.yourmodel, y luego continúe respondiendo no.


Copiado de mi respuesta en https://.com/a/47392970/8971048

En caso de que necesite mover el modelo y ya no tenga acceso a la aplicación (o no desee el acceso), puede crear una nueva Operación y considerar crear un nuevo modelo solo si el modelo migrado no existe.

En este ejemplo, paso ''MyModel'' de old_app a 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)) ], ), ]


Digamos que está moviendo el modelo TheModel de app_a a app_b.

Una solución alternativa es alterar las migraciones existentes a mano. La idea es que cada vez que vea una operación que altere TheModel en las migraciones de app_a, copie esa operación al final de la migración inicial de app_b. Y cada vez que vea una referencia ''app_a.TheModel'' en las migraciones de app_a, la cambia a ''app_b.TheModel''.

Acabo de hacer esto para un proyecto existente, donde quería extraer un cierto modelo a una aplicación reutilizable. El procedimiento fue sin problemas. Supongo que las cosas serían mucho más difíciles si hubiera referencias de app_b a app_a. Además, tenía un Meta.db_table definido manualmente para mi modelo que podría haber ayudado.

En particular, terminarás con un historial de migración alterado. Esto no importa, incluso si tiene una base de datos con las migraciones originales aplicadas. Si tanto las migraciones originales como las reescritas terminan con el mismo esquema de base de datos, entonces dicha reescritura debería estar bien.


Encontré el mismo problema. La respuesta de Ozan me ayudó mucho, pero desafortunadamente no fue suficiente. De hecho, tenía varios ForeignKey vinculados al modelo que quería mover. Después de un dolor de cabeza encontré la solución, así que decidí publicarla para resolver el tiempo de las personas.

Necesita 2 pasos más:

  1. Antes de hacer nada, cambie todos los enlaces de ForeignKey a TheModel en Integerfield . Luego ejecute python manage.py makemigrations
  2. Después de seguir los pasos de Ozan, vuelva a convertir sus claves foráneas: vuelva a colocar ForeignKey(TheModel) lugar de IntegerField() . Luego realice las migraciones nuevamente ( python manage.py makemigrations ). Luego puede migrar y debería funcionar ( python manage.py migrate )

Espero eso ayude. Por supuesto, pruébelo en local antes de intentarlo en producción para evitar malas sorpresas :)


Esto se puede hacer con bastante facilidad utilizando migrations.SeparateDatabaseAndState . migrations.SeparateDatabaseAndState . Básicamente, utilizamos una operación de base de datos para cambiar el nombre de la tabla simultáneamente con dos operaciones de estado para eliminar el modelo del historial de una aplicación y crearlo en el de otra.

Eliminar de la aplicación anterior

python manage.py makemigrations old_app --empty

En la migración:

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

Agregar a nueva aplicación

Primero, copie el modelo a la nueva aplicación model.py, luego:

python manage.py makemigrations new_app

Esto generará una migración con una operación ingenua CreateModel como única operación. Envuelva eso en una operación SeparateDatabaseAndState modo que no intentemos recrear la tabla. Incluya también la migración previa como una dependencia:

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


Estoy eliminando la respuesta anterior, ya que puede provocar la pérdida de datos. Como mencionó ozan , podemos crear 2 migraciones, una en cada aplicación.

Primera migración para eliminar el modelo de la primera aplicación.

$ python manage.py makemigrations old_app --empty

Edite el archivo de migración para incluir estas operaciones.

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

Segunda migración que depende de la primera migración y crea la nueva tabla en la segunda aplicación. Después de mover el código del modelo a la segunda aplicación

$ python manage.py makemigrations new_app

y edite el archivo de migración a algo como esto.

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


Me dan nerviosas las migraciones de codificación manual (como lo requiere Ozan''s respuesta Ozan''s ), así que lo siguiente combina Michael''s estrategias de Ozan y Michael''s para minimizar la cantidad de codificación manual requerida:

  1. Antes de mover cualquier modelo, asegúrese de estar trabajando con una línea base limpia ejecutando makemigrations .
  2. Mueva el código para el Modelo de app1 a app2
  3. Según lo recomendado por @Michael, apuntamos el nuevo modelo a la tabla de base de datos anterior utilizando la opción Meta db_table en el modelo "nuevo":

    class Meta: db_table = ''app1_yourmodel''

  4. Ejecute makemigrations . Esto generará CreateModel en app2 y DeleteModel en app1 . Técnicamente, estas migraciones se refieren exactamente a la misma tabla y eliminarían (incluidos todos los datos) y volverían a crear la tabla.

  5. En realidad, no queremos (o necesitamos) hacer nada a la mesa. Solo necesitamos que Django crea que el cambio se ha realizado. Según la respuesta de @ Ozan, el indicador state_operations en SeparateDatabaseAndState hace esto. Así que envolvemos todas las entradas de migrations EN AMBOS ARCHIVOS DE MIGRACIONES con SeparateDatabaseAndState(state_operations=[...]) . Por ejemplo,

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

    se convierte

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

  6. EDITAR : También debe asegurarse de que la nueva migración "virtual" de CreateModel dependa de cualquier migración que realmente haya creado o alterado la tabla original . Por ejemplo, si sus nuevas migraciones son app2.migrations.0004_auto_<date> (para Create ) y app1.migrations.0007_auto_<date> (para Delete ), lo más simple es:

    • Abra app1.migrations.0007_auto_<date> y copie su dependencia de app1 (p (''app1'', ''0006...''), Ej. (''app1'', ''0006...''), ). Esta es la migración "inmediatamente anterior" en la app1 y debe incluir dependencias de toda la lógica de construcción del modelo real.
    • Abra app2.migrations.0004_auto_<date> y agregue la dependencia que acaba de copiar a su lista de dependencies .

EDITAR : si tiene relaciones ForeignKey con el modelo que está moviendo, lo anterior puede no funcionar. Esto sucede porque:

  • Las dependencias no se crean automáticamente para los cambios de ForeignKey
  • No queremos ajustar los cambios de ForeignKey en state_operations por lo que debemos asegurarnos de que estén separados de las operaciones de la tabla.

El conjunto "mínimo" de operaciones difiere según la situación, pero el siguiente procedimiento debería funcionar para la mayoría / todas las migraciones de ForeignKey :

  1. app1 el modelo de app1 a app2 , establezca db_table , pero NO cambie ninguna referencia de FK.
  2. Ejecute makemigrations y makemigrations toda la migración de state_operations en state_operations (ver arriba)
    • Como se app2 anteriormente, agregue una dependencia en app2 CreateTable a la última migración de app1
  3. Señale todas las referencias de FK al nuevo modelo. Si no está utilizando referencias de cadena, mueva el modelo anterior a la parte inferior de models.py (NO lo elimine) para que no compita con la clase importada.
  4. Ejecute makemigrations pero NO envuelva nada en state_operations (los cambios de FK deberían suceder realmente). Agregue una dependencia en todas las migraciones ForeignKey (es decir, AlterField ) a la migración CreateTable en app2 (necesitará esta lista para el siguiente paso, así que app2 un seguimiento de ellas). Por ejemplo:

    • Encuentre la migración que incluye CreateModel por ejemplo, app2.migrations.0002_auto_<date> y copie el nombre de esa migración.
    • Encuentre todas las migraciones que tengan una ForeignKey para ese modelo (por ejemplo, buscando app2.YourModel para encontrar migraciones como:

      class Migration(migrations.Migration): dependencies = [ (''otherapp'', ''0001_initial''), ] operations = [ migrations.AlterField( model_name=''relatedmodel'', name=''fieldname'', field=models.ForeignKey(... to=''app2.YourModel''), ), ]

    • Agregue la migración CreateModel como una dependencia:

      class Migration(migrations.Migration): dependencies = [ (''otherapp'', ''0001_initial''), (''app2'', ''0002_auto_<date>''), ]

  5. Eliminar los modelos de app1

  6. Ejecute makemigrations y app1 migración de state_operations en state_operations .
    • Agregue una dependencia a todas las migraciones ForeignKey (es decir, AlterField ) del paso anterior (puede incluir migraciones en app1 y app2 ).
    • Cuando construí estas migraciones, DeleteTable ya dependía de las migraciones de AlterField , por lo que no tuve que aplicarlas manualmente (es decir, Alter antes de Delete ).

En este punto, Django está listo. El nuevo modelo apunta a la tabla anterior y las migraciones de Django lo han convencido de que todo se ha reubicado adecuadamente. La gran advertencia (de la respuesta de @ Michael) es que se crea un nuevo ContentType para el nuevo modelo. Si vincula (p. Ej., ForeignKey ) a tipos de contenido, deberá crear una migración para actualizar la tabla ContentType .

Quería limpiar después de mí (opciones de Meta y nombres de tablas), así que utilicé el siguiente procedimiento (de @Michael):

  1. Eliminar la entrada Meta db_table
  2. Ejecute makemigrations nuevamente para generar el cambio de nombre de la base de datos
  3. Edite esta última migración y asegúrese de que depende de la migración de DeleteTable . No parece que deba ser necesario, ya que Delete debe ser puramente lógico, pero me he encontrado con errores (por ejemplo, app1_yourmodel no existe) si no lo hago.

Otra alternativa hacky si los datos no son grandes o demasiado complicados, pero aún son importantes para mantener, es:

  • Obtenga accesorios de datos utilizando manage.py dumpdata
  • Continúe con los cambios y migraciones del modelo correctamente, sin relacionar los cambios.
  • Global reemplaza los accesorios del modelo antiguo y los nombres de las aplicaciones a los nuevos
  • Cargar datos usando manage.py loaddata

Puedes probar lo siguiente (no probado):

  1. mover el modelo de src_app a dest_app
  2. dest_app ; asegúrese de que la migración del esquema dependa de la última migración de src_app ( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files )
  3. agregar una migración de datos a dest_app , que copia todos los datos de src_app
  4. migrar src_app ; asegúrese de que la migración del esquema dependa de la última migración (datos) de dest_app , es decir: la migración del paso 3

Tenga en cuenta que copiará toda la tabla, en lugar de moverla , pero de esa manera ambas aplicaciones no tienen que tocar una tabla que pertenece a la otra aplicación, lo que creo que es más importante.


Esto se prueba aproximadamente, ¡así que no olvide hacer una copia de seguridad de su base de datos!

Por ejemplo, hay dos aplicaciones: src_app y dst_app , queremos mover el modelo MoveMe de src_app a dst_app .

Cree migraciones vacías para ambas aplicaciones:

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

Supongamos que las nuevas migraciones son XXX1_src_app_new y XXX1_dst_app_new , las migraciones principales anteriores son XXX0_src_app_old y XXX0_dst_app_old .

Agregue una operación que MoveMe nombre de la tabla para el modelo MoveMe y cambie el nombre de su app_label en ProjectState a XXX1_dst_app_new . No olvide agregar dependencia en la migración XXX0_src_app_old . La migración XXX1_dst_app_new resultante es:

# -*- 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''), ]

Agregue dependencia en XXX1_dst_app_new a XXX1_src_app_new . XXX1_src_app_new es una migración sin XXX1_src_app_new que se necesita para asegurarse de que las futuras migraciones de src_app se ejecutarán después de XXX1_dst_app_new .

Mueva MoveMe de src_app/models.py a dst_app/models.py . Entonces corre:

python manage.py migrate

¡Eso es todo!