¿Cómo migro un modelo de una aplicación django a una nueva?

Tengo una aplicación django con cuatro modelos. Ahora me doy cuenta de que uno de estos modelos debería estar en una aplicación separada. Tengo instalado South para migraciones, pero no creo que esto sea algo que pueda manejar automáticamente. ¿Cómo puedo migrar uno de los modelos de la aplicación anterior a uno nuevo?

Además, tenga en cuenta que voy a necesitar que este sea un proceso repetible, de modo que pueda migrar el sistema de producción y demás.

Cómo migrar usando el sur

Digamos que tenemos dos aplicaciones: comunes y específicas:

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 el modelo common.models.cat a una aplicación específica (precisamente a specific.models.cat). Primero haga los cambios en el código fuente y luego 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 necesitamos editar ambos 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 ambas migraciones de aplicaciones son conscientes del cambio y la vida apesta un poco menos 🙂 Establecer esta relación entre migraciones es la clave del éxito. Ahora si lo haces:

 python manage.py migrate common > specific: 0003_create_cat > common: 0003_drop_cat 

hará ambas migraciones, y

 python manage.py migrate specific 0002_create_dog < common: 0003_drop_cat < specific: 0003_create_cat 

migrará las cosas.

Tenga en cuenta que para actualizar el esquema utilicé la aplicación común y, para la degradación, utilicé una aplicación específica. Eso es porque la dependencia aquí funciona.

Para aprovechar la respuesta de Potr Czachur , las situaciones que involucran a ForeignKeys son más complicadas y deben manejarse de forma ligeramente diferente.

(El siguiente ejemplo se basa en las aplicaciones common y specific a las que se hace referencia en la respuesta actual).

 # common/models.py class Cat(models.Model): # ... class Toy(models.Model): belongs_to = models.ForeignKey(Cat) # ... 

luego cambiaría 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): # ... 

Corriendo

 ./manage.py schemamigration common --auto ./manage.py schemamigration specific --auto # or --initial 

Generaría las siguientes migraciones (estoy ignorando intencionalmente los cambios de Django ContentType; vea la respuesta a la que se hace 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 puede ver, el FK debe modificarse para hacer referencia a la nueva tabla. Necesitamos agregar una dependencia para que sepamos el orden en el que se aplicarán las migraciones (y, por lo tanto, que la tabla exista antes de intentar agregarle un FK), pero también debemos asegurarnos de que el retroceso funcione también porque el 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 

Según la documentación del Sur , depends_on asegurará que 0004_auto__add_cat ejecute antes de 0009_auto__del_cat al migrar hacia adelante, 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 fallará al intentar migrar la ForeignKey porque la tabla referenciada de tabla no existiría.

Esperemos que esto esté más cerca de una situación de “mundo real” que las soluciones existentes y alguien encontrará útil esto. ¡Aclamaciones!

Los modelos no están muy bien conectados 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 la base de datos, así que si quieres mover tu aplicación puedes renombrar la tabla de la base de datos a través de una statement SQL ALTER TABLE o, incluso más simple, usar el parámetro db_table en la clase Meta tu modelo para referirse al viejo nombre.

Si ha utilizado ContentTypes o relaciones genéricas en cualquier parte de su código hasta el momento, es probable que desee cambiar el nombre de la app_label del app_label de contenido que apunta al modelo que se está moviendo, para que las relaciones existentes se conserven.

Por supuesto, si no tiene ningún dato para preservar, lo más fácil es abandonar completamente las tablas de la base de datos y ejecutar ./manage.py syncdb nuevamente.

Aquí hay una solución más a la excelente solución de Potr. Agregue lo siguiente a specific / 0003_create_cat

 depends_on = ( ('common', '0002_create_cat'), ) 

A menos que se establezca esta dependencia, South no garantizará que la tabla common_cat exista en el momento en que se ejecuta / 0003_create_cat específico , arrojando un django.db.utils.OperationalError: no such table: common_cat error django.db.utils.OperationalError: no such table: common_cat .

El sur ejecuta migraciones en orden lexicográfico a menos que se establezca explícitamente la dependencia. Dado que lo common es antes de lo specific todas las migraciones common se ejecutarían antes del cambio de nombre de la tabla, por lo que probablemente no se reproduzca en el ejemplo original que muestra Potr. Pero si cambia common nombre de common a app2 y specific para la app1 , se encontrará con este problema.

El proceso en el que actualmente me he decidido desde que volví aquí varias veces y decidí formalizarlo.

Esto fue originalmente construido sobre la respuesta de Potr Czachur y la respuesta de Matt Briançon , usando el Sur 0.8.4

Paso 1. Descubra las relaciones de la clave externa infantil

 # 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() [, ] 

Entonces en este caso extendido, 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. Crea 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 3. Control de fuente: confirma los cambios hasta el momento.

Lo convierte en un proceso más repetible si se encuentra con conflictos de combinación, como compañeros de equipo que escriben migraciones en las aplicaciones actualizadas.

Paso 4. Agregar dependencias entre las migraciones.

Básicamente create_kittycat depende del estado actual de todo, y todo depende 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 tabla renombra el cambio 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. Solo si necesita retroceder () para trabajar Y obtener un 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  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. Pruébalo: lo que funciona para mí puede no ser suficiente para tu situación real 🙂

 python manage.py migrate # If you need backwards to work python manage.py migrate specific  

Así que usar la respuesta original de @Potr anterior no me funcionó en South 0.8.1 y Django 1.5.1. Estoy publicando lo que funcionó para mí a continuación con la esperanza de que sea útil para otros.

 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';") 

Voy a dar una versión más explícita de una de las cosas que Daniel Roseman sugirió en su respuesta …

Si solo cambia el db_table Meta db_table del modelo que ha movido para apuntar al nombre de la tabla existente (en lugar del nuevo nombre que le daría Django si dejara caer e hizo un syncdb ), entonces puede evitar migraciones complicadas del Sur. p.ej:

Original:

 # app1/models.py class MyModel(models.Model): ... 

Después de mudarse:

 # app2/models.py class MyModel(models.Model): class Meta: db_table = "app1_mymodel" 

Ahora solo necesita hacer una migración de datos para actualizar la app_label para MyModel en la tabla django_content_type y debería estar app_label para MyModel

Ejecute ./manage.py datamigration django update_content_type y edite el archivo que South crea 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()