En un método save () personalizado de modelo django, ¿cómo debe identificar un nuevo objeto?

172

Quiero activar una acción especial en el método save () de un objeto modelo Django cuando guardo un nuevo registro (sin actualizar un registro existente).

¿La verificación de (self.id! = Ninguno) es necesaria y suficiente para garantizar que el auto registro sea nuevo y no se actualice? ¿Algún caso especial que esto pueda pasar por alto?

MikeN
fuente
Seleccione stackoverflow.com/a/35647389/8893667 como la respuesta correcta. La respuesta no funciona en muchos casos como unUUIDField pk
Kotlinboy

Respuestas:

204

Actualizado: con la aclaración de que self._stateno es una variable de instancia privada, sino que se llama así para evitar conflictos, la verificación self._state.addinges ahora la forma preferible de verificar.


self.pk is None:

devuelve True dentro de un nuevo objeto Model, a menos que el objeto tenga a UUIDFieldcomo su primary_key.

El caso de la esquina por el que debería preocuparse es si hay restricciones de unicidad en los campos que no sean la identificación (por ejemplo, índices únicos secundarios en otros campos). En ese caso, aún podría tener un nuevo registro en la mano, pero no podrá guardarlo.

Dave W. Smith
fuente
20
Debe utilizar is noten lugar de !=en la comprobación de la identidad con el Noneobjeto
Ben James
3
No todos los modelos tienen un atributo id, es decir, un modelo que se extiende a otro a través de a models.OneToOneField(OtherModel, primary_key=True). Creo que necesitas usarself.pk
AJP
44
Esto PUEDE NO FUNCIONAR en algunos casos. Verifique esta respuesta: stackoverflow.com/a/940928/145349
fjsj
55
Esta no es la respuesta correcta. Si utiliza a UUIDFieldcomo clave principal, self.pknunca lo es None.
Daniel van Flymen
1
Nota al margen: esta respuesta es anterior a UUIDField.
Dave W. Smith el
190

Una forma alternativa de verificar self.pkpodemos verificar self._stateel modelo

self._state.adding is True creando

self._state.adding is False actualización

Lo obtuve de esta página

SaintTail
fuente
12
Esta es la única forma correcta cuando se utiliza un campo de clave primaria personalizada.
webtweakers
9
No estoy seguro de todos los detalles de cómo self._state.addingfunciona, pero una advertencia justa de que parece ser siempre igual Falsesi lo está verificando después de llamar super(TheModel, self).save(*args, **kwargs): github.com/django/django/blob/stable/1.10.x/django/db/models/ ...
agilgur5
1
Esta es la forma correcta y debe ser votada / establecida como la respuesta correcta.
flungo
77
@guival: _stateno es privado; como _meta, está prefijado con un guión bajo para evitar confusiones con los nombres de los campos. (Observe cómo se usa en la documentación vinculada).
Ry-
2
Esta es la mejor manera. Solía is_new = self._state.adding, entonces super(MyModel, self).save(*args, **kwargs)y luegoif is_new: my_custom_logic()
kotrfa
45

La comprobación self.idasume que esa ides la clave principal para el modelo. Una forma más genérica sería utilizar el atajo pk .

is_new = self.pk is None

Gerry
fuente
15
Pro Tip: ésta antes del super(...).save().
sbdchd
39

La comprobación noself.pk == None es suficiente para determinar si el objeto se va a insertar o actualizar en la base de datos.

El Django O / RM presenta un truco especialmente desagradable que es básicamente para verificar si hay algo en la posición PK y, de ser así, hacer una ACTUALIZACIÓN, de lo contrario, hacer un INSERT (esto se optimiza a un INSERT si el PK es Ninguno).

La razón por la que tiene que hacer esto es porque se le permite establecer la PK cuando se crea un objeto. Aunque no es común cuando tiene una columna de secuencia para la clave primaria, esto no se cumple para otros tipos de campo de clave primaria.

Si realmente quiere saber, debe hacer lo que hace el O / RM y buscar en la base de datos.

Por supuesto, usted tiene un caso específico en su código y de que es muy probable que self.pk == Nonetodo lo que necesita saber dice, pero es no una solución general.

KayEss
fuente
¡Buen punto! Puedo salir con esto en mi aplicación (buscando la clave principal None) porque nunca configuré el pk para nuevos objetos. Pero esto definitivamente no sería una buena comprobación para un complemento reutilizable o parte del marco.
MikeN
1
Esto es especialmente cierto cuando asigna la clave principal usted mismo y a través de la base de datos. En ese caso, lo más seguro es hacer un viaje a la base de datos.
Constantine M
1
Incluso si el código de su aplicación no especifica pks explícitamente, los accesorios para sus casos de prueba podrían hacerlo. Sin embargo, como normalmente se cargan antes de las pruebas, podría no ser un problema.
Risadinha
1
Esto es especialmente cierto en el caso de utilizar a UUIDFieldcomo clave principal: la clave no se rellena en el nivel de base de datos, por self.pklo que siempre es así True.
Daniel van Flymen
10

Simplemente puede conectarse a la señal post_save que envía un kwargs "creado", si es verdadero, su objeto ha sido insertado.

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

JF Simon
fuente
8
Potencialmente, eso puede causar condiciones de carrera si hay mucha carga. Esto se debe a que la señal post_save se envía al guardar, pero antes de que se haya confirmado la transacción. Esto puede ser problemático y puede hacer que las cosas sean muy difíciles de depurar.
Abel Mohler
No estoy seguro de si las cosas cambiaron (de versiones anteriores), pero mis manejadores de señales se llaman dentro de la misma transacción, por lo que una falla en cualquier lugar revierte toda la transacción. Estoy usando ATOMIC_REQUESTS, así que no estoy muy seguro sobre el valor predeterminado.
Tim Tisdall
7

Verificar self.idy la force_insertbandera.

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

Esto es útil porque su objeto recién creado (self) tiene pkvalor

Kwaw Annor
fuente
5

Llegué muy tarde a esta conversación, pero me encontré con un problema con el self.pk que se rellena cuando tiene un valor predeterminado asociado.

La forma en que solucioné esto es agregando un campo date_created al modelo

date_created = models.DateTimeField(auto_now_add=True)

Desde aquí puedes ir

created = self.date_created is None

Jordán
fuente
4

Para una solución que también funciona incluso cuando tiene una UUIDFieldclave principal (que, como otros han señalado, no es Nonesi simplemente anula save), puede conectarse a la señal post_save de Django . Agregue esto a sus modelos.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

Esta devolución de llamada bloqueará el savemétodo, por lo que puede hacer cosas como notificaciones de activación o actualizar el modelo aún más antes de que su respuesta se envíe por cable, ya sea que esté utilizando formularios o el marco Django REST para llamadas AJAX. Por supuesto, use de manera responsable y descargue tareas pesadas en una cola de trabajo en lugar de hacer que sus usuarios esperen :)

metakermit
fuente
3

más bien use pk en lugar de id :

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

Es la forma común de hacerlo.

la identificación se dará mientras se guarda por primera vez en el db

vikingosegundo
fuente
0

¿Funcionaría esto para todos los escenarios anteriores?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...
Sachin
fuente
esto causaría un impacto adicional en la base de datos.
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
fuente
Usando clean_data.get (), pude determinar si tenía una instancia, también tuve un CharField donde nulo y en blanco donde verdadero. Esto se actualizará en cada actualización de acuerdo con el usuario conectado
Swelan Auguste,
-3

Para saber si está actualizando o insertando el objeto (datos), utilícelo self.instance.fieldnameen su formulario. Defina una función limpia en su formulario y verifique si la entrada del valor actual es la misma que la anterior, de lo contrario, la está actualizando.

self.instancey self.instance.fieldnamecomparar con el nuevo valor

ha22109
fuente