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


Tengo una aplicación django con cuatro modelos en ella. 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 antigua a uno nuevo?

También, tenga en cuenta que voy a necesitar esto para ser un proceso repetible, por lo que puedo migrar el sistema de producción y tal.

Author: Welbog, 2009-08-11

7 answers

Cómo migrar usando south.

Digamos que tenemos dos aplicaciones: común y específica:

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 la 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 clave para é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 hacia abajo.

Tenga en cuenta que para actualizar el esquema he utilizado la aplicación común y para bajar de categoría, he utilizado la aplicación específica. Eso es porque la dependencia aquí funciona.

 181
Author: Potr Czachur,
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-03-27 00:30:21

Para construir sobre la respuesta de Potr Czachur , las situaciones que involucran llaves extranjeras son más complicadas y deben manejarse de manera 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)
    # ...

Entonces 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-ver referencia anterior respuesta para 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 ser alterado para hacer referencia a la nueva tabla. Necesitamos agregar una dependencia para saber el orden en el que se aplicarán las migraciones (y por lo tanto que la tabla existirá antes de intentar agregar un FK a ella) pero también necesitamos asegurarnos de que rodar hacia atrás también funciona porque 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 se asegurará de que 0004_auto__add_cat se ejecuta antes 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 fallaría al intentar migrar la ForeignKey porque la tabla a la que se hace referencia no existiría.

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

 35
Author: Matt Briançon,
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 10:29:40

Los modelos no están muy acoplados 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, por lo que si desea mover su aplicación, puede cambiar el nombre de la tabla de la base de datos a través de una instrucción SQL ALTER TABLE, o db_table parámetro en la clase Meta de su modelo para referirse al nombre antiguo.

Si ha utilizado ContentTypes o relaciones genéricas en cualquier parte de su código hasta ahora, probablemente querrá cambiar el nombre de app_label de la contenttype apuntando al modelo que se está moviendo, para que las relaciones existentes se conserven.

Por supuesto, si no tiene ningún dato que preservar, lo más fácil es eliminar las tablas de la base de datos por completo y ejecutar ./manage.py syncdb de nuevo.

 7
Author: Daniel Roseman,
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
2009-08-11 06:40:17

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

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

A menos que esta dependencia esté establecida South, no garantizará que la tabla common_cat exista en el momento en que se ejecute/0003_create_cat específico, arrojándole un error django.db.utils.OperationalError: no such table: common_cat.

South ejecuta las migraciones en orden lexicográfico a menos que se establezca explícitamente la dependencia. Dado que common viene antes de specific todas las migraciones de common se ejecutarían antes de cambiar el nombre de la tabla, por lo que probablemente no se reproduciría en el ejemplo original mostrado por Potr. Pero si cambia el nombre de common a app2 y specific a app1 se encontrará con este problema.

 4
Author: Ihor Kaharlichenko,
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-01-24 10:38:22

El proceso que he establecido actualmente desde que he estado aquí un par de veces y decidí formalizarlo.

Esto fue construido originalmente en Respuesta de Potr Czachur y La respuesta de Matt Briançon, usando South 0.8.4

Paso 1. Descubra las relaciones de clave foránea del niño

# 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 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. 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 3. Control de código fuente: Commit cambios hasta ahora.

Hace que sea un proceso más repetible si se encuentra con conflictos de fusión, como compañeros de equipo escribiendo migraciones en las aplicaciones actualizadas.

Paso 4. Agregue dependencias entre las migraciones.

Básicamente create_kittycat depende del estado actual de todo, y todo dependerá, 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. El cambio de nombre de la tabla 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 necesitas backwards() para trabajar Y obtener un KeyError corriendo 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. Pruébelo-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>
 4
Author: pzrq,
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 11:55:13

Así que usar la respuesta original de @Potr anterior no funcionó para mí en el Sur 0.8.1 y Django 1.5.1. Estoy publicando lo que hizo trabaja para mí abajo con la esperanza de que sea útil para 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';")
 3
Author: Tim Sutton,
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
2013-12-13 10:07:16

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

Si cambia el atributo Meta db_table del modelo que ha movido para que apunte al nombre de la tabla existente (en lugar del nuevo nombre que Django le daría si se cae y hace un syncdb), entonces puede evitar migraciones complicadas del Sur. eg:

Original:

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

Después de moverse:

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

Ahora solo necesita 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...

Ejecuta ./manage.py datamigration django update_content_type luego edita el archivo que South crea para ti:

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()
 1
Author: Anentropic,
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-01-23 08:00:57