Em um método save () personalizado do modelo django, como você deve identificar um novo objeto?

172

Quero desencadear uma ação especial no método save () de um objeto de modelo do Django quando estou salvando um novo registro (não atualizando um registro existente).

A verificação de (self.id! = None) é necessária e suficiente para garantir que o auto registro seja novo e não esteja sendo atualizado? Algum caso especial que isso possa ignorar?

MikeN
fonte
Selecione stackoverflow.com/a/35647389/8893667 como a resposta correta. A resposta não funciona em muitos casos como umUUIDField pk
Kotlinboy 10/02

Respostas:

204

Atualizado: com o esclarecimento de que self._statenão é uma variável de instância privada, mas nomeada dessa maneira para evitar conflitos, a verificação self._state.addingagora é a maneira preferível de verificação.


self.pk is None:

retorna True dentro de um novo objeto Model, a menos que o objeto tenha um UUIDFieldcomo seu primary_key.

O caso de canto com o qual você pode se preocupar é se há restrições de exclusividade em outros campos que não o ID (por exemplo, índices exclusivos secundários em outros campos). Nesse caso, você ainda pode ter um novo registro em mãos, mas não poderá salvá-lo.

Dave W. Smith
fonte
20
Você deve usar is notem vez de !=durante a verificação de identidade com o Noneobjeto
Ben James
3
Nem todos os modelos têm um atributo id, ou seja, um modelo estendendo outro através de a models.OneToOneField(OtherModel, primary_key=True). Eu acho que você precisa usarself.pk
AJP
4
Isso pode não funcionar em alguns casos. Por favor, verifique esta resposta: stackoverflow.com/a/940928/145349
fjsj 5/15
5
Esta não é a resposta correta. Se usar uma UUIDFieldcomo chave primária, self.pknunca é None.
precisa saber é o seguinte
1
Nota lateral: Esta resposta é anterior ao campo UUIDField.
Dave W. Smith
190

Maneira alternativa de verificar self.pk, podemos verificar self._stateo modelo

self._state.adding is True criando

self._state.adding is False atualizando

Comprei nesta página

SaintTail
fonte
12
Essa é a única maneira correta ao usar um campo de chave primária personalizado.
Webtweakers
9
Não tenho certeza sobre todos os detalhes de como self._state.addingfunciona, mas um aviso justo de que parece sempre igual Falsese você estiver verificando depois de ligar super(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/stable/1.10.x/django/db/models/ ...
agilgur5 26/06/16
1
Esta é a maneira correta e deve ser votada / definida como a resposta correta.
flungo
7
@guival: _statenão é privado; como _meta, é prefixado com um sublinhado para evitar confusão com os nomes dos campos. (Observe como é usado na documentação vinculada.)
Ry-
2
Esta é a melhor maneira. Eu costumava is_new = self._state.adding, em seguida, super(MyModel, self).save(*args, **kwargs)e depoisif is_new: my_custom_logic()
kotrfa
45

A verificação self.idassume que essa idé a chave principal do modelo. Uma maneira mais genérica seria usar o atalho pk .

is_new = self.pk is None

Gerry
fonte
15
Dica profissional: coloque isso ANTES do super(...).save().
sbdchd
39

A verificação nãoself.pk == None é suficiente para determinar se o objeto será inserido ou atualizado no banco de dados.

O Django O / RM apresenta um hack especialmente desagradável, que é basicamente verificar se há algo na posição PK e, se for o caso, fazer um UPDATE, caso contrário, faça um INSERT (isso é otimizado para um INSERT se o PK for Nenhum).

A razão pela qual isso deve ser feito é porque você tem permissão para definir a PK quando um objeto é criado. Embora não seja comum quando você tem uma coluna de sequência para a chave primária, isso não vale para outros tipos de campos de chave primária.

Se você realmente quer saber, precisa fazer o que o O / RM faz e procurar no banco de dados.

Claro que você tem um caso específico em seu código e, para isso, é bastante provável que self.pk == Nonevocê tudo o que você precisa saber diz, mas é não uma solução geral.

KayEss
fonte
Bom ponto! Posso me safar disso no meu aplicativo (verificando a chave primária Nenhum) porque nunca defino o pacote para novos objetos. Mas isso definitivamente não seria uma boa verificação para um plug-in reutilizável ou parte da estrutura.
Miken
1
Isso é especialmente verdade quando você atribui a chave primária a si mesmo e através do banco de dados. Nesse caso, a coisa mais certa a fazer é fazer uma viagem ao banco de dados.
Constantine M
1
Mesmo que o código do aplicativo não especifique pks explicitamente, os acessórios para seus casos de teste podem. No entanto, como eles geralmente são carregados antes dos testes, pode não ser um problema.
Risadinha
1
Isso é especialmente verdade no caso de usar uma UUIDFieldcomo chave primária: a chave não é preenchida no nível do banco de dados, self.pkcomo sempre True.
precisa saber é o seguinte
10

Você pode simplesmente conectar-se ao sinal post_save que envia um kwargs "criado", se verdadeiro, seu objeto foi inserido.

http://docs.djangoproject.com/en/stable/ref/signals/#post-save

JF Simon
fonte
8
Isso pode causar condições de corrida se houver muita carga. Isso ocorre porque o sinal post_save é enviado ao salvar, mas antes da transação ser confirmada. Isso pode ser problemático e pode tornar as coisas muito difíceis de depurar.
Abel Mohler
Não tenho certeza se as coisas mudaram (de versões mais antigas), mas meus manipuladores de sinal são chamados na mesma transação, portanto, uma falha em qualquer lugar reverte toda a transação. Estou usando ATOMIC_REQUESTS, então não tenho muita certeza sobre o padrão.
precisa
7

Verifique self.ide a force_insertbandeira.

if not self.pk or kwargs.get('force_insert', False):
    self.created = True

# call save method.
super(self.__class__, self).save(*args, **kwargs)

#Do all your post save actions in the if block.
if getattr(self, 'created', False):
    # So something
    # Do something else

Isso é útil porque o seu objeto recém-criado (auto) tem pkvalor

Kwaw Annor
fonte
5

Estou muito atrasado para esta conversa, mas tive um problema com o self.pk sendo preenchido quando ele tem um valor padrão associado.

A maneira como resolvi isso foi adicionar um campo date_created ao modelo

date_created = models.DateTimeField(auto_now_add=True)

Daqui você pode ir

created = self.date_created is None

Jordânia
fonte
4

Para uma solução que também funcione mesmo quando você tem uma UUIDFieldchave primária (o que, como outros observaram, não é Nonese você acabou de substituir save), você pode conectar o sinal post_save do Django . Adicione isso ao seu models.py :

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=MyModel)
def mymodel_saved(sender, instance, created, **kwargs):
    if created:
        # do extra work on your instance, e.g.
        # instance.generate_avatar()
        # instance.send_email_notification()
        pass

Esse retorno de chamada bloqueará o savemétodo, para que você possa executar ações como notificações de gatilho ou atualizar ainda mais o modelo antes que sua resposta seja enviada novamente, seja usando formulários ou a estrutura REST do Django para chamadas AJAX. Obviamente, use responsavelmente e descarregue tarefas pesadas em uma fila de tarefas, em vez de manter os usuários esperando :)

metakermit
fonte
3

em vez disso, use pk em vez de id :

if not self.pk:
  do_something()
yedpodtrzitko
fonte
1

É a maneira comum de fazer isso.

o id será dado enquanto salvo pela primeira vez no banco de dados

vikingosegundo
fonte
0

Isso funcionaria para todos os cenários acima?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...
Sachin
fonte
isso causaria um acerto adicional no banco de dados.
David Schumann
0
> def save_model(self, request, obj, form, change):
>         if form.instance._state.adding:
>             form.instance.author = request.user
>             super().save_model(request, obj, form, change)
>         else:
>             obj.updated_by = request.user.username
> 
>             super().save_model(request, obj, form, change)
Swelan Auguste
fonte
Usando clean_data.get (), eu era capaz de determinar se eu tinha uma instância, também tinha um CharField onde null e em branco onde true. Isso será atualizado a cada atualização de acordo com o usuário conectado
Swelan Auguste
-3

Para saber se você está atualizando ou inserindo o objeto (dados), use self.instance.fieldnameem seu formulário. Defina uma função limpa no seu formulário e verifique se a entrada de valor atual é igual à anterior, caso contrário, você está atualizando.

self.instancee self.instance.fieldnamecompare com o novo valor

ha22109
fonte