Django: получить объект из БД или None, если ничего не найдено

90

Есть ли какая-либо функция Django, которая позволит мне получить объект из базы данных или None, если ничего не соответствует?

Сейчас я использую что-то вроде:

foo = Foo.objects.filter(bar=baz)
foo = len(foo) > 0 and foo.get() or None

Но это не очень понятно, и это беспорядочно повсюду.

  • 2
    Вы знаете, что можете просто использовать foo = foo [0], если foo else None
  • 2
    В Python есть троичный оператор, вам не нужно использовать логические операторы. Кроме того, len(foo) - это плохо : « Примечание. Не используйте len () в QuerySets, если все, что вам нужно, это определить количество записей в наборе. Намного эффективнее обрабатывать счет на уровне базы данных, используя SQL SELECT COUNT (), и Django предоставляет метод count () именно по этой причине. " Переписано: foo = foo[0] if foo.exists() else None
Показать ещё 3 комментария
Теги:

8 ответов

76
Лучший ответ

В Django 1.6 вы можете использовать метод first() Queryset. Он возвращает первый объект, соответствующий запросу, или None, если нет соответствующего объекта.

Использование:

p = Article.objects.order_by('title', 'pub_date').first()
  • 16
    Это не очень хороший ответ, если найдено несколько объектов, тогда MultipleObjectsReturned() не вызывается, как описано здесь . Вы можете найти лучшие ответы здесь
  • 23
    @sleepycal Я не согласен. first() работает точно так, как он полагает. Возвращает первый объект в результате запроса и не заботится, если найдено несколько результатов. Если вам нужно проверить наличие нескольких возвращенных объектов, используйте вместо этого .get() .
Показать ещё 5 комментариев
127

Есть два способа сделать это:

try:
    foo = Foo.objects.get(bar=baz)
except model.DoesNotExist:
    foo = None

Или вы можете использовать обертку:

def get_or_none(model, *args, **kwargs):
    try:
        return model.objects.get(*args, **kwargs)
    except model.DoesNotExist:
        return None

Назовите его так:

foo = get_or_none(Foo, baz=bar)
  • 8
    Мне не нравится тот факт, что он использует исключения для функциональности - это плохая практика в большинстве языков. Как это в питоне?
  • 17
    Вот как работает итерация Python (вызывает StopIteration), и это основная часть языка. Я не большой поклонник пробной / исключающей функциональности, но, похоже, это считается Pythonic.
Показать ещё 3 комментария
79

Чтобы добавить некоторый пример кода в sorki answer (я бы добавил это как комментарий, но это мой первый пост, и у меня недостаточно репутации, чтобы оставлять комментарии), я внедрил пользовательский менеджер get_or_none следующим образом:

from django.db import models

class GetOrNoneManager(models.Manager):
    """Adds get_or_none method to objects
    """
    def get_or_none(self, **kwargs):
        try:
            return self.get(**kwargs)
        except self.model.DoesNotExist:
            return None

class Person(models.Model):
    name = models.CharField(max_length=255)
    objects = GetOrNoneManager()

И теперь я могу это сделать:

bob_or_none = Person.objects.get_or_none(name='Bob')
  • 0
    Ах, хорошо. Благодарю.
  • 0
    Эй, это очень мило.
Показать ещё 2 комментария
13

Вы также можете попробовать django раздражать (у него есть еще одна полезная функция!)

установите его с помощью:

pip install django-annoying

from annoying.functions import get_object_or_None
get_object_or_None(Foo, bar=baz)
9

Дайте Foo свой пользовательский менеджер. Это довольно просто - просто введите свой код в пользовательский менеджер, установите пользовательский менеджер в своей модели и вызовите его с помощью Foo.objects.your_new_func(...).

Если вам нужна универсальная функция (использовать ее на любой модели, а не только с помощью настраиваемого менеджера), напишите свой собственный и поместите ее где-нибудь на свой путь python и импортируйте, а не беспорядочно.

4

Если вы выполняете это через менеджер или общую функцию, вы также можете поймать "MultipleObjectsReturned" в инструкции TRY, так как функция get() будет повышать ее, если ваши kwargs извлекают более одного объекта.

Основываясь на общей функции:

def get_unique_or_none(model, *args, **kwargs):
    try:
        return model.objects.get(*args, **kwargs)
    except (model.DoesNotExist, model.MultipleObjectsReturned), err:
        return None

и в менеджере:

class GetUniqueOrNoneManager(models.Manager):
    """Adds get_unique_or_none method to objects
    """
    def get_unique_or_none(self, *args, **kwargs):
        try:
            return self.get(*args, **kwargs)
        except (self.model.DoesNotExist, self.model.MultipleObjectsReturned), err:
            return None
  • 0
    Кажется, это отражает все лучшие моменты других ответов. Хороший :)
  • 0
    @emispowder, что если я не хочу возвращать None, я хочу вернуть предупреждающее сообщение, например «нет соответствующих данных», отображаемое на той же странице?
0

Здесь приведена вариация вспомогательной функции, которая позволяет вам в случае необходимости передать экземпляр QuerySet, если вы хотите получить уникальный объект (если он есть) из запроса, отличного от набора объектов all объектов (например, из подмножества дочерних элементов, принадлежащих родительскому экземпляру):

def get_unique_or_none(model, queryset=None, *args, **kwargs):
    """
        Performs the query on the specified `queryset`
        (defaulting to the `all` queryset of the `model` default manager)
        and returns the unique object matching the given
        keyword arguments.  Returns `None` if no match is found.
        Throws a `model.MultipleObjectsReturned` exception
        if more than one match is found.
    """
    if queryset is None:
        queryset = model.objects.all()
    try:
        return queryset.get(*args, **kwargs)
    except model.DoesNotExist:
        return None

Это можно использовать двумя способами, например:

  • obj = get_unique_or_none(Model, *args, **kwargs), как обсуждалось ранее
  • obj = get_unique_or_none(Model, parent.children, *args, **kwargs)
  • 0
    Я вижу, что django annoying уже занимается этим делом.
-1

Я думаю, что в большинстве случаев вы можете просто использовать:

foo, created = Foo.objects.get_or_create(bar=baz)

Только если не важно, что новая запись будет добавлена ​​в таблицу Foo (другие столбцы будут иметь значения None/default)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню