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

102

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

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

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

Но это не очень понятно, и иметь везде грязно.

Дэвид Волевер
источник
2
Вы знаете, что можете просто использовать foo = foo [0] if foo else None
Visgean Skeloru
2
В Python есть тернарный оператор, вам не нужно использовать логические операторы. Кроме того, len(foo)это плохо : " Примечание. Не используйте len () в QuerySets, если все, что вам нужно, - это определить количество записей в наборе. Гораздо эффективнее обрабатывать счет на уровне базы данных, используя SQL SELECT COUNT. (), и Django предоставляет метод count () именно по этой причине. ". Переписано:foo = foo[0] if foo.exists() else None
mustafa.0x 06
1
@ mustafa.0x Я не думаю, что здесь это применимо. Здесь проверка количества запускает другой запрос. Затем у нас снова будет запрос, чтобы получить фактические данные. Это тот случай, когда фильтрация, а затем подсчет имели бы больше смысла ... но не больше смысла, чем фильтрация и вызов first(): P
MicronXD

Ответы:

88

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

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

p = Article.objects.order_by('title', 'pub_date').first()
Сезар Канасса
источник
19
Это не лучший ответ, если обнаружено несколько объектов, не MultipleObjectsReturned()возникает, как описано здесь . Вы можете найти лучшие ответы здесь
Sleepycal
25
@sleepycal Я не согласен. В first()работе точно так , как предполагает , чтобы. Он возвращает первый объект в результате запроса и не заботится о том, найдено ли несколько результатов. Если вам нужно проверить наличие нескольких возвращенных объектов, вы должны использовать .get()вместо этого.
Cesar Canassa
7
[отредактировано] Как сказал ОП, .get()не подходит для его нужд. Правильный ответ на этот вопрос можно найти ниже, используя @kaapstorm, и он явно является более подходящим ответом. Злоупотребление filter()этим способом может привести к неожиданным последствиям, чего OP, вероятно, не осознавал (если я что-то здесь не
упускаю
1
@sleepycal Какие непредвиденные последствия возникают из этого подхода?
HorseloverFat
12
Если ограничения вашей базы данных не позволяют правильно управлять уникальностью объектов, вы можете столкнуться с крайними случаями, когда ваша логика ожидает, что будет только один объект (который выбирается first ()), но на самом деле существует несколько объектов. Использование get () вместо first () дает вам дополнительный уровень защиты за счет повышения MultipleObjectsReturned(). Если не ожидается, что возвращаемый результат вернет несколько объектов, его не следует рассматривать как таковой. Был долго дебаты об этом здесь
sleepycal
140

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

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
Мне не нравится тот факт, что он использует исключения для функциональности - это плохая практика для большинства языков. Как это в питоне?
pcv
18
Вот как работает итерация Python (вызывает StopIteration), и это основная часть языка. Я тоже не большой поклонник функциональности try / except, но, похоже, она считается Pythonic.
Мэтт Луонго
4
@pcv: нет надежного общепринятого мнения о "большинстве" языков. Я полагаю, вы думаете о каком-то конкретном языке, который используете, но будьте осторожны с такими квантификаторами. Исключения могут быть такими же медленными (или «достаточно быстрыми» в этом отношении), как и все в Python. Возвращаясь к вопросу - это недостаток фреймворка, что они не предоставляли никакого querysetметода для этого до 1.6! Но что касается этой конструкции - она ​​просто корявая. Это не «зло», потому что так сказал автор туториала на вашем любимом языке. Попробуйте погуглить: «просите прощения, а не разрешения».
Tomasz Gandor
@TomaszGandor: Да, это неуклюже и трудно читать, что бы ни говорилось в вашем любимом учебнике по языку. Когда я вижу исключение, я предполагаю, что происходит что-то нестандартное. Читаемость имеет значение. Но я согласен, когда нет другого разумного варианта, вы должны пойти на это, и ничего плохого не произойдет.
pcv
@pcv getМетод не должен возвращаться None, поэтому исключение имеет смысл. Вы должны использовать его в тех случаях, когда вы уверены, что объект будет там / объект «предполагается» там. Также использование подобных исключений считается «хорошо», потому что в этом есть смысл. Вам нужен getдругой контракт, поэтому вы перехватываете исключение и подавляете его, что и является изменением, которое вам нужно.
Дэн Пассаро
83

Чтобы добавить пример кода к ответу Сорки (я бы добавил это как комментарий, но это мой первый пост, и у меня недостаточно репутации, чтобы оставлять комментарии), я реализовал настраиваемый менеджер 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')
каапсторм
источник
Это очень элегантно.
NotSimon
13

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

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

pip install django-annoying

from annoying.functions import get_object_or_None
get_object_or_None(Foo, bar=baz)
llazzaro
источник
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
эмиссионный порошок
источник
Кажется, это отражает все лучшие моменты других ответов. Хороший :)
Эдвард Ньюэлл
@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`'s 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

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

  1. obj = get_unique_or_none(Model, *args, **kwargs) как обсуждалось ранее
  2. obj = get_unique_or_none(Model, parent.children, *args, **kwargs)
Гэри
источник
-1

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

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

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

ТомерП
источник