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?
- cambiar los nombres de modelos antiguos a ''model_name_old''
- hacer migraciones
- 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)
- hacer migraciones
- escribir una migración personalizada que migre todos los datos a las nuevas tablas del modelo
- pruebe al máximo estas migraciones comparando las copias de seguridad con nuevas copias de db antes y después de ejecutar las migraciones
- cuando todo sea satisfactorio, elimine los modelos antiguos
- hacer migraciones
- cambie los nuevos modelos al nombre correcto ''model_name_new'' -> ''model_name''
- probar toda la gran cantidad de migraciones en un servidor provisional
- 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
- Copie YourModel (el código) de app1 a app2.
-
agregue esto a app2.YourModel:
Class Meta: db_table = ''app1_yourmodel''
-
$ python manage.py makemigrations app2
-
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
-
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.
-
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
-
Ahora, si hace $ python manage.py migrate, nada ha cambiado, también cuando hace $ python manage.py makemigrations, no se ha detectado nada nuevo.
-
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.
- 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:
-
Antes de hacer nada, cambie todos los enlaces de
ForeignKey
aTheModel
enIntegerfield
. Luego ejecutepython manage.py makemigrations
-
Después de seguir los pasos de Ozan, vuelva a convertir sus claves foráneas: vuelva a colocar
ForeignKey(TheModel)
lugar deIntegerField()
. 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:
-
Antes de mover cualquier modelo, asegúrese de estar trabajando con una línea base limpia ejecutando
makemigrations
. -
Mueva el código para el Modelo de
app1
aapp2
-
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''
-
Ejecute
makemigrations
. Esto generaráCreateModel
enapp2
yDeleteModel
enapp1
. 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. -
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
enSeparateDatabaseAndState
hace esto. Así que envolvemos todas las entradas demigrations
EN AMBOS ARCHIVOS DE MIGRACIONES conSeparateDatabaseAndState(state_operations=[...])
. Por ejemplo,operations = [ ... migrations.DeleteModel( name=''YourModel'', ), ... ]
se convierte
operations = [ migrations.SeparateDatabaseAndState(state_operations=[ ... migrations.DeleteModel( name=''YourModel'', ), ... ]) ]
-
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 sonapp2.migrations.0004_auto_<date>
(paraCreate
) yapp1.migrations.0007_auto_<date>
(paraDelete
), lo más simple es:-
Abra
app1.migrations.0007_auto_<date>
y copie su dependencia deapp1
(p(''app1'', ''0006...''),
Ej.(''app1'', ''0006...''),
). Esta es la migración "inmediatamente anterior" en laapp1
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 dedependencies
.
-
Abra
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
enstate_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
:
-
app1
el modelo deapp1
aapp2
, establezcadb_table
, pero NO cambie ninguna referencia de FK. -
Ejecute
makemigrations
ymakemigrations
toda la migración destate_operations
enstate_operations
(ver arriba)-
Como se
app2
anteriormente, agregue una dependencia enapp2
CreateTable
a la última migración deapp1
-
Como se
-
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. -
Ejecute
makemigrations
pero NO envuelva nada enstate_operations
(los cambios de FK deberían suceder realmente). Agregue una dependencia en todas las migracionesForeignKey
(es decir,AlterField
) a la migraciónCreateTable
enapp2
(necesitará esta lista para el siguiente paso, así queapp2
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>''), ]
-
Encuentre la migración que incluye
-
Eliminar los modelos de
app1
-
Ejecute
makemigrations
yapp1
migración destate_operations
enstate_operations
.-
Agregue una dependencia a todas las migraciones
ForeignKey
(es decir,AlterField
) del paso anterior (puede incluir migraciones enapp1
yapp2
). -
Cuando construí estas migraciones,
DeleteTable
ya dependía de las migraciones deAlterField
, por lo que no tuve que aplicarlas manualmente (es decir,Alter
antes deDelete
).
-
Agregue una dependencia a todas las migraciones
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):
-
Eliminar la entrada Meta
db_table
-
Ejecute
makemigrations
nuevamente para generar el cambio de nombre de la base de datos -
Edite esta última migración y asegúrese de que depende de la migración de
DeleteTable
. No parece que deba ser necesario, ya queDelete
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):
-
mover el modelo de
src_app
adest_app
-
dest_app
; asegúrese de que la migración del esquema dependa de la última migración desrc_app
( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files ) -
agregar una migración de datos a
dest_app
, que copia todos los datos desrc_app
-
migrar
src_app
; asegúrese de que la migración del esquema dependa de la última migración (datos) dedest_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!