world tutorial run proyectos hello ejemplos python database django-models schema-migration

python - tutorial - Mover modelos entre aplicaciones de Django(1.8) con referencias de ForeignKey requeridas



run django project (7)

Esta es una extensión de esta pregunta: Cómo mover un modelo entre dos aplicaciones de Django (Django 1.7)

Necesito mover un montón de modelos de old_app a new_app . La mejor respuesta parece ser la de Ozan , pero con las referencias de clave foránea requeridas, las cosas son un poco más complicadas. @halfnibble presenta una solución en los comentarios a la respuesta de Ozan, pero sigo teniendo problemas con el orden preciso de los pasos (por ejemplo, cuando copio los modelos a new_app , cuando new_app los modelos de old_app , qué migraciones en old_app.migrations vs. new_app.migrations , etc.)

¡Cualquier ayuda es muy apreciada!


Creé un comando de administración para hacer eso, mover un modelo de una aplicación de Django a otra, según las sugerencias de nostálgico.io en https://.com/a/30613732/1639699

Puedes encontrarlo en GitHub en alexei/django-move-model


Después de terminar el trabajo intenté hacer una nueva migración. Pero me enfrenta con el siguiente error: ValueError: Unhandled pending operations for models: oldapp.modelname (referred to by fields: oldapp.HistoricalProductModelName.model_ref_obj)

Si su modelo de Django utiliza HistoricalRecords campo HistoricalRecords , no olvide agregar modelos / tablas adicionales mientras sigue la respuesta de @ Nostalg.io.

Agregue el siguiente elemento a database_operations en el primer paso (4.a):

migrations.AlterModelTable(''historicalmodelname'', ''newapp_historicalmodelname''),

y agrega Delete adicional en state_operations en el último paso (4.d):

migrations.DeleteModel(name=''HistoricalModleName''),


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

En este ejemplo, estoy pasando ''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)) ], ), ]


Esto funcionó para mí, pero estoy seguro de que voy a escuchar por qué es una idea terrible. Agregue esta función y una operación que lo llame a su migración old_app:

def migrate_model(apps, schema_editor): old_model = apps.get_model(''old_app'', ''MovingModel'') new_model = apps.get_model(''new_app'', ''MovingModel'') for mod in old_model.objects.all(): mod.__class__ = new_model mod.save() class Migration(migrations.Migration): dependencies = [ (''new_app'', ''0006_auto_20171027_0213''), ] operations = [ migrations.RunPython(migrate_model), migrations.DeleteModel( name=''MovingModel'', ), ]

Paso 1: haga una copia de seguridad de su base de datos!
Asegúrese de que su migración new_app se ejecute primero y / o un requisito de la migración old_app. No elimine el tipo de contenido obsoleto hasta que haya completado la migración old_app.

después de Django 1.9 es posible que desee pasar un poco más de cuidado:
Migración1: Crear nueva tabla
Migración2: Rellenar tabla
Migración3: alterar campos en otras tablas
Migración4: Eliminar la tabla anterior


Justo ahora moví dos modelos de old_app a new_app , pero las referencias de FK estaban en algunos modelos de app_x y app_y , en lugar de modelos de old_app .

En este caso, siga los pasos proporcionados por Nostalg.io de esta manera:

  • Mueva los modelos de old_app a new_app , luego actualice las sentencias de import en la base de códigos.
  • makemigrations .
  • Siga el paso 4.a. Pero use AlterModelTable para todos los modelos movidos. Dos para mi
  • Siga el paso 4.b. como es.
  • Siga el paso 4.c. Pero también, para cada aplicación que tenga un archivo de migración recientemente generado, edítelos manualmente, de modo que migre las state_operations en state_operations lugar.
  • Siga el paso 4.d pero use DeleteModel para todos los modelos movidos.

Notas:

  • Todos los archivos de migración generados automáticamente desde otras aplicaciones dependen del archivo de migración personalizado de old_app donde AlterModelTable se usa para cambiar el nombre de la (s) tabla (s). (creado en el paso 4.a)
  • En mi caso, tuve que eliminar el archivo de migración generado automáticamente de old_app porque no tenía operaciones de AlterField , solo operaciones DeleteModel y RemoveField . O mantenlo con operations = [] vacías operations = []
  • Para evitar excepciones de migración al crear la base de datos de prueba desde cero, asegúrese de que la migración personalizada desde old_app creada en el paso 4.a. tiene todas las dependencias de migración anteriores de otras aplicaciones.

    old_app 0020_auto_others 0021_custom_rename_models.py dependencies: (''old_app'', ''0020_auto_others''), (''app_x'', ''0002_auto_20170608_1452''), (''app_y'', ''0005_auto_20170608_1452''), (''new_app'', ''0001_initial''), 0022_auto_maybe_empty_operations.py dependencies: (''old_app'', ''0021_custom_rename_models''), 0023_custom_clean_models.py dependencies: (''old_app'', ''0022_auto_maybe_empty_operations''), app_x 0001_initial.py 0002_auto_20170608_1452.py 0003_update_fk_state_operations.py dependencies (''app_x'', ''0002_auto_20170608_1452''), (''old_app'', ''0021_custom_rename_models''), app_y 0004_auto_others_that_could_use_old_refs.py 0005_auto_20170608_1452.py 0006_update_fk_state_operations.py dependencies (''app_y'', ''0005_auto_20170608_1452''), (''old_app'', ''0021_custom_rename_models''),

Por cierto: hay un boleto abierto sobre esto: https://code.djangoproject.com/ticket/24686


La forma en que Nostalg.io funcionó fue hacia adelante (autogeneración de todas las otras aplicaciones FK que la referencian). Pero también necesitaba retroceder. Para esto, la AlterTable hacia atrás tiene que ocurrir antes de que cualquier FK sea reintegrado (en el original ocurriría después de eso). Así que para esto, dividí AlterTable en 2 AlterTableF y AlterTableR separadas, cada una trabajando solo en una dirección, luego usando forward one en vez del original en la primera migración personalizada, y reverse one en la última migración de carros (ambas ocurren en la aplicación de autos) ) Algo como esto:

#cars/migrations/0002...py : class AlterModelTableF( migrations.AlterModelTable): def database_backwards(self, app_label, schema_editor, from_state, to_state): print( ''nothing back on'', app_label, self.name, self.table) class Migration(migrations.Migration): dependencies = [ (''cars'', ''0001_initial''), ] database_operations= [ AlterModelTableF( ''tires'', ''tires_tires'' ), ] operations = [ migrations.SeparateDatabaseAndState( database_operations= database_operations) ] #cars/migrations/0004...py : class AlterModelTableR( migrations.AlterModelTable): def database_forwards(self, app_label, schema_editor, from_state, to_state): print( ''nothing forw on'', app_label, self.name, self.table) def database_backwards(self, app_label, schema_editor, from_state, to_state): super().database_forwards( app_label, schema_editor, from_state, to_state) class Migration(migrations.Migration): dependencies = [ (''cars'', ''0003_auto_20150603_0630''), ] # This needs to be a state-only operation because the database model was renamed, and no longer exists according to Django. state_operations = [ migrations.DeleteModel( name=''Tires'', ), ] database_operations= [ AlterModelTableR( ''tires'', ''tires_tires'' ), ] operations = [ # After this state operation, the Django DB state should match the actual database structure. migrations.SeparateDatabaseAndState( state_operations=state_operations, database_operations=database_operations) ]


Migrando un modelo entre aplicaciones.

La respuesta corta es, ¡no lo hagas!

Pero esa respuesta rara vez funciona en el mundo real de proyectos vivos y bases de datos de producción. Por lo tanto, he creado un repositorio de GitHub de muestra para demostrar este proceso bastante complicado.

Estoy usando MySQL. (No, esas no son mis credenciales reales).

El problema

El ejemplo que estoy usando es un proyecto de fábrica con una aplicación de automóviles que inicialmente tiene un modelo de Car y un modelo de Tires .

factory |_ cars |_ Car |_ Tires

El modelo de Car tiene una relación ForeignKey con Tires . (Como en, especifique los neumáticos a través del modelo de automóvil).

Sin embargo, pronto nos damos cuenta de que Tires va a ser un modelo grande con sus propios puntos de vista, etc., y por lo tanto lo queremos en su propia aplicación. La estructura deseada es por lo tanto:

factory |_ cars |_ Car |_ tires |_ Tires

Y debemos mantener la relación ForeignKey entre Car y Tires porque demasiado depende de la preservación de los datos.

La solución

Paso 1. Configurar la aplicación inicial con mal diseño.

Navegue a través del código del paso 1.

Paso 2. Cree una interfaz de administrador y agregue un grupo de datos que contengan relaciones ForeignKey.

Ver el paso 2.

Paso 3. Decida mover el modelo de Tires a su propia aplicación. Meticulosamente cortar y pegar el código en la nueva aplicación de neumáticos. Asegúrese de actualizar el modelo de Car para que apunte a los tires.Tires nuevos. Modelo de tires.Tires .

A continuación, ejecute ./manage.py makemigrations y ./manage.py makemigrations copia de seguridad de la base de datos en algún lugar (en caso de que esto falle horriblemente).

Finalmente, ejecute ./manage.py migrate y vea el mensaje de error de doom,

django.db.utils.IntegrityError: (1217, ''No se puede eliminar o actualizar una fila primaria: falla una restricción de clave externa'')

Ver código y migraciones hasta el momento en el paso 3.

Paso 4. La parte difícil. La migración generada automáticamente no ve que simplemente ha copiado un modelo a una aplicación diferente. Entonces, tenemos que hacer algunas cosas para remediar esto.

Puede seguir y ver las migraciones finales con comentarios en el paso 4. Lo probé para verificar que funciona.

Primero, vamos a trabajar en cars . Tienes que hacer una nueva migración vacía. En realidad, esta migración debe ejecutarse antes de la migración creada más recientemente (la que no se ejecutó). Por lo tanto, volví a numerar la migración que creé y cambié las dependencias para ejecutar primero mi migración personalizada y luego la última migración autogenerada para la aplicación de cars .

Puede crear una migración vacía con:

./manage.py makemigrations --empty cars

Paso 4.a. Realice una migración old_app personalizada.

En esta primera migración personalizada, solo realizaré una migración de "database_operations". Django le ofrece la opción de dividir las operaciones de "estado" y "base de datos". Puede ver cómo se hace esto al ver el código aquí .

Mi objetivo en este primer paso es cambiar el nombre de las tablas de la base de datos de oldapp_model a newapp_model sin newapp_model con el estado de Django. Debes averiguar qué nombre habría dado Django a tu tabla de base de datos en función del nombre de la aplicación y el modelo.

Ahora está listo para modificar la migración inicial de tires .

Paso 4.b. Modificar la migración inicial new_app

Las operaciones están bien, pero solo queremos modificar el "estado" y no la base de datos. ¿Por qué? Porque mantenemos las tablas de la base de datos de la aplicación de cars . Además, debe asegurarse de que la migración personalizada previamente realizada sea una dependencia de esta migración. Ver el archivo de migración de neumáticos.

Por lo tanto, ahora hemos cambiado el nombre de los cars.Tires a tires.Tires la base de datos y cambia el estado de Django para reconocer los tires.Tires . Tabla de tires.Tires .

Paso 4.c. Modifica la última migración autogenerada de old_app .

Volviendo a los automóviles, tenemos que modificar esa última migración autogenerada. Debería requerir nuestra primera migración de automóviles personalizados y la migración inicial de neumáticos (que acabamos de modificar).

Aquí deberíamos dejar las operaciones de AlterField porque el modelo de Car apunta a un modelo diferente (aunque tiene los mismos datos). Sin embargo, debemos eliminar las líneas de migración relacionadas con DeleteModel porque el modelo cars.Tires ya no existe. Se ha convertido completamente en tires.Tires . tires.Tires . Ver esta migración

Paso 4.d. Limpiar el modelo obsoleto en old_app .

Por último, pero no menos importante, debe hacer una migración personalizada final en la aplicación de autos. Aquí, haremos una operación de "estado" solo para eliminar los cars.Tires . Modelo de cars.Tires . Es solo de estado porque la tabla de la base de datos para cars.Tires ya han sido renombrados. Esta última migración limpia el estado restante de Django.