Як я можу отримати об'єкт, якщо він існує, або Немає, якщо його немає?


223

Коли я прошу менеджера моделі отримати об’єкт, він піднімається, DoesNotExistколи немає відповідного об'єкта.

go = Content.objects.get(name="baby")

Замість того DoesNotExist, як я можу goбути Noneнатомість?

Відповіді:


332

Не існує "побудованого" способу для цього. Джанго щоразу підніме виняток DoesNotExist. Ідіоматичний спосіб впоратися з цим у python - це загортати його у спробу лову:

try:
    go = SomeModel.objects.get(foo='bar')
except SomeModel.DoesNotExist:
    go = None

Що я зробив, це підкласи моделей. Менеджер, створити safe_getподібний код вище і використовувати цей менеджер для моїх моделей. Таким чином , ви можете написати: SomeModel.objects.safe_get(foo='bar').


7
Хороше використання SomeModel.DoesNotExist замість імпорту винятку.
суперміт

196
Це рішення триває чотири рядки. Для мене це занадто багато. З django 1.6 ви можете використовувати SomeModel.objects.filter(foo='bar').first()це повертає перший матч, або None. Він не провалюється, якщо є кілька випадків типуqueryset.get()
guettli

9
Я думаю, що це поганий стиль - надмірне використання виключень для обробки випадків за замовчуванням. Так, "простіше просити пробачення, ніж дозволу". Але, на мої очі, все ж слід використовувати виняток для винятків.
Костянтин Шуберт

8
Явне краще, ніж неявне. Якщо немає причини для використання, filter().first()я думаю, що виняток - це шлях.
christianbundy

6
Використання first () - це лише добра ідея, якщо вам все одно, коли є кратні. В іншому випадку це рішення є вищим, оскільки воно все-таки кине Виняток, якщо ви несподівано знайдете кілька об'єктів, що, як правило, ви хотіли б у цьому випадку.
rossdavidh

182

Оскільки django 1.6, ви можете використовувати метод first (), наприклад:

Content.objects.filter(name="baby").first()

26
У цьому випадку помилка не виникає, якщо є більше одного матчу.
Костянтин Шуберт

7
"FeroxTL" вам потрібно віддати кредит @guettli за цю відповідь, оскільки він прокоментував це прийняту відповідь за рік до вашого повідомлення.
colm.anseo

7
@colminator Я скоріше скажу, що гуетлі повинен дізнатися, що нова відповідь не належить до коментаря, якщо він хоче підняти свою репутацію stackoverflow :) FeroxTL повинен отримати бали за те, щоб зробити щось приховане як коментар більш чіткий як відповідь. Ваш коментар достатньо кредитний для гуетлі, я думаю, і його не слід додавати у відповідь, якщо це було вашою пропозицією.
Йоаким

3
@Joakim У мене немає проблем з публікацією нової "відповіді" - просто щоб дати кредит там, де це належить :-)
colm.anseo

3
Що щодо ефективності цього підходу порівняно з прийнятою відповіддю?
MaxCore

36

З док. Джанго

get()створює DoesNotExistвиняток, якщо об’єкт не знайдений для заданих параметрів. Цей виняток також є атрибутом класу моделі. У DoesNotExist виняток успадковує відdjango.core.exceptions.ObjectDoesNotExist

Ви можете зловити виняток і призначити Noneперехід.

from django.core.exceptions import ObjectDoesNotExist
try:
    go  = Content.objects.get(name="baby")
except ObjectDoesNotExist:
    go = None

33

Для цього можна створити загальну функцію.

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

Використовуйте це як нижче:

go = get_or_none(Content,name="baby")

go буде "None", якщо жоден запис, який не відповідає, ще не поверне вміст.

Примітка. Він збільшує виняток MultipleObjectsReturned, якщо більше одного запису повертається для name = "baby"



14

Щоб полегшити ситуацію, ось фрагмент коду, який я написав, на основі вхідних даних із чудових відповідей тут:

class MyManager(models.Manager):

    def get_or_none(self, **kwargs):
        try:
            return self.get(**kwargs)
        except ObjectDoesNotExist:
            return None

І тоді у вашій моделі:

class MyModel(models.Model):
    objects = MyManager()

Це воно. Тепер у вас є MyModel.objects.get (), а також MyModel.objetcs.get_or_none ()


7
також не забудьте імпортувати: з django.core.exceptions import ObjectDoesNotExist
Моті Радомскі

13

ви можете використовувати existsз фільтром:

Content.objects.filter(name="baby").exists()
#returns False or True depending on if there is anything in the QS

просто альтернатива, якщо ви хочете знати, чи вона існує


4
Це може викликати додатковий виклик бази даних, коли існує. Непогана ідея
Крістофер

@Christoffer не знає, чому це буде додатковий дзвінок. Відповідно до документів :Note: If you only want to determine if at least one result exists (and don’t need the actual objects), it’s more efficient to use exists().
Анупам

2
@Christoffer Я думаю, ти маєш рацію. Тепер я знову читаю питання, і ОП насправді хоче повернути фактичний об’єкт. Таким чином, exists()він буде використаний з ifпунктом перед тим, як отримати об'єкт, отже, спричинивши подвійне потрапляння на db. Я все одно буду тримати коментар навколо, якщо він допомагає комусь іншому.
Анупам

7

Поводження з винятками в різних точках ваших поглядів може бути справді громіздким .. Щодо визначення користувальницького менеджера моделей у файлі models.py, наприклад

class ContentManager(model.Manager):
    def get_nicely(self, **kwargs):
        try:
            return self.get(kwargs)
        except(KeyError, Content.DoesNotExist):
            return None

а потім включити його до змістового класу Model

class Content(model.Model):
    ...
    objects = ContentManager()

Таким чином, це може бути легко вирішено у поглядах, тобто

post = Content.objects.get_nicely(pk = 1)
if post:
    # Do something
else:
    # This post doesn't exist

1
Мені дуже подобається це рішення, але не вдалося змусити його працювати так, як це використовується при використанні python 3.6. Хотів залишити примітку, що змінивши повернення в ContentManager, щоб return self.get(**kwargs)він працював на мене. Не кажучи, що у відповіді нічого поганого, лише порада для тих, хто намагається використовувати його з пізнішими версіями (або з будь-яким іншим, що перешкоджало мені працювати).
skagzilla

7

Це одна з таких дратівливих функцій, яку ви, можливо, не хочете повторно реалізовувати:

from annoying.functions import get_object_or_None
#...
user = get_object_or_None(Content, name="baby")

Я перевірив код, get_object_or_Noneале виявив, що він все ще піднімається, MultipleObjectsReturnedякщо більше одного об'єкта. Таким чином, користувач може розглянути питання про оточення з try-except(що ця функція вже є try-except).
Джон Панг

4

Якщо ви хочете просте однолінійне рішення, яке не передбачає обробку виключень, умовні заяви або вимогу Django 1.6+, зробіть це замість цього:

x = next(iter(SomeModel.objects.filter(foo='bar')), None)

4

Я думаю, що це не погана ідея get_object_or_404()

from django.shortcuts import get_object_or_404

def my_view(request):
    my_object = get_object_or_404(MyModel, pk=1)

Цей приклад еквівалентний:

from django.http import Http404

def my_view(request):
    try:
        my_object = MyModel.objects.get(pk=1)
    except MyModel.DoesNotExist:
        raise Http404("No MyModel matches the given query.")

Більше про get_object_or_404 () можна прочитати в онлайн-документації django.


2

З django 1.7 ви можете:

class MyQuerySet(models.QuerySet):

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


class MyBaseModel(models.Model):

    objects = MyQuerySet.as_manager()


class MyModel(MyBaseModel):
    ...

class AnotherMyModel(MyBaseModel):
    ...

Перевага "MyQuerySet.as_manager ()" полягає в тому, що працюватимуть обидві дії:

MyModel.objects.filter(...).get_or_none()
MyModel.objects.get_or_none()

1

Ось варіант щодо функції помічника, що дозволяє необов'язково передавати QuerySetекземпляр, якщо ви хочете отримати унікальний об'єкт (за наявності) з набору запитів, відмінного allвід набору об'єктів моделі (наприклад, із підмножини дочірніх елементів, що належать до батьківський екземпляр):

def get_unique_or_none(model, queryset=None, **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(**kwargs)
    except model.DoesNotExist:
        return None

Це можна використовувати двома способами, наприклад:

  1. obj = get_unique_or_none(Model, **kwargs) як попередньо обговорювалося
  2. obj = get_unique_or_none(Model, parent.children, **kwargs)

1

Без винятку:

if SomeModel.objects.filter(foo='bar').exists():
    x = SomeModel.objects.get(foo='bar')
else:
    x = None

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

try:
   x = SomeModel.objects.get(foo='bar')
except SomeModel.DoesNotExist:
   x = None

Існує трохи аргументу про те, коли слід використовувати виняток у python. З одного боку, "простіше просити пробачення, ніж дозволу". Хоча я погоджуюся з цим, я вважаю, що виняток повинен залишатися, ну, виняток, і "ідеальний випадок" повинен працювати без удару.


1

Ми можемо використовувати виняток Django вбудований у моделі, названі як .DoesNotExist. Отже, нам не потрібно імпортуватиObjectDoesNotExist винятки.

Замість цього роби:

from django.core.exceptions import ObjectDoesNotExist

try:
    content = Content.objects.get(name="baby")
except ObjectDoesNotExist:
    content = None

Ми можемо це зробити:

try:
    content = Content.objects.get(name="baby")
except Content.DoesNotExist:
    content = None

0

Це копія коду з get_object_or_404 Django, за винятком того, що метод повертає None. Це надзвичайно корисно, коли нам потрібно використовувати only()запит лише для отримання певних полів. Цей метод може приймати модель або набір запитів.

from django.shortcuts import _get_queryset


def get_object_or_none(klass, *args, **kwargs):
    """
    Use get() to return an object, or return None if object
    does not exist.
    klass may be a Model, Manager, or QuerySet object. All other passed
    arguments and keyword arguments are used in the get() query.
    Like with QuerySet.get(), MultipleObjectsReturned is raised if more than
    one object is found.
    """
    queryset = _get_queryset(klass)
    if not hasattr(queryset, 'get'):
        klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
        raise ValueError(
            "First argument to get_object_or_none() must be a Model, Manager, "
            "or QuerySet, not '%s'." % klass__name
        )
    try:
        return queryset.get(*args, **kwargs)
    except queryset.model.DoesNotExist:
        return None

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