Question

J'ai une application django avec quatre modèles en elle. Je me rends compte maintenant que l'un de ces modèles devrait être dans une application séparée. J'ai installé au sud pour les migrations, mais je ne pense pas que ce soit quelque chose qu'il peut gérer automatiquement. Comment puis-je migrer l'un des modèles de l'ancienne application dans un nouveau?

De plus, gardez à l'esprit que je vais avoir besoin de ce être un processus répétitif, afin que je puisse migrer le système de production et autres.

Était-ce utile?

La solution

Comment migrer en utilisant sud.

Disons que nous avons eu deux applications: communs et spécifiques:

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   `-- 0002_create_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   `-- 0002_create_dog.py
    `-- models.py

Maintenant, nous voulons déplacer modèle common.models.cat à l'application spécifique (précisément à specific.models.cat). Tout d'abord faire les changements dans le code source et puis exécutez:

$ 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

Maintenant, nous devons modifier les fichiers de migration:

#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

Maintenant, les deux migrations d'applications sont au courant du changement et de la vie suce juste un peu moins :-) La définition de cette relation entre les migrations est la clé du succès. Maintenant, si vous faites:

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

fera à la fois la migration, et

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

migreront les choses.

Notez que pour la mise à niveau du schéma je application commune et pour déclasser, je application spécifique. En effet, comment fonctionne la dépendance ici.

Autres conseils

Pour construire sur de Potr Czachur réponse , les situations qui impliquent ForeignKeys sont plus complexes et doivent être traitées de façon légèrement différente.

(L'exemple suivant fait fond sur les applications common et specific visés au courant dans la réponse).

# common/models.py

class Cat(models.Model):
    # ...

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

changerait alors à

# common/models.py

from specific.models import Cat

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

# specific/models.py

class Cat(models.Model):
    # ...

Exécution

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

générerait ce qui suit les migrations (j'ignore volontairement les changements à voir Django ContentType précédemment référencé réponse pour savoir comment gérer cela):

# 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')

Comme vous pouvez le voir, le FK doit être modifié pour faire référence à la nouvelle table. Nous devons ajouter une dépendance afin que nous sachions l'ordre dans lequel les migrations seront appliquées (et donc que la table existera avant d'essayer d'ajouter un FK à), mais nous devons aussi veiller à rouler fonctionne en arrière aussi parce que < strong> applique la dépendance dans le sens inverse .

# 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

Par la documentation du Sud , depends_on veillera à ce que 0004_auto__add_cat fonctionne avant 0009_auto__del_cat lors de la migration vers l'avant , mais dans le sens opposé afin lors de la migration vers l'arrière . Si nous avons quitté db.rename_table('specific_cat', 'common_cat') dans le rollback specific, l'annulation de common échouait lors de la tentative de migration du ForeignKey parce que la table de référence n'existerait pas.

Espérons que cela est plus proche d'une situation de « monde réel » que les solutions existantes et quelqu'un trouve cela utile. Vive!

Les modèles ne sont pas très étroitement couplés à des applications, si émouvant est assez simple. Django utilise le nom de l'application au nom de la table de base de données, donc si vous voulez déplacer votre application, vous pouvez renommer la table de base de données via une instruction ALTER TABLE SQL, ou - encore plus simple - il suffit d'utiliser la paramètre db_table dans la classe Meta de votre modèle pour faire référence à l'ancien nom.

Si vous avez utilisé ContentTypes ou relations génériques partout dans votre code jusqu'à présent, vous voudrez probablement renommer le app_label du pointage contenttype au modèle qui bouge, de sorte que les relations existantes sont préservées.

Bien sûr, si vous ne disposez pas de données à tout pour préserver, la meilleure chose à faire est de laisser tomber les tables de base de données complètement et exécutez ./manage.py syncdb à nouveau.

Voici une autre solution à la solution excellente de Potr. Ajouter ce qui suit à spécifique / 0003_create_cat

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

A moins que cette dépendance est situé dans le sud ne garantit pas que la table common_cat existe au moment où spécifique / 0003_create_cat est exécuté, lancer une erreur de django.db.utils.OperationalError: no such table: common_cat à vous.

Sud va migrations dans ordre lexicographique sauf si la dépendance est explicitement ensemble. Depuis common vient avant specific toutes les migrations de common obtiendraient courir avant changement de nom de la table, il ne serait probablement pas se reproduire dans l'exemple original montré par Potr. Mais si vous renommez common à app2 et specific à app1 vous courrez dans ce problème.

Le processus que j'ai actuellement installés depuis que je suis de retour ici à quelques reprises et a décidé de le formaliser.

a été construit à l'origine sur Potr réponse Czachur et Matt réponse de Briançon, en utilisant du Sud 0.8.4

Étape 1. Découvrez l'enfant des relations clés étrangères

# 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>]

Donc dans ce cas étendu, nous avons découvert un autre modèle lié comme:

# 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)

    ...

Étape 2. Créer des migrations

# 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

Étape 3. Contrôle de la Source: Valider les modifications jusqu'à présent

.

en fait un processus plus reproductible si vous avez un conflit de fusion comme ses coéquipiers à écrire sur les migrations des applications mises à jour.

Étape 4. Ajouter les dépendances entre les migrations.

En gros create_kittycat dépend de l'état actuel de tout, et tout dépend alors 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'),
    )
    ...

Étape 5. Le tableau renomme le changement que nous voulons faire.

# 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

Étape 6. Seulement si vous avez besoin en arrière () pour travailler et obtenir un KeyError en cours d'exécution en arrière.

# 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']})
        },
        ...
    }

Étape 7. Testez - ce qui fonctionne pour moi peut-être pas assez pour votre situation de vie réelle:)

python manage.py migrate

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

Donc, en utilisant la réponse originale de @Potr ci-dessus ne fonctionne pas pour moi sur South 0.8.1 et Django 1.5.1. Je signale ce fait travailler pour moi ci-dessous dans l'espoir qu'il est utile pour les autres.

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

Je vais donner une version plus explicite de l'une des choses Daniel Roseman suggéré dans sa réponse ...

Si vous changez juste le db_table attribut Meta du modèle que vous avez déplacé pour pointer vers le nom de la table existante (au lieu du nouveau nom Django lui donnerait si vous laissiez tomber et a fait un syncdb) alors vous pouvez éviter complexes migrations Sud. par exemple:

Original:

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

Après avoir déménagé:

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

Maintenant, vous avez juste besoin de faire une migration de données mise à jour du app_label pour MyModel dans la table django_content_type et vous devriez être bon d'aller ...

Exécuter ./manage.py datamigration django update_content_type puis modifiez le fichier du Sud crée pour vous:

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()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top