Подтвердить что ты не робот

Каков канонический способ выяснить, сохранена ли модель Django в db?

Обычно я проверяю if obj.pk на knwo, если объекты сохранены. Однако это не будет работать, если в некоторых полях установлено primary_key = True. Например, я установил user = models.OneToOneField(User, primary_key=True) на мой UserProfile.

Каков канонический способ узнать, сохранена ли модель Django в db?

4b9b3361

Ответ 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) действительно был удален.