Domanda

Ho un app Django con quattro modelli in esso. Mi rendo conto ora che uno di questi modelli dovrebbe essere in un'applicazione separata. Io ho installato per le migrazioni sud, ma non credo che questo è qualcosa che si può gestire automaticamente. Come faccio a migrare uno dei modelli fuori il vecchio app in uno nuovo?

Inoltre, tenete a mente che ho intenzione di bisogno di questo per essere un processo ripetibile, in modo che possa eseguire la migrazione del sistema di produzione e così via.

È stato utile?

Soluzione

Come migrare usando sud.

Diciamo che abbiamo ottenuto due applicazioni: comuni e specifici:

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

Ora vogliamo spostare common.models.cat modello specifico app (proprio per specific.models.cat). In primo luogo apportare le modifiche nel codice sorgente e quindi eseguire:

$ 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

Ora abbiamo bisogno di modificare sia i file di migrazione:

#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

Ora Entrambe le applicazioni migrazioni sono consapevoli del cambiamento e la vita fa schifo solo un po 'meno :-) L'impostazione di questa relazione tra migrazioni è la chiave del successo. Ora, se si fa:

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

farà sia la migrazione, e

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

migreranno le cose.

Si noti che per l'aggiornamento dello schema che ho usato applicazione comune e per il downgrade, ho usato specifica app. Questo perché come funziona la dipendenza qui.

Altri suggerimenti

Per costruire su s 'Potr Czachur risposta , situazioni che coinvolgono ForeignKeys sono più complicate e devono essere maneggiati in modo leggermente diverso.

(Il seguente esempio costruisce le applicazioni common e specific cui alla nella risposta corrente).

# common/models.py

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

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

sarebbe poi passare 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):
    # ...

Esecuzione

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

genera il seguente le migrazioni (sto volutamente ignorando Django ContentType cambia-vedi risposta in precedenza riferimento per sapere come gestire questo):

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

Come si può vedere, l'FK deve essere modificato per fare riferimento alla nuova tabella. Abbiamo bisogno di aggiungere una dipendenza in modo che sappiamo l'ordine in cui saranno applicate le migrazioni (e quindi che la tabella esisterà prima di cercare di aggiungere un FK ad esso), ma abbiamo anche bisogno di assicurarsi che a rotazione funziona al contrario anche perché < strong> la dipendenza applica nella direzione 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

Per la Sud documentazione , depends_on farà in modo che 0004_auto__add_cat eseguito prima 0009_auto__del_cat durante la migrazione in avanti , ma nel ordine inverso durante la migrazione a ritroso . Se abbiamo lasciato db.rename_table('specific_cat', 'common_cat') nel rollback specific, il rollback common fallirebbe quando si cerca di eseguire la migrazione del ForeignKey perché la tabella tabella di riferimento non esisterebbe.

Speriamo che questo è più vicino a una situazione di "mondo reale" rispetto alle soluzioni esistenti e qualcuno troverà questa utile. Cheers!

I modelli non sono molto strettamente accoppiati alle applicazioni, quindi in movimento è abbastanza semplice. Django utilizza il nome dell'app nel nome della tabella di database, quindi se si desidera spostare la vostra applicazione è possibile rinominare la tabella del database tramite un'istruzione ALTER TABLE SQL, o - ancora più semplice - basta usare il parametro db_table in classe Meta del vostro modello per fare riferimento al vecchio nome.

Se hai utilizzato ContentTypes o relazioni generiche qualsiasi punto del codice finora, probabilmente si vuole rinominare il app_label della punta contenttype il modello che si muove, in modo che le relazioni esistenti sono conservati.

Naturalmente, se non si dispone di alcun dato a tutti per preservare, la cosa più semplice da fare è eliminare le tabelle del database completamente ed eseguire nuovamente ./manage.py syncdb.

Ecco un altro fix ad eccellente soluzione Potr. Aggiungere il seguente specifica / 0003_create_cat

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

A meno che questa dipendenza è situato a sud non garantisce che la tabella common_cat esistente al momento in cui specifica / 0003_create_cat è gestito, lanciando un errore django.db.utils.OperationalError: no such table: common_cat a voi.

Sud corre migrazioni in lessicografico ordine a meno che la dipendenza è esplicitamente impostato. Dal momento che viene prima di common specific tutte le migrazioni del common otterrebbero eseguito prima tabella di ridenominazione, quindi probabilmente non sarebbe riprodurre nell'esempio originale mostrato da Potr. Ma se si rinomina common a app2 e specific a app1 si incorrere in questo problema.

Il processo Ho attualmente piazzate sul da quando sono tornato qui un paio di volte e ha deciso di formalizzare esso.

Questo è stato originariamente costruito su di Potr Czachur risposta e di Matt Briançon risposta , utilizzando Sud 0.8.4

Passaggio 1. Scopri bambino relazioni di chiave esterna

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

Quindi, in questo caso estesa, abbiamo scoperto un altro modello correlate come:

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

    ...

Passaggio 2. Creare migrazioni

# 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

Passaggio di controllo 3. Fonte: Conferma modifiche finora

.

lo rende un processo più ripetibile se si esegue in conflitti di unione, come i compagni di squadra che scrivono le migrazioni dalle applicazioni aggiornate.

Passaggio 4. Aggiungere le dipendenze tra le migrazioni.

Fondamentalmente create_kittycat dipende dallo stato attuale di tutto, e tutto dipende poi 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'),
    )
    ...

Passaggio 5. La tabella rinominare cambiamento che vogliamo fare.

# 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

Passaggio 6. Solo se avete bisogno all'indietro () per lavorare e ottenere una KeyError corsa all'indietro.

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

Passaggio 7. Prova che - ciò che funziona per me potrebbe non essere sufficiente per la situazione reale:)

python manage.py migrate

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

Quindi, utilizzando la risposta originale @Potr di cui sopra non ha funzionato per me su South 0.8.1 e 1.5.1 Django. Vi metto quello che ha fatto funziona per me al di sotto, nella speranza che sia utile agli altri.

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

ho intenzione di dare una versione più esplicita di una delle cose Daniel Roseman suggerito nella sua risposta ...

Se basta cambiare il db_table attributo Meta del modello è stata spostata per puntare al nome della tabella esistente (al posto del nuovo nome Django darebbe se si caduto e ha fatto un syncdb), allora si può evitare complicati migrazioni Sud. ad esempio:

originale:

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

Dopo aver spostato:

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

Ora non vi resta che fare una migrazione di dati per aggiornare il app_label per MyModel nella tabella django_content_type e si dovrebbe essere pronti per partire ...

Esegui ./manage.py datamigration django update_content_type quindi modificare il file che il Sud crea per voi:

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()
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top