Django: รับวัตถุจากฐานข้อมูลหรือ 'ไม่มี' หากไม่มีสิ่งใดที่ตรงกัน


101

มีฟังก์ชั่น Django ที่จะให้ฉันได้รับวัตถุจากฐานข้อมูลหรือไม่ถ้าไม่มีอะไรที่ตรงกัน

ตอนนี้ฉันกำลังใช้สิ่งที่ชอบ:

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

แต่มันไม่ชัดเจนนักและมันก็ยุ่งเหยิงที่จะมีทุกที่


2
คุณรู้ว่าคุณสามารถใช้ foo = foo [0] ได้ถ้า foo else None
Visgean Skeloru

2
Python มีตัวดำเนินการ ternary คุณไม่จำเป็นต้องใช้ตัวดำเนินการบูลีน นอกจากนี้ยังlen(foo)ไม่ดี : " หมายเหตุ: อย่าใช้ len () ใน QuerySets ถ้าสิ่งที่คุณต้องการทำคือกำหนดจำนวนระเบียนในชุดการจัดการการนับในระดับฐานข้อมูลจะมีประสิทธิภาพมากกว่ามากโดยใช้ 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 ส่งคืนอ็อบเจ็กต์แรกที่จับคู่โดย queryset หรือ None ถ้าไม่มีอ็อบเจ็กต์ที่ตรงกัน

การใช้งาน:

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

19
นี้ไม่ได้เป็นคำตอบที่ดีถ้าวัตถุหลายที่พบแล้วMultipleObjectsReturned()จะไม่ยกเป็นเอกสารที่นี่ คุณสามารถหาคำตอบที่ดีกว่าได้ที่นี่
sleepycal

25
@sleepycal ฉันไม่เห็นด้วย first()ทำงานตรงตามที่คิดว่าจะ ส่งคืนวัตถุแรกในผลลัพธ์การค้นหาและไม่สนใจหากพบผลลัพธ์หลายรายการ หากคุณต้องการตรวจสอบวัตถุหลายรายการที่ส่งคืนคุณควรใช้.get()แทน
Cesar Canassa

7
[แก้ไข] ตามที่ OP กล่าวว่า.get()ไม่เหมาะกับความต้องการของเขา คำตอบที่ถูกต้องสำหรับคำถามนี้มีอยู่ด้านล่างโดย @kaapstorm และเป็นคำตอบที่เหมาะสมกว่าอย่างชัดเจน การใช้filter()วิธีนี้ในทางที่ผิดอาจนำไปสู่ผลลัพธ์ที่ไม่คาดคิดบางอย่างที่ OP อาจไม่ได้ตระหนักถึง (เว้นแต่ฉันจะพลาดบางสิ่งที่นี่)
sleepycal

1
@sleepycal ผลลัพธ์ที่ไม่ได้ตั้งใจเกิดจากแนวทางนี้คืออะไร?
HorseloverFat

12
หากข้อ จำกัด ฐานข้อมูลของคุณไม่สามารถจัดการความเป็นเอกลักษณ์ของวัตถุต่างๆได้อย่างถูกต้องคุณสามารถตีขอบกรณีที่ตรรกะของคุณคาดว่าจะมีเพียงวัตถุเดียว (ซึ่งถูกเลือกโดยตัวแรก ()) แต่ในความเป็นจริงมีหลายวัตถุ ใช้รับ () แทน () 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) และนั่นเป็นส่วนหลักของภาษา ฉันไม่ใช่แฟนตัวยงของฟังก์ชัน try / except แต่ดูเหมือนว่าจะถือว่าเป็น Pythonic
Matt Luongo

4
@pcv: ไม่มีภูมิปัญญาดั้งเดิมที่เชื่อถือได้เกี่ยวกับภาษา "ส่วนใหญ่" ฉันคิดว่าคุณคิดเกี่ยวกับภาษาเฉพาะบางภาษาที่คุณใช้ แต่ระวังด้วยตัวระบุจำนวนดังกล่าว ข้อยกเว้นอาจช้า (หรือ "เร็วพอ" สำหรับเรื่องนั้น) เช่นเดียวกับสิ่งใด ๆ ใน Python กลับไปที่คำถาม - มันเป็นข้อบกพร่องของ framework ที่พวกเขาไม่ได้ให้querysetวิธีการใด ๆ ในการทำสิ่งนี้มาก่อน 1.6! แต่สำหรับโครงสร้างนี้ - มันเป็นเพียงเงอะงะ ไม่ใช่เรื่อง "ชั่วร้าย" เพราะผู้เขียนบทแนะนำภาษาที่คุณชื่นชอบบางคนพูดเช่นนั้น ลองใช้ Google: "ขอการอภัยมากกว่าการอนุญาต"
Tomasz Gandor

@TomaszGandor: ใช่มันดูเงอะงะและอ่านยากไม่ว่าบทแนะนำภาษาโปรดของคุณจะพูดอะไร เมื่อฉันเห็นข้อยกเว้นฉันคิดว่ามีบางอย่างที่ไม่ได้มาตรฐานเกิดขึ้น จำนวนการอ่าน แต่ฉันเห็นด้วยเมื่อไม่มีทางเลือกอื่นที่สมเหตุสมผลคุณควรไปหามันและจะไม่มีอะไรเลวร้ายเกิดขึ้น
pcv

@pcv getวิธีนี้ไม่ควรส่งคืนNoneดังนั้นข้อยกเว้นจึงสมเหตุสมผล คุณควรจะใช้ในกรณีที่คุณแน่ใจว่าวัตถุนั้นจะอยู่ที่นั่น / วัตถุนั้น "ควร" อยู่ที่นั่น นอกจากนี้การใช้ข้อยกเว้นเช่นนี้ถือว่า 'โอเค' เพราะมันสมเหตุสมผลดี คุณต้องการgetสัญญาที่แตกต่างออกไปดังนั้นคุณจึงจับข้อยกเว้นและระงับข้อยกเว้นซึ่งเป็นการเปลี่ยนแปลงที่คุณต้องการ
Dan Passaro

83

หากต้องการเพิ่มโค้ดตัวอย่างในคำตอบของ sorki (ฉันจะเพิ่มสิ่งนี้เป็นความคิดเห็น แต่นี่เป็นโพสต์แรกของฉันและฉันไม่มีชื่อเสียงมากพอที่จะแสดงความคิดเห็น) ฉันติดตั้งตัวจัดการแบบกำหนดเอง 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)

9

ให้ฟูผู้จัดการที่กำหนดเอง มันสวยง่าย - เพียงแค่ใส่รหัสของคุณลงในฟังก์ชั่นในการจัดการที่กำหนดเอง, 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

ดูเหมือนว่าจะจับประเด็นที่ดีที่สุดของคำตอบอื่น ๆ Nice one :)
Edward Newell

@emispowder จะเกิดอะไรขึ้นถ้าฉันไม่ต้องการส่งคืนไม่มีฉันต้องการส่งคืนข้อความแจ้งเตือนเช่น "ไม่มีข้อมูลที่ตรงกัน" ที่แสดงในหน้า SAME
Héléna

0

นี่คือรูปแบบของฟังก์ชันตัวช่วยที่ช่วยให้คุณสามารถเลือกส่งผ่านQuerySetอินสแตนซ์ได้ในกรณีที่คุณต้องการรับอ็อบเจ็กต์ที่ไม่ซ้ำกัน (ถ้ามี) จากชุดแบบสอบถามอื่นที่ไม่ใช่ชุดข้อมูลของโมเดลall(เช่นจากชุดย่อยของรายการย่อยที่เป็นของ a อินสแตนซ์หลัก):

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)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.