Cómo mover un modelo entre dos aplicaciones Django (Django 1.7)
Así que hace aproximadamente un año empecé un proyecto y como todos los nuevos desarrolladores que realmente no se centran demasiado en la estructura, sin embargo ahora estoy más junto con Django ha comenzado a aparecer que el diseño de mi proyecto principalmente mis modelos son horribles en la estructura.
Tengo modelos principalmente en una sola aplicación y realmente la mayoría de estos modelos deben estar en sus propias aplicaciones individuales, traté de resolver esto y moverlos con el sur, sin embargo, me pareció complicado y muy difícil debido a llaves extranjeras ect.
Sin embargo, debido a Django 1.7 y el soporte integrado para migraciones, ¿hay una mejor manera de hacer esto ahora?
11 answers
Estoy eliminando la respuesta anterior, ya que puede resultar en la pérdida de datos. Como ozan mencionó, podemos crear 2 migraciones una en cada aplicación.
Primera migración para eliminar el modelo de la 1a 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 crear la nueva tabla en la 2a aplicación. Después de mover el código del modelo a 2nd app
$ python manage.py makemigrations new_app
Y editar 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)
]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-09 16:05:51
Esto se puede hacer con bastante facilidad usando migrations.SeparateDatabaseAndState
. Básicamente, usamos 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 antigua
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)
]
Añadir a la nueva aplicación
Primero, copie el modelo a la nueva aplicación model.py, entonces:
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
tal que no intentemos recrear la tabla. También incluya 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)
]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-20 18:30:12
Me encontré con el mismo problema. La respuesta de Ozan me ayudó mucho, pero desafortunadamente no fue suficiente. De hecho, tenía varios enlaces ForeignKey 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 la gente.
Necesitas 2 pasos más:
- Antes de hacer nada, cambia todos tus
ForeignKey
enlaces aTheModel
aIntegerfield
. A continuación, ejecutepython manage.py makemigrations
- Después de hacer los pasos de Ozan, vuelve a convertir tus claves foráneas: put back
ForeignKey(TheModel)
en lugar deIntegerField()
. Luego haga las migraciones de nuevo (python manage.py makemigrations
). Luego puede migrar y debería funcionar (python manage.py migrate
)
Espero que ayude. Por supuesto, pruébelo en local antes de probar en producción para evitar malas sorpresas:)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:27
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
- Copia tu modelo (el código) de app1 a app2.
-
Añadir esto a app2.YourModel:
Class Meta: db_table = 'app1_yourmodel'
-
$ python manage.py makemigrations app2
-
Una nueva migración (p.ej. 0009_auto_something.py) se realiza en app2 con una migración.Modelo de creación() instrucción, mueva esta instrucción a la migración inicial de app2 (p. ej. 0001_initial.py) (será como siempre ha estado allí). Y ahora eliminar la migración creada = 0009_auto_something.py
Así como actúas, como app2.YourModel siempre ha estado ahí, ahora elimina la existencia de app1.YourModel de tus migraciones. Significado: comenta las sentencias CreateModel, y cada ajuste o migración de datos que usaste después de eso.
-
Y por supuesto, cada referencia a app1.YourModel tiene que ser cambiado a app2.YourModel a través de su proyecto. Además, no olvide que todas las claves foráneas posibles para app1.Tu modelo en las migraciones tiene que ser cambiado a app2.YourModel
Ahora si lo haces python python manage.py migrar, nada ha cambiado, también cuando lo haces python python manage.py makemigrations, no se ha detectado nada nuevo.
-
Ahora el toque final: eliminar la Meta de Clase de app2.YourModel y hacer python python manage.py makemigrations app2 y python manage.py migrar app2 (si miras esta migración verás 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.
P. S durante la migración verá que content_type app1.yourmodel ha sido eliminado y puede ser eliminado. Puedes decir que sí, pero solo si no lo usas. En caso de que pesadamente confíe en que FKs a ese tipo de contenido esté intacto, no responda sí o no todavía, pero vaya a la base de datos esa vez manualmente y elimine la aplicación contentype 2.yourmodel, y cambiar el nombre de la aplicación contenttype 1.yourmodel to app2.yourmodel, y luego continuar contestando no.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 15:06:51
Recibo migraciones nerviosas de codificación manual (como lo requiere la respuesta de Ozan), por lo que lo siguiente combina las estrategias de Ozan y Michael para minimizar la cantidad de codificación manual requerida:{[79]]}
- Antes de mover cualquier modelo, asegúrese de que está trabajando con una línea de base limpia ejecutando
makemigrations
. - Mueva el código para el Modelo de
app1
aapp2
-
Como recomienda @Michael, apuntamos el nuevo modelo a la antigua tabla de la base de datos usando el Meta
db_table
opción en el modelo" nuevo":class Meta: db_table = 'app1_yourmodel'
Ejecutar
makemigrations
. Esto generaráCreateModel
enapp2
y {[12] {} en[6]}. Técnicamente, estas migraciones se refieren 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 hecho. Según la respuesta de @Ozan, la bandera
state_operations
enSeparateDatabaseAndState
hace esto. Así que envolvemos todo elmigrations
entradas EN AMBOS ARCHIVOS DE MIGRACIÓN conSeparateDatabaseAndState(state_operations=[...])
. Por ejemplo,operations = [ ... migrations.DeleteModel( name='YourModel', ), ... ]
Se convierte en
operations = [ migrations.SeparateDatabaseAndState(state_operations=[ ... migrations.DeleteModel( name='YourModel', ), ... ]) ]
-
EDITAR : También debe asegurarse de que la nueva migración "virtual"
CreateModel
depende de cualquier migración que realmente haya creado o alterado la tabla original. Por ejemplo, si su nuevo migraciones sonapp2.migrations.0004_auto_<date>
(para elCreate
) yapp1.migrations.0007_auto_<date>
(para elDelete
), lo más sencillo es:- Abra
app1.migrations.0007_auto_<date>
y copie su dependenciaapp1
(e. g.('app1', '0006...'),
). Esta es la migración" inmediatamente anterior " enapp1
y debe incluir dependencias en toda la lógica de construcción del modelo real. - Abre
app2.migrations.0004_auto_<date>
y añade la dependencia que acabas de copiar a su listadependencies
.
- Abra
EDITAR: Si tiene ForeignKey
relación(es) con el modelo que está moviendo, lo anterior puede no funcionar. Esto sucede porque:
- Las dependencias no se crean automáticamente para los cambios
ForeignKey
- no queremos para envolver los cambios
ForeignKey
enstate_operations
, debemos asegurarnos de que estén separados de las operaciones de la tabla.
El conjunto "mínimo" de operaciones difiere dependiendo de la situación, pero el siguiente procedimiento debería funcionar para la mayoría/todas las migraciones ForeignKey
:
-
COPIE el modelo de
app1
aapp2
, establezcadb_table
, pero NO cambie ninguna referencia FK. - Ejecute
makemigrations
y envuelva toda la migraciónapp2
enstate_operations
(ver arriba)- Como anteriormente, agregue una dependencia en el
app2
CreateTable
a la últimaapp1
migración
- Como anteriormente, agregue una dependencia en el
- Apunta todas las referencias FK al nuevo modelo. Si no está utilizando referencias de cadena, mueva el modelo antiguo 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 FK realmente deberían ocurrir)- Agregue una dependencia en todas las migraciones
ForeignKey
(es decir,AlterField
) a la migraciónCreateTable
enapp2
(usted necesita esta lista para el siguiente paso, así que haga un seguimiento de ellos). Por ejemplo: - Busque la migración que incluye el
CreateModel
por ejemplo,app2.migrations.0002_auto_<date>
y copie el nombre de esa migración. -
Encuentra todas las migraciones que tienen 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 como una dependencia:class Migration(migrations.Migration): dependencies = [ ('otherapp', '0001_initial'), ('app2', '0002_auto_<date>'), ]
- Agregue una dependencia en todas las migraciones
Eliminar los modelos de
app1
- Ejecutar
makemigrations
y envolver elapp1
migración 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 migracionesAlterField
, por lo que no necesitaba forzarlas 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 vieja tabla y las migraciones de Django lo han convencido de que todo ha sido reubicado apropiadamente. La gran advertencia (de la respuesta de @Michael) es que se crea un nuevo ContentType
para el nuevo modelo. Si enlazas (por ejemplo, por ForeignKey
) a tipos de contenido, necesitarás crear una migración para actualizar la tabla ContentType
.
Quería limpiar después de mí mismo (opciones Meta y nombres de tabla), así que usé el siguiente procedimiento (de @Michael):
- Eliminar la entrada
db_table
Meta - Ejecute
makemigrations
de nuevo para generar la base de datos rename - Edite esta última migración y asegúrese de que depende de la migración
DeleteTable
. No parece que deba ser necesario ya que elDelete
debería ser puramente lógico, pero me he encontrado con errores (por ejemplo,app1_yourmodel
no existe) si no lo hago.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-17 18:42:05
Esto se prueba aproximadamente, así que no se olvide de 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
.
Crear 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 anteriores son XXX0_src_app_old
y XXX0_dst_app_old
.
Agrega una operación que cambia el nombre de table para MoveMe
model y cambia el nombre de app_label en ProjectState a XXX1_dst_app_new
. No olvide agregar dependencia en la migración XXX0_src_app_old
. La migración resultante XXX1_dst_app_new
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'),
]
Añade dependencia de XXX1_dst_app_new
a XXX1_src_app_new
. XXX1_src_app_new
es la migración sin op que se necesita para asegurarse de que las futuras migraciones src_app
se ejecutarán después de XXX1_dst_app_new
.
Mover MoveMe
de src_app/models.py
a dst_app/models.py
. A continuación, ejecutar:
python manage.py migrate
Eso es todo!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-12 19:09:43
Puedes probar lo siguiente (no probado):
- mueva el modelo de
src_app
adest_app
- migrate
dest_app
; asegúrese de que la migración del esquema depende de la última migraciónsrc_app
( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files ) - agregue una migración de datos a
dest_app
, que copie todos los datos desrc_app
- migrate
src_app
; asegúrese de que la migración del esquema depende de la última migración (de datos) dedest_app
that es decir: la migración de step 3
Tenga en cuenta que será copiandotoda la tabla, en lugar de moviéndola, 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.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-13 13:18:41
Digamos que está moviendo el modelo del modelo 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 alterando el modelo 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 app_a migraciones, la cambia a 'app_b.TheModel'.
Acabo de hacer esto para un proyecto existente, donde quería extraer un cierto modele 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 definido manualmente.db_table para mi modelo que podría haber ayudado.
Notablemente, terminará con un historial de migración alterado. Esto no importa, incluso si tiene una base de datos con las migraciones originales aplicadas. Si las migraciones original y reescrita terminan con el mismo esquema de base de datos, entonces dicha reescritura debe ser OK.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-13 07:11:10
- cambie los nombres de los modelos antiguos a'model_name_old'
- makemigrations
- hacer nuevos modelos llamados 'model_name_new' con relaciones idénticas en los modelos relacionados (eg. el modelo de usuario ahora tiene usuario.blog_old y usuario.blog_new)
- makemigrations
- escriba una migración personalizada que migre todos los datos a las tablas del nuevo modelo
- pruebe el infierno de estas migraciones comparando copias de seguridad con nuevas copias de base de datos antes y después de ejecutar el migraciones
- cuando todo sea satisfactorio, elimine los modelos antiguos
- makemigrations
- cambie los nuevos modelos al nombre correcto 'model_name_new' - > 'model_name'
- pruebe toda la serie 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 necesita ser movido. No sugeriría hacer lo que la otra respuesta dice por cambiar a enteros y volver a claves foráneas Existe la posibilidad de que las nuevas claves foráneas sean diferentes y las filas puedan tener diferentes ID después de las migraciones y no quería correr ningún riesgo de que no coincidan los ID al cambiar de nuevo a las claves foráneas.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-04 05:25:20
Otra alternativa de hackeo si los datos no son grandes o demasiado complicados, pero aún así importantes de mantener, es:
- Obtenga los accesorios de datos usando manage.py dumpdata
- Proceda correctamente a los cambios y migraciones del modelo, sin relacionar los cambios
- Global reemplace los accesorios del modelo antiguo y los nombres de las aplicaciones por los nuevos
- Cargar datos usando manage.py loaddata
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-20 06:45:07
Copiado de mi respuesta en https://stackoverflow.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 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))
],
),
]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-20 13:23:03