Django: Отримайте об'єкт із БД або "None", якщо нічого не відповідає


101

Чи є якась функція Django, яка дозволить мені отримати об'єкт із бази даних, або None, якщо нічого не відповідає?

Зараз я використовую щось на кшталт:

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

Але це не дуже зрозуміло, і це брудно мати всюди.


2
Ви знаєте, що ви можете просто використовувати foo = foo [0], якщо foo else Немає
Visgean Skeloru

2
У Python є потрійний оператор, вам не доведеться використовувати булі оператори. Також len(foo)погано : " Примітка: Не використовуйте len () на QuerySets, якщо все, що ви хочете зробити, це визначити кількість записів у наборі. Набагато ефективніше обробляти підрахунок на рівні бази даних, використовуючи SELECT COUNT SQL (), і Django забезпечує метод count () саме з цієї причини. " Переписано:foo = foo[0] if foo.exists() else None
mustafa.0x


1
@ mustafa.0x Я не думаю, що це стосується тут. Тут перевірка на кількість запустила б ще один запит. Тоді ми знову запитуємо, щоб отримати фактичні дані. Це випадок, коли фільтрування та підрахунок мають більше сенсу ... але не більше сенсу, ніж фільтрування та виклик first(): P
MicronXD

Відповіді:


88

У Django 1.6 ви можете використовувати first()метод Queryset. Він повертає перший об'єкт, відповідний набору запитів, або None, якщо немає відповідного об'єкта.

Використання:

p = Article.objects.order_by('title', 'pub_date').first()

19
Це не є гарною відповіддю, якщо знайдено кілька об'єктів, то MultipleObjectsReturned()вони не піднімаються, як це зафіксовано тут . Ви можете знайти кілька кращих відповідей тут
сонливий

25
@sleepycal Я не згоден. В first()роботі точно так , як передбачає , щоб. Він повертає перший об'єкт у результати запиту і не піклується про те, чи знайдено кілька результатів. Якщо вам потрібно перевірити наявність повернених кількох об'єктів, слід скористатися .get()натомість.
Сезар Канаса

7
[ред.] Як сказав ОП, .get()не підходить для його потреб. Правильну відповідь на це питання можна знайти нижче за допомогою @kaapstorm, і явно є більш підходящою відповіддю. Зловживання filter()цим способом може призвести до несподіваних наслідків, чого, можливо, ОП не усвідомив (якщо я тут чогось не пропускаю)
сонливий

1
@sleepycal Які ненавмисні наслідки виникають від такого підходу?
HorseloverFat

12
Якщо обмеження вашої бази даних не керують унікальністю об'єктів, ви можете вражати крайові випадки, коли ваша логіка передбачає, що буде лише один об'єкт (який вибирається першим ()), але насправді існує кілька об'єктів. Використання get () замість first () дає додатковий рівень захисту, підвищуючи MultipleObjectsReturned(). Якщо результат, який повертається, не очікує повернення декількох об'єктів, тоді він не повинен розглядатися як такий. Був довго дебати про це тут
sleepycal

139

Є два способи зробити це;

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
Мені не подобається, що він використовує винятки для функціональності - це погана практика більшості мов. Як це в python?
pcv

18
Ось так працює ітерація Python (підвищує StopIteration), і це основна частина мови. Я не є великим прихильником спроб / крім функціональності, але, здається, вважається пітонічним.
Метт Луонго

4
@pcv: немає надійної загальноприйнятої мудрості щодо "більшості" мов. Я гадаю, ви думаєте про якусь конкретну мову, якою ви користуєтесь, але будьте обережні з такими кванторами. Винятки можуть бути настільки повільними (або "досить швидкими" з цього приводу), як будь-що в Python. Повертаючись до питання - це недолік фреймворку, що вони не запропонували жодного querysetметоду зробити це до 1.6! А що стосується цієї конструкції - вона просто незграбна. Це не "зло", тому що якийсь автор підручника улюбленої мови сказав так. Спробуйте google: "просити пробачення, а не дозволу".
Томаш Гандор

@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

Ви також можете спробувати використовувати джанго дратівливого (він має ще одну корисну функцію!)

встановіть його за допомогою:

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

Здається, це охоплює всі найкращі моменти інших відповідей. Хороший :)
Едвард Ньюелл

@emispowder, що робити, якщо я не хочу повернути жодного, я хочу повернути попереджувальне повідомлення на зразок "відсутні відповідні дані", що відображається на сторінці SAME?
Héléna

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 / default)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.