ฉันจะได้รับวัตถุถ้ามันมีอยู่หรือไม่มีถ้ามันไม่ได้อยู่?


223

เมื่อฉันขอให้ตัวจัดการโมเดลรับวัตถุมันจะเพิ่มขึ้นDoesNotExistเมื่อไม่มีวัตถุที่ตรงกัน

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

แทนที่จะเป็นDoesNotExistฉันgoจะNoneเป็นอย่างไร

คำตอบ:


332

ไม่มีวิธีการ 'ภายใน' ในการทำเช่นนี้ Django จะเพิ่มข้อยกเว้น DoesNotExist ทุกครั้ง วิธีที่ใช้สำนวนในการจัดการสิ่งนี้ในไพ ธ อนคือการห่อมันด้วยการลองจับ:

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

สิ่งที่ฉันทำคือ subclass models.Manager สร้างsafe_getโค้ดที่เหมือนด้านบนและใช้ตัวจัดการนั้นสำหรับโมเดลของฉัน SomeModel.objects.safe_get(foo='bar')วิธีการที่คุณสามารถเขียน:


7
ใช้ NiceModel.DoesNotExist แทนการอิมพอร์ตข้อยกเว้นเช่นกัน
supermitch

196
วิธีนี้มีความยาวสี่บรรทัด สำหรับฉันมันมากเกินไป ด้วย django 1.6 คุณสามารถใช้SomeModel.objects.filter(foo='bar').first()สิ่งนี้คืนค่าการจับคู่แรกหรือไม่มี มันจะไม่ล้มเหลวหากมีหลายกรณีเช่นqueryset.get()
guettli

9
ฉันคิดว่ามันเป็นรูปแบบที่ไม่ดีที่จะใช้ข้อยกเว้นมากเกินไปสำหรับการจัดการกรณีเริ่มต้น ใช่ "ง่ายกว่าที่จะขอการให้อภัยมากกว่าขออนุญาต" แต่ข้อยกเว้นควรจะยังคงใช้ในสายตาของฉันสำหรับข้อยกเว้น
Konstantin Schubert

8
ชัดเจนดีกว่าโดยปริยาย หากไม่มีเหตุผลด้านประสิทธิภาพในการใช้filter().first()ฉันคิดว่าข้อยกเว้นเป็นวิธีที่จะดำเนินการ
christianbundy

6
ใช้ครั้งแรก () เป็นความคิดที่ดีถ้าคุณไม่สนใจเมื่อมีหลายรายการ มิฉะนั้นโซลูชันนี้จะดีกว่าเพราะจะยังคงมีข้อยกเว้นหากคุณพบหลายวัตถุโดยไม่คาดคิดซึ่งโดยปกติคือสิ่งที่คุณต้องการให้เกิดขึ้นในกรณีนั้น
rossdavidh

182

ตั้งแต่ django 1.6 คุณสามารถใช้วิธีแรก ()ดังนี้:

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

26
ในกรณีนี้จะไม่มีข้อผิดพลาดเกิดขึ้นหากมีการแข่งขันมากกว่าหนึ่งรายการ
Konstantin Schubert

7
'FeroxTL' คุณต้องให้เครดิต @guettli สำหรับคำตอบนี้เนื่องจากเขาแสดงความคิดเห็นนี้กับคำตอบที่ได้รับการยอมรับหนึ่งปีก่อนโพสต์ของคุณ
colm.anseo

7
@colminator ฉันควรจะบอกว่า guettli ควรเรียนรู้ว่าคำตอบใหม่ไม่ได้เป็นความคิดเห็นหากเขาต้องการเพิ่ม reputaiton stackoverflow :) FeroxTL ควรได้รับคะแนนจากการทำสิ่งที่ซ่อนอยู่ในความคิดเห็นที่ชัดเจนยิ่งขึ้นเป็นคำตอบ ความคิดเห็นของคุณมีเครดิตเพียงพอสำหรับ guettli ฉันคิดว่าและไม่ควรเพิ่มคำตอบหากเป็นคำแนะนำของคุณ
Joakim

3
@ โจอาคิมฉันไม่มีปัญหากับการโพสต์ "คำตอบ" ใหม่ - เพียงแค่ให้เครดิตในกรณีที่เป็นเพราะ :-)
colm.anseo

3
ประสิทธิภาพของวิธีการนี้เทียบกับคำตอบที่ยอมรับแล้วหรือไม่
MaxCore

36

จาก django docs

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 นำเข้า ObjectDoesNotExist
Moti Radomski

13

คุณสามารถใช้existsกับตัวกรอง:

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

ทางเลือกสำหรับถ้าคุณเพียงต้องการทราบว่ามีอยู่


4
ที่จะทำให้การโทรฐานข้อมูลพิเศษเมื่อมีอยู่ ไม่ใช่ความคิดที่ดี
Christoffer

@Christoffer ไม่แน่ใจว่าทำไมจะเป็นสาย db พิเศษ ตามเอกสาร :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 ฉันคิดว่าคุณพูดถูก ตอนนี้ฉันอ่านคำถามอีกครั้งและ OP ต้องการให้ส่งคืนวัตถุจริง ดังนั้นexists()จะใช้ร่วมกับifข้อก่อนดึงวัตถุจึงก่อให้เกิดการตีสองครั้งเพื่อ db ฉันจะยังคงแสดงความคิดเห็นในกรณีที่มันช่วยคนอื่น
Anupam

7

การจัดการข้อยกเว้นที่จุดต่าง ๆ ในมุมมองของคุณอาจยุ่งยากจริง ๆ แล้วเรื่องการกำหนด Model Manager แบบกำหนดเองในไฟล์ models.py เช่น

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

และรวมไว้ในคลาสโมเดลเนื้อหา

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อยู่แล้ว)
John Pang

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

มีข้อโต้แย้งเล็กน้อยเกี่ยวกับเมื่อหนึ่งควรใช้ข้อยกเว้นในหลาม ในอีกด้านหนึ่ง "การขอการอภัยได้ง่ายกว่าการขออนุญาต" ในขณะที่ฉันเห็นด้วยกับสิ่งนี้ฉันเชื่อว่าข้อยกเว้นควรจะยังคงอยู่ข้อยกเว้นและ "กรณีอุดมคติ" ควรรันโดยไม่ต้องกดปุ่มใดปุ่มหนึ่ง


1

เราสามารถใช้ Django builtin .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

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