Django-DB-Migrations: não é possível ALTER TABLE porque tem eventos de gatilho pendentes

121

Quero remover null = True de um TextField:

-    footer=models.TextField(null=True, blank=True)
+    footer=models.TextField(blank=True, default='')

Eu criei uma migração de esquema:

manage.py schemamigration fooapp --auto

Como algumas colunas de rodapé contêm, NULLeu entendi errorse executar a migração:

django.db.utils.IntegrityError: a coluna "footer" contém valores nulos

Eu adicionei isso à migração do esquema:

    for sender in orm['fooapp.EmailSender'].objects.filter(footer=None):
        sender.footer=''
        sender.save()

Agora eu consigo:

django.db.utils.DatabaseError: cannot ALTER TABLE "fooapp_emailsender" because it has pending trigger events

O que está errado?

Guettli
fonte
1
Esta pergunta é semelhante: stackoverflow.com/questions/28429933/… e teve respostas que foram mais úteis para mim.
SpoonMeiser

Respostas:

138

Outra razão para isso pode ser porque você tenta definir uma coluna para NOT NULLquando, na verdade, ela já tem NULLvalores.

maazza
fonte
7
Para resolver isso, você pode usar uma migração de dados ou manualmente (shell manage.py) e atualizar os valores não compatíveis
mgojohn
@mgojohn Como você faz isso?
Pyramidface
1
@pyramidface Se você não for muito exigente, você pode apenas atualizar os valores nulos no shell django. Se você está procurando por algo mais formal e testável, isso depende de quais versões você está usando. Se você usa o sul, consulte: south.readthedocs.org/en/latest/tutorial/part3.html e se você usa as migrações do django, consulte a seção "migrações de dados" aqui: docs.djangoproject.com/en/1.8/topics/ migrações
mgojohn
131

Cada migração está dentro de uma transação. No PostgreSQL você não deve atualizar a tabela e então alterar o esquema da tabela em uma transação.

Você precisa dividir a migração de dados e a migração de esquema. Primeiro crie a migração de dados com este código:

 for sender in orm['fooapp.EmailSender'].objects.filter(footer=None):
    sender.footer=''
    sender.save()

Em seguida, crie a migração do esquema:

manage.py schemamigration fooapp --auto

Agora você tem duas transações e a migração em duas etapas deve funcionar.

Guettli
fonte
8
O PostgreSQL provavelmente mudou seu comportamento em relação a tais transações, pois consegui executar uma migração com alterações de dados e esquema em minha máquina de desenvolvimento (PostgreSQL 9.4) enquanto falhou no servidor (PostgreSQL 9.1).
Bertrand Bordage
1
Quase o mesmo para mim. Funcionou perfeitamente para mais de 100 migrações (incluindo ~ 20 migrações de dados) até hoje, enquanto adicionava restrições exclusivas junto com a migração de dados removendo duplicatas anteriores. PostgreSQL 10.0
LinPy fan
Se estiver usando uma operação RunPython na migração para a migração de dados, você só precisa ter certeza de que é a última operação. Django sabe que, se a operação RunPython for a última, deve abrir sua própria transação.
Dougyfresh de
1
@Dougyfresh este é um recurso documentado do django?
guettli
Na verdade, não vejo isso em lugar nenhum, foi apenas algo que observei. docs.djangoproject.com/en/2.2/ref/migration-operations/…
Dougyfresh
9

Acabei de atingir este problema. Você também pode usar db.start_transaction () e db.commit_transaction () na migração do esquema para separar as mudanças de dados das mudanças de esquema. Provavelmente não tão limpo a ponto de ter uma migração de dados separada, mas no meu caso eu precisaria de esquema, dados e outra migração de esquema, então decidi fazer tudo de uma vez.

clima
fonte
7
O problema com esta solução é o seguinte: O que acontecerá se sua migração falhar após db.commit_transaction ()? Eu prefiro usar três migrações, se você precisar disso: schema-mig, data-mig, schema-mig.
guettli
5
Veja: django.readthedocs.io/en/latest/ref/migration-operations.html Em bancos de dados que suportam transações DDL (SQLite e PostgreSQL), as operações RunPython não têm nenhuma transação adicionada automaticamente além das transações criadas para cada migração. Assim, no PostgreSQL, por exemplo, você deve evitar combinar mudanças de esquema e operações RunPython na mesma migração ou pode encontrar erros como OperationalError: não é possível ALTER TABLE "mytable" porque possui eventos de gatilho pendentes.
Iasmini Gomes
5

Nas operações eu coloquei SET CONSTRAINTS:

operations = [
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
    migrations.RunPython(migration_func),
    migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'),
]
sluge
fonte
Melhor usar SeparateDatabaseAndState
bdoubleu
0

Você está alterando o esquema da coluna. Essa coluna de rodapé não pode mais conter um valor em branco. Provavelmente, existem valores em branco já armazenados no BD para essa coluna. Django irá atualizar essas linhas em branco em seu banco de dados de branco para o valor padrão agora com o comando migrate. Django tenta atualizar as linhas onde a coluna do rodapé tem um valor em branco e alterar o esquema ao mesmo tempo que parece (não tenho certeza).

O problema é que você não pode alterar o mesmo esquema de coluna para o qual está tentando atualizar os valores ao mesmo tempo.

Uma solução seria excluir o arquivo de migração atualizando o esquema. Em seguida, execute um script para atualizar todos esses valores para o valor padrão. Em seguida, execute novamente a migração para atualizar o esquema. Dessa forma, a atualização já está feita. A migração do Django está apenas alterando o esquema.

Uzzi Emuchay
fonte
1
Executar algum script não é realmente uma opção para mim. Eu tenho várias instâncias do banco de dados e o processo de implantação contínua chama apenas "manage.py migrate". Esta questão já é respostas válidas que funcionam bem.
guettli