[Django]-What is the canonical way to find out if a Django model is saved to db?

45đź‘Ť

âś…

Important Note (as of 6 May ’19): If your models use UUID fields (or other method of internal ID generation, use self._state.adding as mentioned in the comments.

Actually,obj.pk is the most canonical way. Django itself often doesn’t “know” if the object is saved or not. According to the django model instance reference, if there is a primary key set already, it checks onsave() calls by selecting for the id in the database before any insert.

Even if you set user = models.OneToOneField(..., primary_key=True) the .pk attribute will still point to the correct primary key (most likely user_id) and you can use it and set it as if it was the same property.

If you want to know after an object has been saved, you can catch the post_save signal. This signal is fired on model saves, and if you want you can add your own application-specific attribute to the model, for example obj.was_saved = True. I think django avoids this to keep their instances clean, but there’s no real reason why you couldn’t do this for yourself. Here is a minimal example:

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)

You can alternately have this function work for all models in your app by simply connecting the signal without specifying the sender= argument. Beware though, you can create undefined behaviours if you override a property on someone else’s model instance that you are importing.

👤Crast

56đź‘Ť

Nowadays you can check for:

self._state.adding

This value is set by the QuerySet.iterator() for objects which are not added yet in the database. You can’t use this value in the __init__() method yet, as it’s set after the object is constructed.

👤vdboor

10đź‘Ť

Lets say obj is an instance of MyModel. Then we could use the following block of code to check if there already is an instance with that primary key in the database:

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

This is better than checking whether obj.pk is None, because you could do

obj = MyModel()
obj.pk = 123

then

obj.pk is None  # False

This is even very likely when you don’t use the autoincrement id field as the primary key but a natural one instead.

Also, prior to Django 4.1, there was another case where obj.pk is None might be unreliable: As Matthew pointed out in the comments, you could do

obj.delete()

after which you still have

obj.pk is None  # False

(In Django 4.1, obj.delete() sets obj.pk to None.)

👤Rubinous

5đź‘Ť

@Crast’s answer was good, but I think incomplete. The code I use in my unit tests for determining if an object is in the database is as follows. Below it, I will explain why I think it is superior to checking if obj.pk is None.

My solution

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)

Notes: Use the above code with care if you use custom managers on your models name something other than objects. (I’m sure there’s a way to get Django to tell you what the default manager is.) Further, I know that /assert(Not)?InDB/ are not a PEP 8 method names, but I used the style the rest of the unittest package used.

Justification

The reason I think assertInDB(obj) is better than assertIsNotNone(obj.pk) is because of the following case. Suppose you have the following model.

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

Node models a doubly linked list: you can attach arbitrary data to each node using foreign keys and the tail is the Node obj such that obj.next is None. By default, Django adds the SQL constraint ON DELETE CASCADE to the primary key of Node. Now, suppose you have a list nodes of length n such that nodes[i].next == nodes[i + 1] for i in [0, n – 1). Suppose you call nodes[0].delete(). In my tests on Django 1.5.1 on Python 3.3, I found that nodes[i].pk is not None for i in [1, n) and only nodes[0].pk is None. However, my /assert(Not)?InDB/ methods above correctly detected that nodes[i] for i in [1, n) had indeed been deleted.

👤wkschwartz

Leave a comment