Strategia di migrazione di Django per rinominare un modello e campi di relazione


154

Sto programmando di rinominare diversi modelli in un progetto Django esistente in cui ci sono molti altri modelli che hanno relazioni di chiave esterna con i modelli che vorrei rinominare. Sono abbastanza sicuro che ciò richiederà più migrazioni, ma non sono sicuro della procedura esatta.

Diciamo che inizio con i seguenti modelli all'interno di un'app Django chiamata myapp:

class Foo(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_ridonkulous = models.BooleanField()

Voglio rinominare il Foomodello perché il nome non ha davvero senso e sta causando confusione nel codice e Barrenderebbe un nome molto più chiaro.

Da quanto ho letto nella documentazione di sviluppo di Django, presumo la seguente strategia di migrazione:

Passo 1

Modifica models.py:

class Bar(models.Model):  # <-- changed model name
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_ridonkulous = models.BooleanField()

Si noti che il AnotherModelnome del campo per foonon cambia, ma la relazione viene aggiornata al Barmodello. Il mio ragionamento è che non dovrei cambiare troppo in una volta e che se cambiassi questo nome di campo barrischierei di perdere i dati in quella colonna.

Passo 2

Crea una migrazione vuota:

python manage.py makemigrations --empty myapp

Passaggio 3

Modificare la Migrationclasse nel file di migrazione creato nel passaggio 2 per aggiungere RenameModell'operazione all'elenco delle operazioni:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'),
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

Passaggio 4

Applica la migrazione:

python manage.py migrate

Passaggio 5

Modifica i nomi dei campi correlati in models.py:

class Bar(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_ridonkulous = models.BooleanField()

Passaggio 6

Crea un'altra migrazione vuota:

python manage.py makemigrations --empty myapp

Passaggio 7

Modificare la Migrationclasse nel file di migrazione creato nel passaggio 6 per aggiungere le RenameFieldoperazioni per i nomi di campi correlati all'elenco delle operazioni:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_rename_fields'),  # <-- is this okay?
    ]

    operations = [
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Passaggio 8

Applica la seconda migrazione:

python manage.py migrate

Oltre ad aggiornare il resto del codice (viste, moduli, ecc.) Per riflettere i nuovi nomi delle variabili, è sostanzialmente questo il modo in cui funzionerebbe la nuova funzionalità di migrazione?

Inoltre, questo sembra un sacco di passaggi. Le operazioni di migrazione possono essere condensate in qualche modo?

Grazie!

Risposte:


128

Quindi, quando ho provato questo, sembra che tu possa condensare i passaggi 3 - 7:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'), 
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar'),
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Potresti ricevere alcuni errori se non aggiorni i nomi in cui è importato, ad esempio admin.py e file di migrazione anche più vecchi (!).

Aggiornamento : come menziona Ceasaro , le versioni più recenti di Django sono in genere in grado di rilevare e chiedere se un modello viene rinominato. Quindi prova manage.py makemigrationsprima e poi controlla il file di migrazione.


Grazie per la risposta. Da allora sono migrato usando i passaggi che ho delineato, ma sono curioso di averlo provato con dati esistenti o semplicemente con un database vuoto?
Fiver,

2
Ho provato con i dati esistenti, anche se solo poche righe su sqlite nel mio ambiente locale (quando mi sposto in produzione ho intenzione di cancellare tutto, compresi i file di migrazione)
wasabigeek,

4
Non è necessario modificare il nome del modello nei file di migrazione se si utilizza apps.get_modelin essi. mi ci è voluto molto tempo per capirlo.
Ahmed,

9
In django 2.0 se cambi il nome del tuo modello, il ./manage.py makemigrations myappcomando ti chiederà se hai rinominato il tuo modello. Ad esempio: hai rinominato il modello myapp.Foo in Bar? [y / N] Se rispondi 'y' la tua migrazione conterrà gli migration.RenameModel('Foo', 'Bar')stessi conteggi per i campi rinominati :-)
ceasaro

1
manage.py makemigrations myapppotrebbe comunque non riuscire: "Potrebbe essere necessario aggiungerlo manualmente se si modifica il nome del modello e alcuni dei suoi campi contemporaneamente; al rilevatore automatico, sembrerà che tu abbia eliminato un modello con il vecchio nome e ne abbia aggiunto uno nuovo con un nome diverso e la migrazione che crea perderà tutti i dati nella vecchia tabella. " Django 2.1 Docs Per me, è stato sufficiente creare una migrazione vuota, aggiungere il modello rinominato ad esso, quindi eseguire makemigrationscome al solito.
hlongmore,

37

All'inizio, ho pensato che il metodo di Fiver funzionasse per me perché la migrazione ha funzionato bene fino al passaggio 4. Tuttavia, le modifiche implicite "ForeignKeyField (Foo)" in "ForeignKeyField (Bar)" non erano correlate in nessuna migrazione. Questo è il motivo per cui la migrazione non è riuscita quando volevo rinominare i campi delle relazioni (passaggio 5-8). Ciò potrebbe essere dovuto al fatto che i miei "AnotherModel" e "YetAnotherModel" vengono inviati in altre app nel mio caso.

Quindi sono riuscito a rinominare i miei modelli e campi di relazione procedendo come segue:

Ho adattato il metodo da questo e in particolare il trucco di otranzer.

Quindi come Fiver diciamo che abbiamo in myapp :

class Foo(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

E in myotherapp :

class AnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_ridonkulous = models.BooleanField()

Passo 1:

Trasforma ogni OneToOneField (Foo) o ForeignKeyField (Foo) in IntegerField (). (Ciò manterrà l'id dell'oggetto Foo correlato come valore dell'intero campo).

class AnotherModel(models.Model):
    foo = models.IntegerField()
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.IntegerField()
    is_ridonkulous = models.BooleanField()

Poi

python manage.py makemigrations

python manage.py migrate

Passaggio 2: (come il passaggio 2-4 da Fiver)

Cambia il nome del modello

class Bar(models.Model):  # <-- changed model name
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

Crea una migrazione vuota:

python manage.py makemigrations --empty myapp

Quindi modificalo come segue:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'),
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

Infine

python manage.py migrate

Passaggio 3:

Trasforma Back IntegerField () nel precedente ForeignKeyField o OneToOneField ma con il nuovo modello di barra. (Il campo intero precedente stava memorizzando l'id, quindi django lo capisce e ristabilisce la connessione, il che è fantastico.)

class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_ridonkulous = models.BooleanField()

Quindi fa:

python manage.py makemigrations 

Molto importante, in questo passaggio è necessario modificare ogni nuova migrazione e aggiungere la dipendenza dalle migrazioni RenameModel Foo-> Bar. Quindi, se sia AnotherModel che YetAnotherModel sono presenti in myotherapp, la migrazione creata in myotherapp deve essere simile a questa:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '00XX_the_migration_of_myapp_with_renamemodel_foo_bar'),
        ('myotherapp', '00xx_the_migration_of_myotherapp_with_integerfield'),
    ]

    operations = [
        migrations.AlterField(
            model_name='anothermodel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar'),
        ),
        migrations.AlterField(
            model_name='yetanothermodel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar')
        ),
    ]

Poi

python manage.py migrate

Step 4:

Alla fine puoi rinominare i tuoi campi

class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar) <------- Renamed fields
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar) <------- Renamed fields
    is_ridonkulous = models.BooleanField()

e quindi rinominare automaticamente

python manage.py makemigrations

(django dovrebbe chiederti se hai effettivamente rinominato il nome del modello, dire di sì)

python manage.py migrate

E questo è tutto!

Funziona su Django1.8


3
Grazie! È stato estremamente utile. Ma una nota: ho anche dovuto rinominare e / o rimuovere manualmente gli indici dei campi PostgreSQL perché, dopo aver rinominato Foo in Bar, ho creato un nuovo modello chiamato Bar.
Anatoly Scherbakov,

Grazie per questo! Penso che la parte chiave stia convertendo tutte le chiavi esterne, dentro o fuori il modello da rinominare, in IntegerField. Questo ha funzionato perfettamente per me e ha l'ulteriore vantaggio di essere ricreato con il nome corretto. Naturalmente consiglierei di rivedere tutte le migrazioni prima di eseguirle effettivamente!
zelanix,

Grazie! Ho provato molte strategie diverse per rinominare un modello a cui altri modelli hanno chiavi esterne (passaggi 1-3), e questo è stato l'unico che ha funzionato.
MSH

La modifica di ForeignKeys in IntegerFields mi ha salvato la giornata oggi!
Mehmet,

8

Dovevo fare la stessa cosa e seguire. Ho cambiato il modello tutto in una volta (passaggi 1 e 5 insieme dalla risposta di Fiver). Quindi ha creato una migrazione dello schema ma l'ha modificata per essere questa:

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('Foo','Bar')

    def backwards(self, orm):
        db.rename_table('Bar','Foo')

Questo ha funzionato perfettamente. Sono stati mostrati tutti i miei dati esistenti, tutte le altre tabelle referenziate Bar bene.

da qui: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/


Ottimo, grazie per averlo condiviso. Assicurati di fare +1 su wasibigeek se la risposta è stata di aiuto.
Fiver

7

Per Django 1.10, sono riuscito a cambiare due nomi di classe del modello (incluso ForeignKey e con i dati) semplicemente eseguendo Makemigrations, quindi Migrate per l'app. Per il passaggio Makemigrations, ho dovuto confermare che volevo cambiare i nomi delle tabelle. La migrazione ha cambiato i nomi delle tabelle senza problemi.

Quindi ho cambiato il nome del campo ForeignKey in modo che corrisponda, e ancora una volta Makemigrations mi ha chiesto di confermare che volevo cambiare il nome. Migrare che apportare la modifica.

Quindi ho preso questo in due passaggi senza alcuna modifica speciale dei file. Inizialmente ho ricevuto degli errori perché ho dimenticato di cambiare il file admin.py, come indicato da @wasibigeek.


Molte grazie! Perfetto anche per Django 1.11
Francisco

6

Ho anche affrontato il problema come descritto da v.thorey e ho scoperto che il suo approccio è molto utile ma può essere condensato in meno passaggi che sono in realtà i passaggi da 5 a 8 come Fiver ha descritto senza passaggi da 1 a 4, tranne per il fatto che il passaggio 7 deve essere modificato come mio sotto il passaggio 3. I passaggi generali sono i seguenti:

Passaggio 1: modifica i nomi dei campi correlati in models.py

class Bar(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_ridonkulous = models.BooleanField()

Passaggio 2: creare una migrazione vuota

python manage.py makemigrations --empty myapp

Passaggio 3: modificare la classe di migrazione nel file di migrazione creato nel passaggio 2

class Migration(migrations.Migration):

dependencies = [
    ('myapp', '0001_initial'), 
]

operations = [
    migrations.AlterField(
        model_name='AnotherModel',
        name='foo',
        field=models.IntegerField(),
    ),
    migrations.AlterField(
        model_name='YetAnotherModel',
        name='foo',
        field=models.IntegerField(),
    ),
    migrations.RenameModel('Foo', 'Bar'),
    migrations.AlterField(
        model_name='AnotherModel',
        name='foo',
        field=models.ForeignKey(to='myapp.Bar'),
    ),
    migrations.AlterField(
        model_name='YetAnotherModel',
        name='foo',
        field=models.ForeignKey(to='myapp.Bar'),
    ),
    migrations.RenameField('AnotherModel', 'foo', 'bar'),
    migrations.RenameField('YetAnotherModel', 'foo', 'bar')
]

Passaggio 4: applicare la migrazione

python manage.py migrate

Fatto

PS Ho provato questo approccio su Django 1.9


5

Sto usando Django versione 1.9.4

Ho seguito i seguenti passi: -

Ho appena rinominato il modello oldName in NewName Run python manage.py makemigrations. Ti chiederà di Did you rename the appname.oldName model to NewName? [y/N]selezionare Y

Corri python manage.py migratee ti chiederà

I seguenti tipi di contenuto sono obsoleti e devono essere eliminati:

appname | oldName
appname | NewName

Anche gli oggetti correlati a questi tipi di contenuto tramite una chiave esterna verranno eliminati. Sei sicuro di voler eliminare questi tipi di contenuto? Se non sei sicuro, rispondi "no".

Type 'yes' to continue, or 'no' to cancel: Select No

Rinomina e migra tutti i dati esistenti nella nuova tabella denominata per me.


Grazie amico, ero confuso perché non è successo nulla quando dopo aver colpito "no"
farhawa

3

Sfortunatamente, ho riscontrato problemi (ogni django 1.x) con la migrazione della ridenominazione che lasciano vecchi nomi di tabella nel database.

Django non prova nemmeno nulla sul vecchio tavolo, solo rinominando il suo modello. Lo stesso problema con le chiavi esterne e gli indici in generale: i cambiamenti non vengono tracciati correttamente da Django.

La soluzione più semplice (soluzione alternativa):

class Foo(models.Model):
     name = models.CharField(unique=True, max_length=32)
     ...
Bar = Foo  # and use Bar only

La vera soluzione (un modo semplice per commutare tutti gli indici, i vincoli, i trigger, i nomi, ecc. In 2 commit, ma piuttosto per tabelle più piccole ):

commettere A:

  1. crea lo stesso modello di quello vecchio
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...
  1. cambia codice per funzionare solo con il nuovo modello Bar. (comprese tutte le relazioni sullo schema)

Nella migrazione preparare RunPython, che copia i dati da Foo a Bar (incluso idFoo)

  1. ottimizzazione opzionale (se necessario per tabelle più grandi)

commit B: (nessuna fretta, fallo quando viene migrata un'intera squadra)

  1. caduta sicura del vecchio modello Foo

ulteriore pulizia:

  • schiacciare le migrazioni

bug in Django:


3

Volevo solo confermare e aggiungere un commento su ceasaro. Django 2.0 sembra farlo automaticamente adesso.

Sono su Django 2.2.1, tutto quello che dovevo fare per rinominare il modello ed eseguire makemigrations.

Qui mi viene chiesto se avevo rinominato la classe specifica da Aa B, ho scelto Sì e ho eseguito la migrazione e tutto sembra funzionare.

Nota Non ho rinominato il vecchio nome del modello in nessun file all'interno della cartella project / migrations.


1

Avevo bisogno di rinominare un paio di tabelle. Ma Django ha notato solo una ridenominazione del modello. Ciò è accaduto perché Django ripete i modelli aggiunti e rimossi. Per ogni coppia verifica se appartengono alla stessa app e hanno campi identici . Solo una tabella non aveva chiavi esterne per le tabelle da rinominare (le chiavi esterne contengono il nome della classe del modello, come ricorderete). In altre parole, solo una tabella non ha subito modifiche al campo. Ecco perché è stato notato.

Quindi, la soluzione è quella di rinominare una tabella alla volta, modificando il nome della classe del modello models.py, possibilmente views.py, ed eseguendo una migrazione. Successivamente, controlla il tuo codice per altri riferimenti (nomi delle classi del modello, nomi (query) correlati, nomi delle variabili). Effettua una migrazione, se necessario. Quindi, opzionalmente combinare tutte queste migrazioni in una (assicurati di copiare anche le importazioni).


1

Vorrei fare @ceasaro parole, le mie sul suo commento su questa risposta .

Le versioni più recenti di Django sono in grado di rilevare le modifiche e chiedere cosa è stato fatto. Vorrei anche aggiungere che Django potrebbe mescolare l'ordine di esecuzione di alcuni comandi di migrazione.

Sarebbe saggio per applicare piccole modifiche ed eseguire makemigrationse migratese l'errore si verifica il file di migrazione può essere modificato.

L'ordine di esecuzione di alcune righe può essere modificato per evitare errori.


Buono a notare che questo non funziona se si cambiano i nomi dei modelli e ci sono chiavi esterne definite, ecc ...
Dean Kayton,

Espandendo sul commento precedente: se tutto ciò che faccio è cambiare i nomi dei modelli ed eseguire i makemigrations ottengo "NameError: il nome" <modello>> "non è definito" nelle chiavi straniere, ecc ... Se lo cambio ed eseguo i makemigrations, ottengo errori di importazione in admin.py ... se lo risolvo ed eseguo di nuovo i makemigrations, ricevo i messaggi "Hai rinominato il modello <app.oldmodel> in <newmodel>" Ma poi applicando le migrazioni, ottengo "ValueError: The field <app .newmodel.field1> è stato dichiarato con un riferimento pigro a '<app.oldmodel>', ma l'app '<app>' non fornisce il modello '<oldmodel>', ecc ... '
Dean Kayton,

Questo errore sembra che sia necessario rinominare i riferimenti nelle migrazioni storiche.
mhatch

@DeanKayton direbbe che migrations.SeparateDatabaseAndStatepuò aiutare?
diogosimao,

1

Se stai usando un buon IDE come PyCharm puoi fare clic con il tasto destro sul nome del modello ed eseguire un refactor -> rinomina. Questo ti risparmia la difficoltà di esaminare tutto il codice che fa riferimento al modello. Quindi eseguire makemigrations e migrare. Django 2+ confermerà semplicemente la modifica del nome.


-10

Ho aggiornato Django dalla versione 10 alla versione 11:

sudo pip install -U Django

( -Uper "upgrade") e risolto il problema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.