Come faccio a migrare un modello di un app Django e in uno nuovo?
-
12-09-2019 - |
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.
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()