Каков канонический способ выяснить, сохранена ли модель Django в db?
Обычно я проверяю if obj.pk
на knwo, если объекты сохранены. Однако это не будет работать, если в некоторых полях установлено primary_key = True
. Например, я установил user = models.OneToOneField(User, primary_key=True)
на мой UserProfile
.
Каков канонический способ узнать, сохранена ли модель Django в db?
Ответы
Ответ 1
Важное примечание (по состоянию на 6 мая 19 года): если в ваших моделях используются поля UUID (или другой метод генерации внутреннего идентификатора, используйте self._state.adding
как указано в комментариях.
На самом деле, obj.pk
- самый канонический способ. Сам Джанго часто не "знает", сохранен объект или нет. Согласно справочному экземпляру модели django, если первичный ключ уже задан, он проверяет вызовы save()
, выбирая идентификатор в базе данных перед любой вставкой.
Даже если вы установите user = models.OneToOneField(..., primary_key=True)
атрибут .pk
все равно будет указывать на правильный первичный ключ (наиболее вероятно, user_id
), и вы можете использовать его и установить его так, как если бы это было то же свойство,
Если вы хотите знать, после того как объект был сохранен, вы можете поймать сигнал post_save. Этот сигнал срабатывает при сохранении модели, и при желании вы можете добавить в модель свой собственный атрибут приложения, например, obj.was_saved = True
. Я думаю, что django избегает этого, чтобы поддерживать чистоту своих экземпляров, но нет никакой реальной причины, почему вы не могли бы сделать это для себя. Вот минимальный пример:
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)
Вы можете поочередно заставить эту функцию работать для всех моделей в вашем приложении, просто подключив сигнал без указания аргумента sender=
. Остерегайтесь, однако, вы можете создать неопределенное поведение, если переопределите свойство в чужом экземпляре модели, который вы импортируете.
Ответ 2
В настоящее время вы можете проверить:
self._state.adding
Это значение устанавливается QuerySet.iterator()
для объектов, которые еще не добавлены в базу данных. Вы не можете использовать это значение в методе __init__()
еще, как оно установлено после создания объекта.
Ответ 3
Допустим, что obj
является экземпляром MyModel
. Затем мы могли бы использовать следующий блок кода, чтобы проверить, есть ли в базе данных уже такой первичный ключ:
if obj.pk is None:
# Definitely doesn't exist, since there 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
Это лучше, чем проверка того, obj.pk is None
, потому что вы могли бы сделать
obj = MyModel()
obj.pk = 123
затем
obj.pk is None # False
Это очень вероятно, если вы не используете поле autoincrement id
в качестве первичного ключа, а вместо него - естественное.
Или, как заметил Матфей в комментариях, вы могли бы сделать
obj.delete()
после которого у вас все еще есть
obj.pk is None # False
Ответ 4
@Краст ответ был хорошим, но я думаю, что он неполный. Код, который я использую в своих модульных тестах для определения того, находится ли объект в базе данных, выглядит следующим образом. Ниже я объясню, почему я думаю, что это лучше, чем проверка obj.pk is None
.
Мое решение
from django.test import TestCase
class TestCase(TestCase):
def assertInDB(self, obj, msg=None):
"""Test for obj 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 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)
Примечания. Используйте приведенный выше код с осторожностью, если вы используете пользовательские менеджеры на своих моделях, именами, отличными от objects
. (Я уверен, что есть способ заставить Django рассказать вам, что такое менеджер по умолчанию.) Кроме того, я знаю, что /assert(Not)?InDB/
не являются именами метода PEP 8, но я использовал стиль для остальных пакетов unittest
б.
Обоснование
Я думаю, что assertInDB(obj)
лучше, чем assertIsNotNone(obj.pk)
, из-за следующего случая. Предположим, у вас есть следующая модель.
from django.db import models
class Node(models.Model):
next = models.OneToOneField('self', null=True, related_name='prev')
Node
моделирует двойной список: вы можете прикреплять произвольные данные к каждому node с помощью внешних ключей, а хвост - Node
obj, такой как obj.next is None
. По умолчанию Django добавляет ограничение SQL ON DELETE CASCADE
к первичному ключу Node
. Предположим теперь, что у вас есть list
узлы длины n такие, что nodes[i].next == nodes[i + 1]
для я в [0, n - 1). Предположим, вы вызываете nodes[0].delete()
. В моих тестах на Django 1.5.1 на Python 3.3 я обнаружил, что nodes[i].pk is not None
для я в [1, n) и только nodes[0].pk is None
. Тем не менее, мои методы /assert(Not)?InDB/
выше правильно обнаружили, что nodes[i]
для я в [1, n) действительно был удален.