2015-02-10 12 views
18

Desidero modificare una chiave esterna in uno dei miei modelli che al momento può avere valori NULL non annullabili.Migrazioni di Django con RunPython per eseguire il commit delle modifiche

ho rimosso il null=True dal mio campo e corse makemigrations

perché io sono un alterare una tabella che ha già le righe che contengono i valori NULL in quel campo mi viene chiesto di fornire un valore una tantum subito o modificare il file di migrazione e aggiungere un'operazione RunPython.

L'operazione My RunPython è elencata PRIMA dell'operazione AlterField e fa l'aggiornamento richiesto per questo campo in modo che non contenga valori NULL (solo le righe che già contengono un valore NULL).

Ma, la migrazione non riesce ancora con questo errore: django.db.utils.OperationalError: cannot ALTER TABLE "my_app_site" because it has pending trigger events

Ecco il mio codice:

# -*- coding: utf-8 -*- 
from __future__ import unicode_literals 

from django.db import models, migrations 

def add_default_template(apps, schema_editor): 
    Template = apps.get_model("my_app", "Template") 
    Site = apps.get_model("my_app", "Site") 

    accept_reject_template = Template.objects.get(name="Accept/Reject") 
    Site.objects.filter(template=None).update(template=accept_reject_template)  

class Migration(migrations.Migration): 

    dependencies = [ 
     ('my_app', '0021_auto_20150210_1008'), 
    ] 

    operations = [ 
     migrations.RunPython(add_default_template), 
     migrations.AlterField(
      model_name='site', 
      name='template', 
      field=models.ForeignKey(to='my_app.Template'), 
      preserve_default=False, 
     ), 
    ] 

Se ho capito si può verificare in modo corretto questo errore quando un campo viene alterato per essere non annullabile, ma il campo contiene valori nulli. In questo caso, l'unica ragione per cui riesco a pensare perché ciò accade è perché la transazione dell'operazione RunPython non "ha commesso" le modifiche nel database prima di eseguire AlterField.

Se questo è davvero il motivo: come posso assicurarmi che le modifiche si riflettano nel database? In caso contrario - quale può essere la causa dell'errore?

Grazie!

+4

Primo pensiero che viene in mente, dividerlo. Effettua prima la datamigration, quindi rendi il tuo campo non Null. – danielcorreia

+0

Sì, ho pensato a questo approccio, ma volevo sapere se c'è un modo per evitarlo e fare tutto nella stessa migrazione –

risposta

27

Questo accade perché Django crea vincoli come DEFERRABLE INITIALLY DEFERRED:

ALTER TABLE my_app_site 
ADD CONSTRAINT "[constraint_name]" 
FOREIGN KEY (template_id) 
REFERENCES my_app_template(id) 
DEFERRABLE INITIALLY DEFERRED; 

Questo dice PostgreSQL che la chiave esterna non ha bisogno di essere controllato dopo ogni comando, ma può essere differito fino alla fine delle operazioni.

Quindi, quando una transazione modifica il contenuto e la struttura, i vincoli vengono verificati in parallelo con le modifiche della struttura, oppure i controlli devono essere eseguiti dopo aver modificato la struttura. Entrambi questi stati sono negativi e il database interromperà la transazione invece di fare ipotesi.

È possibile indicare PostgreSQL per verificare i vincoli immediatamente nella transazione corrente chiamando SET CONSTRAINTS ALL IMMEDIATE, quindi cambia la struttura non sarà un problema (fare riferimento alla SET CONSTRAINTS documentazione). La migrazione dovrebbe essere così:

operations = [ 
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE', 
         reverse_sql=migrations.RunSQL.noop), 

    # ... the actual migration operations here ... 

    migrations.RunSQL(migrations.RunSQL.noop, 
         reverse_sql='SET CONSTRAINTS ALL IMMEDIATE'), 
] 

La prima operazione è per l'applicazione (in avanti) le migrazioni, e l'ultimo è per unapplying (indietro) migrazioni.

MODIFICA: Il rimando di vincolo è utile per evitare l'ordinamento di inserimento, specialmente per tabelle e tabelle autoreferenziali con dipendenze cicliche. Quindi fai attenzione quando pieghi Django.

EDIT LATE: su Django 1.7 e versioni più recenti v'è una speciale SeparateDatabaseAndState operazione che consente di modificare i dati e cambia la struttura sulla stessa migrazione. Prova a utilizzare questa operazione prima di ricorrere al metodo "imposta vincoli tutti immediati" sopra. Esempio:

operations = [ 
    migrations.SeparateDatabaseAndState(database_operations=[ 
      # put your sql, python, whatever data migrations here 
     ], 
     state_operations=[ 
      # field/model changes goes here 
     ]), 
] 
+0

Questa è la risposta che stavo cercando! –

+1

Questo ha bisogno di ripristinare come era? – Shadow

+2

@shadow No, il documento dice che "SET CONSTRAINTS imposta il comportamento del controllo dei vincoli all'interno della transazione corrente". – eric

17

Sì, direi che sono i limiti di transazione che impediscono la modifica dei dati nella migrazione in corso prima dell'esecuzione di ALTER.

Farei come dice @danielcorreia e implementarlo come due migrazioni, in quanto sembra che anche lo SchemaEditor sia vincolato dalle transazioni, tramite il gestore di contesto che si sarebbe obbligati a utilizzare.

+0

Devo ammettere che anche se questo approccio è valido e funziona tecnicamente, è abbastanza frustrante che non ci sia è un modo per ottenerlo nella stessa migrazione. Grazie! –

+2

Divisione in 2 (uno con --empty e gestione manuale) è la soluzione! – danigosa

Problemi correlati