Django: Dapatkan sebuah objek dari DB, atau 'Tidak Ada' jika tidak ada yang cocok

101

Apakah ada fungsi Django yang akan membiarkan saya mendapatkan objek dari database, atau None jika tidak ada yang cocok?

Saat ini saya menggunakan sesuatu seperti:

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

Tapi itu tidak terlalu jelas, dan berantakan untuk dimiliki di mana-mana.

David Wolever
sumber
2
Anda tahu Anda bisa menggunakan foo = foo [0] if foo else Tidak ada
Visgean Skeloru
2
Python memiliki operator terner, Anda tidak perlu menggunakan operator boolean. Juga, len(foo)buruk : " Catatan: Jangan gunakan len () di QuerySets jika semua yang ingin Anda lakukan adalah menentukan jumlah record dalam set. Jauh lebih efisien untuk menangani hitungan di tingkat database, menggunakan SQL SELECT COUNT (), dan Django menyediakan metode count () tepat untuk alasan ini. ". Ditulis ulang:foo = foo[0] if foo.exists() else None
mustafa.0x
1
@ mustafa.0x Saya tidak berpikir itu berlaku di sini. Di sini, memeriksa hitungan akan menjalankan kueri lain. Kemudian kami akan meminta lagi untuk mendapatkan data yang sebenarnya. Ini adalah kasus di mana memfilter dan kemudian menghitung akan lebih masuk akal ... tetapi tidak lebih masuk akal daripada memfilter dan memanggil first(): P
MicronXD

Jawaban:

88

Dalam Django 1.6 Anda dapat menggunakan first()metode Queryset. Ini mengembalikan objek pertama yang cocok dengan queryset, atau None jika tidak ada objek yang cocok.

Pemakaian:

p = Article.objects.order_by('title', 'pub_date').first()
Cesar Canassa
sumber
19
Ini bukan jawaban yang baik, jika beberapa objek ditemukan maka MultipleObjectsReturned()tidak dimunculkan, seperti yang didokumentasikan di sini . Anda dapat menemukan jawaban yang lebih baik di sini
sleepycal
25
@ sleepycu, aku tidak setuju. The first()karya persis seperti rasa untuk. Ini mengembalikan objek pertama dalam hasil kueri dan tidak peduli jika beberapa hasil ditemukan. Jika Anda perlu memeriksa beberapa objek yang dikembalikan, Anda harus menggunakan .get()sebagai gantinya.
Cesar Canassa
7
[sunting] Seperti kata OP, .get()tidak sesuai dengan kebutuhannya. Jawaban yang benar untuk pertanyaan ini dapat ditemukan di bawah oleh @kaapstorm, dan jelas merupakan jawaban yang lebih cocok. Menyalahgunakan filter()cara ini dapat menyebabkan konsekuensi yang tidak terduga, sesuatu yang mungkin tidak disadari oleh OP (kecuali saya melewatkan sesuatu di sini)
sleepycal
1
@sleepycal Apa konsekuensi yang tidak diinginkan yang timbul dari pendekatan ini?
HorseloverFat
12
Jika batasan database Anda tidak mengelola keunikan dengan benar di seluruh objek, maka Anda bisa menghadapi kasus edge di mana logika Anda mengharapkan hanya ada satu objek (yang dipilih oleh first ()), tetapi pada kenyataannya ada beberapa objek. Menggunakan get () daripada first () memberi Anda lapisan perlindungan ekstra, dengan menaikkan MultipleObjectsReturned(). Jika hasil yang dikembalikan tidak diharapkan mengembalikan banyak objek, maka itu tidak boleh diperlakukan seperti itu. Ada perdebatan panjang tentang ini di sini
sleepycal
139

Ada dua cara untuk melakukan ini;

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

Atau Anda bisa menggunakan pembungkus:

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

Sebut saja seperti ini

foo = get_or_none(Foo, baz=bar)
Nick Craig-Wood
sumber
8
Saya tidak suka fakta bahwa ini menggunakan pengecualian untuk fungsionalitas - ini adalah praktik yang buruk di sebagian besar bahasa. Bagaimana dengan python?
pcv
18
Begitulah cara kerja iterasi Python (memunculkan StopIteration), dan itu adalah bagian inti dari bahasa tersebut. Saya bukan penggemar berat mencoba / kecuali fungsionalitas, tetapi tampaknya dianggap Pythonic.
Matt Luongo
4
@pcv: tidak ada kebijaksanaan konvensional yang dapat diandalkan tentang bahasa "kebanyakan". Saya kira Anda memikirkan beberapa bahasa tertentu yang kebetulan Anda gunakan, tetapi berhati-hatilah dengan bilangan seperti itu. Pengecualian bisa jadi lambat (atau "cukup cepat" dalam hal ini), seperti apa pun di Python. Kembali ke pertanyaan - ini adalah kekurangan dari framework, bahwa mereka tidak menyediakan querysetmetode apa pun untuk melakukan ini sebelum 1.6! Tetapi untuk konstruksi ini - itu hanya kikuk. Ini tidak "jahat" karena beberapa penulis tutorial bahasa favorit Anda mengatakannya. Coba google: "minta maaf daripada izin".
Tomasz Gandor
@TomaszGandor: Ya, ini kikuk dan sulit untuk dibaca, apapun yang dikatakan tutorial bahasa favorit Anda. Ketika saya melihat pengecualian, saya kira sesuatu yang tidak standar sedang terjadi. Keterbacaan itu penting. Tetapi saya setuju ketika tidak ada pilihan lain yang masuk akal, Anda harus melakukannya dan tidak ada hal buruk yang akan terjadi.
pcv
@pcv getMetode ini tidak seharusnya kembali None, jadi pengecualian masuk akal. Anda seharusnya menggunakannya jika Anda yakin objek akan ada di sana / objek "seharusnya" ada di sana. Juga menggunakan pengecualian seperti ini dianggap 'oke' karena, yah, itu masuk akal. Anda menginginkan getdengan kontrak yang berbeda, sehingga Anda menangkap pengecualian dan menahannya, yang merupakan perubahan yang Anda cari.
Dan Passaro
83

Untuk menambahkan beberapa kode contoh ke jawaban sorki (saya akan menambahkan ini sebagai komentar, tetapi ini adalah posting pertama saya, dan saya tidak memiliki cukup reputasi untuk meninggalkan komentar), saya menerapkan manajer kustom get_or_none seperti ini:

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()

Dan sekarang saya bisa melakukan ini:

bob_or_none = Person.objects.get_or_none(name='Bob')
kaapstorm.dll
sumber
Ini sangat elegan.
NotSimon
13

Anda juga dapat mencoba menggunakan django mengganggu (ini memiliki fungsi lain yang berguna!)

instal dengan:

pip install django-annoying

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

Berikan Foo manajer kustomnya . Ini cukup mudah - cukup letakkan kode Anda ke dalam fungsi di pengelola khusus, setel pengelola khusus dalam model Anda dan panggil dengan Foo.objects.your_new_func(...).

Jika Anda memerlukan fungsi generik (untuk menggunakannya pada model apa pun tidak hanya dengan custom manager) tulis sendiri dan letakkan di suatu tempat di jalur python Anda dan impor, tidak berantakan lagi.

sorki
sumber
4

Baik melakukannya melalui manajer atau fungsi generik, Anda mungkin juga ingin menangkap 'MultipleObjectsReturned' dalam pernyataan TRY, karena fungsi get () akan menaikkannya jika kwarg Anda mengambil lebih dari satu objek.

Membangun dari fungsi generik:

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

dan di manajer:

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
bubuk mesiu
sumber
Ini sepertinya menangkap semua poin terbaik dari jawaban lainnya. Bagus :)
Edward Newell
@emispowder, bagaimana jika saya tidak ingin mengembalikan Tidak ada, saya ingin mengembalikan pesan peringatan seperti "tidak ada data yang cocok" yang ditampilkan di laman YANG SAMA?
Héléna
0

Berikut adalah variasi pada fungsi helper yang memungkinkan Anda untuk meneruskan sebuah QuerySetinstance secara opsional , jika Anda ingin mendapatkan objek unik (jika ada) dari queryset selain allqueryset objek model (misalnya dari subset item anak milik a contoh induk):

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

Ini dapat digunakan dengan dua cara, misalnya:

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

Saya pikir dalam banyak kasus Anda hanya dapat menggunakan:

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

Hanya jika tidak penting bahwa entri baru akan ditambahkan dalam tabel Foo (kolom lain akan memiliki nilai Tidak Ada / default)

TomerP
sumber