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
anew_app
, luego actualice las sentencias deimport
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
enstate_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
dondeAlterModelTable
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 deAlterField
, solo operacionesDeleteModel
yRemoveField
. O mantenlo conoperations = []
vacíasoperations = []
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.