¿Cómo puedo migrar de un modelo de una aplicación de django y en una nueva?
-
12-09-2019 - |
Pregunta
Tengo una aplicación de django con cuatro modelos en ella.Ahora me doy cuenta de que uno de estos modelos debe ser en una aplicación separada.Yo tengo el sur instalado para las migraciones, pero no creo que esto es algo que se puede gestionar de forma automática.¿Cómo puedo migrar uno de los modelos de la antigua aplicación en una nueva?
Además, tenga en mente que voy a necesitar esto para ser un proceso repetible, por lo que puedo migrar el sistema de producción y tal.
Solución
Cómo migrar el uso del sur.
Digamos que tenemos dos aplicaciones:comunes y específicos:
myproject/
|-- common
| |-- migrations
| | |-- 0001_initial.py
| | `-- 0002_create_cat.py
| `-- models.py
`-- specific
|-- migrations
| |-- 0001_initial.py
| `-- 0002_create_dog.py
`-- models.py
Ahora queremos mover modelo común.modelos.gato de aplicación específica (precisamente a lo más específico.modelos.cat).Primero hacer los cambios en el código fuente y, a continuación, ejecute:
$ python manage.py schemamigration specific create_cat --auto
+ Added model 'specific.cat'
$ python manage.py schemamigration common drop_cat --auto
- Deleted model 'common.cat'
myproject/
|-- common
| |-- migrations
| | |-- 0001_initial.py
| | |-- 0002_create_cat.py
| | `-- 0003_drop_cat.py
| `-- models.py
`-- specific
|-- migrations
| |-- 0001_initial.py
| |-- 0002_create_dog.py
| `-- 0003_create_cat.py
`-- models.py
Ahora tenemos que editar los archivos de migración:
#0003_create_cat: replace existing forward and backward code
#to use just one sentence:
def forwards(self, orm):
db.rename_table('common_cat', 'specific_cat')
if not db.dry_run:
# For permissions to work properly after migrating
orm['contenttypes.contenttype'].objects.filter(
app_label='common',
model='cat',
).update(app_label='specific')
def backwards(self, orm):
db.rename_table('specific_cat', 'common_cat')
if not db.dry_run:
# For permissions to work properly after migrating
orm['contenttypes.contenttype'].objects.filter(
app_label='specific',
model='cat',
).update(app_label='common')
#0003_drop_cat:replace existing forward and backward code
#to use just one sentence; add dependency:
depends_on = (
('specific', '0003_create_cat'),
)
def forwards(self, orm):
pass
def backwards(self, orm):
pass
Ahora tanto las migraciones son conscientes del cambio y la vida apesta un poco menos :-) La configuración de esta relación entre las migraciones es la clave del éxito.Ahora si que debes hacer:
python manage.py migrate common
> specific: 0003_create_cat
> common: 0003_drop_cat
va a hacer la migración, y
python manage.py migrate specific 0002_create_dog
< common: 0003_drop_cat
< specific: 0003_create_cat
migrar las cosas.
Observe que para la actualización de esquema que he utilizado comunes de la app y de la rebaja, he utilizado una aplicación específica.Eso es porque la manera en que la dependencia de aquí trabaja.
Otros consejos
Para construir sobre Potr Czachur 's respuesta , situaciones que implican ForeignKeys son más complicados y deben ser manejados de forma ligeramente diferente.
(El siguiente ejemplo se basa en las aplicaciones common
y specific
se refiere a la en la respuesta actual).
# common/models.py
class Cat(models.Model):
# ...
class Toy(models.Model):
belongs_to = models.ForeignKey(Cat)
# ...
cambiaría entonces a
# common/models.py
from specific.models import Cat
class Toy(models.Model):
belongs_to = models.ForeignKey(Cat)
# ...
# specific/models.py
class Cat(models.Model):
# ...
Ejecutar
./manage.py schemamigration common --auto
./manage.py schemamigration specific --auto # or --initial
generaría los siguientes (las migraciones estoy ignorando intencionadamente Django ContentType cambia-véase la respuesta referencia anteriormente para saber cómo manejar eso):
# common/migrations/0009_auto__del_cat.py
class Migration(SchemaMigration):
def forwards(self, orm):
db.delete_table('common_cat')
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))
def backwards(self, orm):
db.create_table('common_cat', (
# ...
))
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
# specific/migrations/0004_auto__add_cat.py
class Migration(SchemaMigration):
def forwards(self, orm):
db.create_table('specific_cat', (
# ...
))
def backwards(self, orm):
db.delete_table('specific_cat')
Como se puede ver, el FK debe ser modificado para hacer referencia a la nueva tabla. Tenemos que añadir una dependencia por lo que sabemos que el orden en el que se aplicarán las migraciones (y por lo tanto que existirá la mesa antes de tratar de añadir un FK a ella), pero también hay que asegurarse de que se vaya hacia atrás también funciona porque < strong> la dependencia se aplica en la dirección inversa .
# common/migrations/0009_auto__del_cat.py
class Migration(SchemaMigration):
depends_on = (
('specific', '0004_auto__add_cat'),
)
def forwards(self, orm):
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))
def backwards(self, orm):
db.rename_table('specific_cat', 'common_cat')
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
# specific/migrations/0004_auto__add_cat.py
class Migration(SchemaMigration):
def forwards(self, orm):
db.rename_table('common_cat', 'specific_cat')
def backwards(self, orm):
pass
Sur documentación , depends_on
se asegurará de que se ejecuta antes de 0004_auto__add_cat
0009_auto__del_cat
al migrar hacia delante , pero en el orden opuesto al migrar hacia atrás . Si dejamos db.rename_table('specific_cat', 'common_cat')
en la reversión specific
, la reversión common
fracasaría al intentar migrar el ForeignKey porque no existiría la tabla tabla de referencia.
Esperamos que esto se acerca más a una situación "mundo real" que las soluciones existentes y alguien encontrarán útil esta información. Saludos!
Los modelos no están muy estrechamente vinculadas a las aplicaciones, por lo que moverse es bastante simple. Django usa el nombre de la aplicación en el nombre de la tabla de base de datos, por lo que si desea mover su aplicación que puede o bien cambiar el nombre de la tabla de base de datos a través de un comunicado ALTER TABLE
SQL o - aún más simple - sólo tiene que utilizar el db_table
parámetro en clase Meta
de su modelo para referirse al antiguo nombre.
Si ha utilizado TiposContenido o relaciones genéricas cualquier parte del código hasta el momento, es probable que desee cambiar el nombre del app_label
del señalador contenttype en el modelo que se está moviendo, por lo que las relaciones existentes se conservan.
Por supuesto, si usted no tiene ningún dato en absoluto para preservar, lo más fácil de hacer es eliminar las tablas de base de datos y ejecutar completamente ./manage.py syncdb
de nuevo.
He aquí una solución más que excelente solución de Potr. Agregue lo siguiente a específica / 0003_create_cat
depends_on = (
('common', '0002_create_cat'),
)
A menos que esta dependencia se establece Sur no garantiza que la tabla common_cat
existe en el momento en específica / 0003_create_cat es correr, lanzar un error django.db.utils.OperationalError: no such table: common_cat
en usted.
Sur corre migraciones en lexicográfico fin menos que la dependencia es explícitamente conjunto. Desde common
viene antes specific
todas las migraciones del common
serían ubicadas antes del cambio de nombre de la tabla, por lo que probablemente no se reproducirían en el ejemplo original mostrada por Potr. Sin embargo, si cambia el nombre common
a app2
y specific
a app1
que se ejecutará en este problema.
El proceso que he actualmente se asentaron en desde que he estado aquí unas cuantas veces y decidió formalizar la misma.
Este fue construido originalmente en de Potr Czachur respuesta y de Matt Briançon respuesta , 0.8.4 usando Sur
Paso 1. Descubrir niño relaciones de clave externa
# Caution: This finds OneToOneField and ForeignKey.
# I don't know if this finds all the ways of specifying ManyToManyField.
# Hopefully Django or South throw errors if you have a situation like that.
>>> Cat._meta.get_all_related_objects()
[<RelatedObject: common:toy related to cat>,
<RelatedObject: identity:microchip related to cat>]
Así que en este caso prolongado, hemos descubierto otro modelo relacionado como:
# Inside the "identity" app...
class Microchip(models.Model):
# In reality we'd probably want a ForeignKey, but to show the OneToOneField
identifies = models.OneToOneField(Cat)
...
Paso 2. Crear migraciones
# Create the "new"-ly renamed model
# Yes I'm changing the model name in my refactoring too.
python manage.py schemamigration specific create_kittycat --auto
# Drop the old model
python manage.py schemamigration common drop_cat --auto
# Update downstream apps, so South thinks their ForeignKey(s) are correct.
# Can skip models like Toy if the app is already covered
python manage.py schemamigration identity update_microchip_fk --auto
Paso de control 3. Fuente: Registrar los cambios hasta el momento
.Hace que sea un proceso más repetible si llegas a tener conflictos de fusión como compañeros que escriben las migraciones en las aplicaciones actualizadas.
Paso 4. Añadir las dependencias entre las migraciones.
Básicamente create_kittycat
depende del estado actual de todo, y todo depende entonces de create_kittycat
.
# create_kittycat
class Migration(SchemaMigration):
depends_on = (
# Original model location
('common', 'the_one_before_drop_cat'),
# Foreign keys to models not in original location
('identity', 'the_one_before_update_microchip_fk'),
)
...
# drop_cat
class Migration(SchemaMigration):
depends_on = (
('specific', 'create_kittycat'),
)
...
# update_microchip_fk
class Migration(SchemaMigration):
depends_on = (
('specific', 'create_kittycat'),
)
...
Paso 5. La mesa de cambio de nombre que queremos hacer.
# create_kittycat
class Migration(SchemaMigration):
...
# Hopefully for create_kittycat you only need to change the following
# 4 strings to go forward cleanly... backwards will need a bit more work.
old_app = 'common'
old_model = 'cat'
new_app = 'specific'
new_model = 'kittycat'
# You may also wish to update the ContentType.name,
# personally, I don't know what its for and
# haven't seen any side effects from skipping it.
def forwards(self, orm):
db.rename_table(
'%s_%s' % (self.old_app, self.old_model),
'%s_%s' % (self.new_app, self.new_model),
)
if not db.dry_run:
# For permissions, GenericForeignKeys, etc to work properly after migrating.
orm['contenttypes.contenttype'].objects.filter(
app_label=self.old_app,
model=self.old_model,
).update(
app_label=self.new_app,
model=self.new_model,
)
# Going forwards, should be no problem just updating child foreign keys
# with the --auto in the other new South migrations
def backwards(self, orm):
db.rename_table(
'%s_%s' % (self.new_app, self.new_model),
'%s_%s' % (self.old_app, self.old_model),
)
if not db.dry_run:
# For permissions, GenericForeignKeys, etc to work properly after migrating.
orm['contenttypes.contenttype'].objects.filter(
app_label=self.new_app,
model=self.new_model,
).update(
app_label=self.old_app,
model=self.old_model,
)
# Going backwards, you probably should copy the ForeignKey
# db.alter_column() changes from the other new migrations in here
# so they run in the correct order.
#
# Test it! See Step 6 for more details if you need to go backwards.
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['common.Cat']))
# drop_cat
class Migration(SchemaMigration):
...
def forwards(self, orm):
# Remove the db.delete_table(), if you don't at Step 7 you'll likely get
# "django.db.utils.ProgrammingError: table "common_cat" does not exist"
# Leave existing db.alter_column() statements here
db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.KittyCat']))
def backwards(self, orm):
# Copy/paste the auto-generated db.alter_column()
# into the create_kittycat migration if you need backwards to work.
pass
# update_microchip_fk
class Migration(SchemaMigration):
...
def forwards(self, orm):
# Leave existing db.alter_column() statements here
db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['specific.KittyCat']))
def backwards(self, orm):
# Copy/paste the auto-generated db.alter_column()
# into the create_kittycat migration if you need backwards to work.
pass
Paso 6. Sólo si necesita hacia atrás () de trabajo y obtener una KeyError corriendo hacia atrás.
# the_one_before_create_kittycat
class Migration(SchemaMigration):
# You many also need to add more models to South's FakeORM if you run into
# more KeyErrors, the trade-off chosen was to make going forward as easy as
# possible, as that's what you'll probably want to do once in QA and once in
# production, rather than running the following many times:
#
# python manage.py migrate specific <the_one_before_create_kittycat>
models = {
...
# Copied from 'identity' app, 'update_microchip_fk' migration
u'identity.microchip': {
'Meta': {'object_name': 'Microchip'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'identifies': ('django.db.models.fields.related.OneToOneField', [], {to=orm['specific.KittyCat']})
},
...
}
Paso 7. Prueba de ello - lo que funciona para mí puede no ser suficiente para su situación de la vida real:)
python manage.py migrate
# If you need backwards to work
python manage.py migrate specific <the_one_before_create_kittycat>
Así que usando la respuesta original de la @Potr anterior no funcionó para mí en Sur 0.8.1 y 1.5.1 Django. Estoy publicar lo que hizo funciona para mí más adelante, con la esperanza de que sea útil a los demás.
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
db.rename_table('common_cat', 'specific_cat')
if not db.dry_run:
db.execute(
"update django_content_type set app_label = 'specific' where "
" app_label = 'common' and model = 'cat';")
def backwards(self, orm):
db.rename_table('specific_cat', 'common_cat')
db.execute(
"update django_content_type set app_label = 'common' where "
" app_label = 'specific' and model = 'cat';")
Me voy a dar una versión más explícita de una de las cosas Daniel Roseman sugirió en su respuesta ...
Si acaba de cambiar el atributo Meta db_table
del modelo se ha mudado a apuntar al nombre de tabla existente (en lugar del nuevo nombre de Django se lo daría si se le cayó e hizo un syncdb
), entonces usted puede evitar las migraciones Sur complicados. por ejemplo:
Original:
# app1/models.py
class MyModel(models.Model):
...
Después de movimiento:
# app2/models.py
class MyModel(models.Model):
class Meta:
db_table = "app1_mymodel"
Ahora sólo tiene que hacer una migración de datos para actualizar el app_label
para MyModel
en la tabla django_content_type
y usted debe ser bueno para ir ...
Ejecutar ./manage.py datamigration django update_content_type
continuación, editar el archivo que crea Sur para usted:
def forwards(self, orm):
moved = orm.ContentType.objects.get(app_label='app1', model='mymodel')
moved.app_label = 'app2'
moved.save()
def backwards(self, orm):
moved = orm.ContentType.objects.get(app_label='app2', model='mymodel')
moved.app_label = 'app1'
moved.save()