2010-01-10 13 views

Respuesta

30

En realidad, obj.pk es la manera más canónica. Django a menudo no "sabe" si el objeto está guardado o no. De acuerdo con the django model instance reference, si ya hay una clave primaria establecida, comprueba las llamadas save() seleccionando para el ID en la base de datos antes de cualquier inserción.

Incluso si configura user = models.OneToOneField(..., primary_key=True), el atributo .pk aún apuntará a la clave principal correcta (muy probablemente user_id) y puede usarlo y configurarlo como si fuera la misma propiedad.

Si desea saber después de guardar un objeto, puede tomar el post_save signal. Esta señal se activa en el modelo de guardado, y si lo desea, puede agregar su propio atributo específico de la aplicación al modelo, por ejemplo obj.was_saved = True. Creo que django evita esto para mantener sus instancias limpias, pero no hay una razón real por la que no puedas hacer esto por ti mismo. Aquí está un ejemplo mínimo:

from django.db.models.signals import post_save 
from myapp.models import MyModel 

def save_handler(sender, instance, **kwargs): 
    instance.was_saved = True 

post_save.connect(save_handler, sender=MyModel) 

Usted puede tener alternativamente este trabajo función para todos los modelos en su aplicación, simplemente conectando la señal sin especificar el argumento sender=. Sin embargo, tenga cuidado, puede crear comportamientos indefinidos si anula una propiedad en la instancia de modelo de otra persona que está importando.

+14

hay un kwarg booleano 'created' en la señal de guardado de la publicación de forma predeterminada, por lo que no es necesario que inserte el suyo propio. – kibitzer

+0

Para Django moderno, debe tenerse en cuenta que esto no funciona cuando utiliza sus propios campos de clave primaria con un generador de valores predeterminado como un UUIDField u otro método para generar identificadores. Como se señaló en otra respuesta, debe usar 'self._state.adding'. https://stackoverflow.com/a/19379636/23972 –

6

Digamos que obj es una instancia de MyModel. Entonces podemos usar el siguiente bloque de código para comprobar si ya existe una instancia con esa clave primaria en la base de datos:

if obj.pk is None: 
    # Definitely doesn't exist, since there's no `pk`. 
    exists = False 
else: 
    # The `pk` is set, but it doesn't guarantee exists in db. 
    try: 
     obj_from_db = MyModel.objects.get(pk=obj.pk) 
     exists = True 
    except MyModel.DoesNotExist: 
     exists = False 

Esto es mejor que comprobar si obj.pk is None, ya que podría hacer

obj = MyModel() 
obj.pk = 123 

continuación

obj.pk is None # False 

esto es aún muy probable cuando no se utiliza el campo de incremento automático id como la clave primaria, pero uno natural i en su lugar.

O, como Mateo señaló en los comentarios, que podría hacer

obj.delete() 

después de lo cual todavía tienen

obj.pk is None # False 
+1

Esta respuesta me ayudó b/c Encontré que .pk todavía estaba configurado después de un delete(). Gracias. –

3

@ respuesta de Crast era bueno, pero creo incompleta. El código que uso en mi unidad para determinar si un objeto está en la base de datos es el siguiente. Debajo, explicaré por qué creo que es mejor que verificar obj.pk is None.

Mi solución

from django.test import TestCase 
class TestCase(TestCase): 
    def assertInDB(self, obj, msg=None): 
     """Test for obj's presence in the database.""" 
     fullmsg = "Object %r unexpectedly not found in the database" % obj 
     fullmsg += ": " + msg if msg else "" 
     try: 
      type(obj).objects.get(pk=obj.pk) 
     except obj.DoesNotExist: 
      self.fail(fullmsg) 

    def assertNotInDB(self, obj, msg=None): 
     """Test for obj's absence from the database.""" 
     fullmsg = "Object %r unexpectedly found in the database" % obj 
     fullmsg += ": " + msg if msg else "" 
     try: 
      type(obj).objects.get(pk=obj.pk) 
     except obj.DoesNotExist: 
      return 
     else: 
      self.fail(fullmsg) 

Notas: Utilice el código anterior con cuidado si utiliza los administradores personalizados en su nombre de modelos algo más que objects. (Estoy seguro de que hay una forma de que Django le diga cuál es el administrador predeterminado). Además, sé que /assert(Not)?InDB/ no son nombres de un método PEP 8, pero utilicé el estilo del resto del paquete unittest utilizado.

Justificación

La razón creo que es mejor que assertInDB(obj)assertIsNotNone(obj.pk) es por lo siguiente caso. Supongamos que tiene el siguiente modelo.

from django.db import models 
class Node(models.Model): 
    next = models.OneToOneField('self', null=True, related_name='prev') 

Node modelos de una lista doblemente enlazada: puede adjuntar datos arbitrarios a cada nodo utilizando las claves externas y la cola es la Nodeobj tal que obj.next is None. De forma predeterminada, Django agrega la restricción de SQL ON DELETE CASCADE a la clave principal de Node. Ahora, supongamos que tiene un listnodos de longitud n tal que nodes[i].next == nodes[i + 1] para i en [0, n - 1). Supongamos que llama al nodes[0].delete(). En mis pruebas en Django 1.5.1 en Python 3.3, encontré que nodes[i].pk is not None para i en [1, n) y solo nodes[0].pk is None. Sin embargo, mis métodos /assert(Not)?InDB/ de más arriba detectaron correctamente que nodes[i] para i en [1, n] habían sido eliminados.

+0

Esto será cierto para todas las versiones de Django y Python porque los elementos en su lista de 'nodos' son objetos del modelo instanciados y, por lo tanto, ya no se ven afectados por nada que ocurra en la base de datos. La comprobación de 'pk no es None' al menos le indica si la instancia * en algún momento * se ha guardado en el archivo db (... suponiendo que nadie le haya asignado manualmente un valor a' pk' en la instancia) – Anentropic

+0

Una mejor alternativa para 'assertInDb' sería:' obj.refresh_from_db() ', y para' assertNotInDb': 'con self.assertRaises (obj.DoesNotExist): obj.refresh_from_db()'. –

20

Hoy en día se puede comprobar:

self._state.adding 

Este valor se establece por el QuerySet.iterator() de objetos que no se hayan añadido sin embargo, en la base de datos. No puede usar este valor en el método __init__() todavía, ya que se configura después de que se construye el objeto.

+1

Gracias no sabía que esto existió trabajó en Django 1.8 (Python 3.x) –

+3

Se pone aún mejor en Django 1.8, ahora hay 'Model.from_db()' – vdboor

+5

esta debería ser la respuesta actualmente aceptada, porque la búsqueda de la clave principal no funciona si usa campos UUID, porque esos se generan automáticamente cuando se crea una instancia. – nemesisdesign

Cuestiones relacionadas