What is the canonical way to find out if a Django model is saved to db?
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.
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.
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.
Or, as Matthew pointed out in the comments, you could do
obj.delete()
after which you still have
obj.pk is None # False
@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.