Bagaimana saya mendapatkan objek jika ada, atau tidak ada jika tidak ada?

222

Ketika saya meminta manajer model untuk mendapatkan objek, itu muncul DoesNotExistketika tidak ada objek yang cocok.

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

Alih-alih DoesNotExist, bagaimana saya bisa gomenjadi Nonegantinya?

TIMEX
sumber

Jawaban:

332

Tidak ada cara 'bawaan' untuk melakukan ini. Django akan memunculkan exception DoesNotExist setiap saat. Cara idiomatis untuk menangani ini dengan python adalah membungkusnya dengan try catch:

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

Apa yang saya lakukan adalah membuat subclass models.Manager, buat safe_getkode seperti di atas dan gunakan manajer itu untuk model saya. Dengan cara itu Anda dapat menulis: SomeModel.objects.safe_get(foo='bar').

Arthur Debert
sumber
7
Penggunaan SomeModel.DoesNotExist yang bagus alih-alih mengimpor pengecualian juga.
supermitch
195
Solusi ini panjangnya empat baris. Bagi saya ini terlalu banyak. Dengan django 1.6 Anda dapat menggunakan SomeModel.objects.filter(foo='bar').first()ini mengembalikan pertandingan pertama, atau Tidak Ada. Tidak gagal jika ada beberapa contoh sepertiqueryset.get()
guettli
9
Saya pikir itu gaya buruk untuk terlalu sering menggunakan pengecualian untuk menangani kasus default. Ya, "lebih mudah untuk meminta maaf daripada izin". Tapi di mata saya, pengecualian masih harus digunakan untuk pengecualian.
Konstantin Schubert
8
Eksplisit lebih baik daripada implisit. Kecuali ada alasan kinerja untuk menggunakan, filter().first()saya pikir pengecualian adalah cara untuk pergi.
christianbundy
6
Menggunakan first () hanya ide yang bagus jika Anda tidak peduli ketika ada banyak. Jika tidak, solusi ini lebih unggul, karena masih akan mengeluarkan Pengecualian jika Anda secara tak terduga menemukan beberapa objek, yang biasanya merupakan apa yang Anda inginkan terjadi dalam kasus itu.
rossdavidh
181

Sejak django 1.6 Anda dapat menggunakan metode first () seperti:

Content.objects.filter(name="baby").first()
FeroxTL
sumber
26
Dalam hal ini, tidak ada Kesalahan yang muncul jika ada lebih dari satu kecocokan.
Konstantin Schubert
7
'FeroxTL' Anda harus memberi kredit ke @guettli untuk jawaban ini, saat ia mengomentari jawaban yang diterima ini setahun sebelum posting Anda.
colm.anseo
7
@colminator Saya lebih suka mengatakan guettli harus belajar bahwa jawaban baru bukan milik sebagai komentar jika ia ingin meningkatkan reputasi stackoverflow nya :) FeroxTL harus mendapatkan poin untuk membuat sesuatu yang disembunyikan sebagai komentar lebih jelas sebagai jawaban. Komentar Anda cukup kredit untuk guettli, saya pikir dan tidak boleh ditambahkan ke jawaban jika itu saran Anda.
Joakim
3
@ Joakim Saya tidak punya masalah dengan memposting "jawaban" baru - hanya untuk memberi kredit di mana sudah waktunya :-)
colm.anseo
3
Bagaimana dengan kinerja pendekatan ini dibandingkan dengan jawaban yang diterima?
MaxCore
36

Dari django docs

get()memunculkan DoesNotExisteksepsi jika suatu objek tidak ditemukan untuk parameter yang diberikan. Pengecualian ini juga merupakan atribut dari kelas model. The DoesNotExist pengecualian mewarisi daridjango.core.exceptions.ObjectDoesNotExist

Anda dapat menangkap pengecualian dan menetapkan Noneuntuk pergi.

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

Anda dapat membuat fungsi generik untuk ini.

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

Gunakan ini seperti di bawah ini:

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

go akan menjadi None jika tidak ada entri yang cocok dengan yang lain akan mengembalikan entri Konten.

Catatan: Ini akan memunculkan pengecualian MultipleObjectsReturned jika lebih dari satu entri dikembalikan untuk name = "baby"

Ranju R
sumber
14

Untuk mempermudah, berikut cuplikan kode yang saya tulis, berdasarkan masukan dari balasan yang indah di sini:

class MyManager(models.Manager):

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

Dan kemudian dalam model Anda:

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

Itu dia. Sekarang Anda memiliki MyModel.objects.get () serta MyModel.objetcs.get_or_none ()

Moti Radomski
sumber
7
juga, jangan lupa untuk mengimpor: dari django.core.exceptions mengimpor ObjectDoesNotExist
Moti Radomski
13

Anda dapat menggunakan existsdengan filter:

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

hanya alternatif untuk jika Anda hanya ingin tahu apakah itu ada

Ryan Saxe
sumber
4
Itu akan menyebabkan panggilan basis data tambahan saat ada. Bukan ide yang bagus
Christoffer
@ Christoffer tidak yakin mengapa itu menjadi panggilan db ekstra. Sesuai dokumen :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().
Anupam
2
@ Christoffer, saya pikir Anda benar. Saya sekarang membaca pertanyaan lagi dan OP benar-benar ingin objek yang sebenarnya dikembalikan. Jadi exists()akan digunakan dengan ifklausa sebelum mengambil objek sehingga menyebabkan pukulan ganda ke db. Saya akan tetap menyimpan komentar kalau-kalau itu membantu orang lain.
Anupam
7

Menangani pengecualian pada titik yang berbeda dalam tampilan Anda bisa sangat merepotkan..Bagaimana dengan mendefinisikan Manajer Model kustom, dalam file models.py, seperti

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

dan kemudian memasukkannya ke dalam kelas Model konten

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

Dengan cara ini dapat dengan mudah ditangani dalam pandangan yaitu

post = Content.objects.get_nicely(pk = 1)
if post:
    # Do something
else:
    # This post doesn't exist
Adil Malik
sumber
1
Saya sangat suka solusi ini, tetapi tidak bisa membuatnya berfungsi seperti halnya ketika menggunakan python 3.6. Ingin meninggalkan catatan yang mengubah pengembalian di ContentManager return self.get(**kwargs)agar berfungsi untuk saya. Bukan untuk mengatakan ada yang salah dengan jawabannya, hanya tip bagi siapa pun yang mencoba menggunakannya dengan versi yang lebih baru (atau dengan apa pun yang membuatnya tidak bekerja untuk saya).
skagzilla
7

Ini salah satu fungsi menjengkelkan yang mungkin tidak ingin Anda laksanakan kembali:

from annoying.functions import get_object_or_None
#...
user = get_object_or_None(Content, name="baby")
mknecht
sumber
Saya memeriksa kode get_object_or_Nonetetapi ternyata masih naik MultipleObjectsReturnedjika lebih dari satu objek. Jadi, pengguna mungkin mempertimbangkan untuk mengelilinginya dengan try-except(yang fungsinya sendiri try-exceptsudah ada).
John Pang
4

Jika Anda menginginkan solusi satu baris sederhana yang tidak melibatkan penanganan pengecualian, pernyataan bersyarat atau persyaratan Django 1.6+, lakukan ini sebagai gantinya:

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

Saya pikir itu bukan ide yang buruk untuk digunakan 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)

Contoh ini setara dengan:

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.")

Anda dapat membaca lebih lanjut tentang get_object_or_404 () di dokumentasi online Django.

Mohammad Reza
sumber
2

Dari django 1.7 dan selanjutnya Anda dapat melakukan seperti:

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):
    ...

Keuntungan dari "MyQuerySet.as_manager ()" adalah bahwa kedua hal berikut ini akan berfungsi:

MyModel.objects.filter(...).get_or_none()
MyModel.objects.get_or_none()
Vinayak Kaniyarakkal
sumber
1

Berikut adalah variasi pada fungsi helper yang memungkinkan Anda untuk secara opsional mengirimkan QuerySetcontoh, jika Anda ingin mendapatkan objek unik (jika ada) dari queryset selain model allobjek queryset (misalnya dari subset item anak yang dimiliki oleh sebuah instance induk):

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

Ini dapat digunakan dalam dua cara, misalnya:

  1. obj = get_unique_or_none(Model, **kwargs) seperti yang telah dibahas sebelumnya
  2. obj = get_unique_or_none(Model, parent.children, **kwargs)
Gary
sumber
1

Tanpa pengecualian:

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

Menggunakan pengecualian:

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

Ada sedikit argumen tentang kapan seseorang harus menggunakan pengecualian dalam python. Di satu sisi, "lebih mudah untuk meminta maaf daripada izin". Sementara saya setuju dengan ini, saya percaya bahwa pengecualian harus tetap, well, pengecualian, dan "kasus ideal" harus berjalan tanpa memukul satu pun.

Konstantin Schubert
sumber
1

Kita dapat menggunakan pengecualian builtin Django yang melekat pada model bernama .DoesNotExist. Jadi, kita tidak perlu mengimpor ObjectDoesNotExistpengecualian.

Alih-alih melakukan:

from django.core.exceptions import ObjectDoesNotExist

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

Kita bisa melakukan ini:

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

Ini adalah peniru dari get_object_or_404 Django kecuali bahwa metode mengembalikan Tidak ada. Ini sangat berguna ketika kita harus menggunakan only()kueri untuk hanya mengambil bidang tertentu. Metode ini dapat menerima model atau queryset.

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
Sandeep Balagopal
sumber
0

Mungkin lebih baik Anda gunakan:

User.objects.filter(username=admin_username).exists()
Jonathan Souza
sumber