Django: Lấy một đối tượng ở dạng DB hoặc 'Không có' nếu không có gì phù hợp


101

Có bất kỳ hàm Django nào cho phép tôi lấy một đối tượng trong cơ sở dữ liệu hay không nếu không có gì phù hợp?

Ngay bây giờ tôi đang sử dụng một cái gì đó như:

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

Nhưng điều đó không rõ ràng lắm, và thật lộn xộn khi có ở khắp mọi nơi.


2
Bạn biết bạn chỉ có thể sử dụng foo foo = [0] nếu foo khác Không
Visgean Skeloru

2
Python có toán tử bậc ba, bạn không cần phải sử dụng toán tử boolean. Ngoài ra, len(foo)không tốt : " Lưu ý: Không sử dụng len () trên QuerySets nếu tất cả những gì bạn muốn làm là xác định số lượng bản ghi trong tập hợp. Sẽ hiệu quả hơn nhiều khi xử lý số lượng ở cấp cơ sở dữ liệu, bằng cách sử dụng SELECT COUNT (), và Django cung cấp một phương thức count () vì lý do này. ". Đã viết lại:foo = foo[0] if foo.exists() else None
mustafa.0x


1
@ mustafa.0x Tôi không nghĩ điều đó áp dụng ở đây. Tại đây, việc kiểm tra số lượng sẽ chạy một truy vấn khác. Sau đó, chúng tôi sẽ truy vấn lại để lấy dữ liệu thực tế. Đây là một trường hợp lọc và sau đó đếm sẽ có ý nghĩa hơn ... nhưng cảm giác không quá lọc và gọi first(): P
MicronXD

Câu trả lời:


88

Trong Django 1.6, bạn có thể sử dụng first()phương thức Queryset. Nó trả về đối tượng đầu tiên được đối sánh bởi bộ truy vấn hoặc Không có nếu không có đối tượng phù hợp.

Sử dụng:

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

19
Đây không phải là một câu trả lời hay, nếu nhiều đối tượng được tìm thấy thì MultipleObjectsReturned()sẽ không được nêu ra, như được ghi lại ở đây . Bạn có thể tìm thấy một số câu trả lời tốt hơn ở đây
sleepycal

25
@sleepycal Tôi không đồng ý. Các first()hoạt động chính xác như nó giả định. Nó trả về đối tượng đầu tiên trong kết quả truy vấn và không quan tâm nếu nhiều kết quả được tìm thấy. Nếu bạn cần kiểm tra nhiều đối tượng được trả lại, bạn nên sử dụng .get()thay thế.
Cesar Canassa

7
[sửa] Như OP đã nói, .get()không phù hợp với nhu cầu của anh ấy. Câu trả lời chính xác cho câu hỏi này có thể được tìm thấy dưới đây bởi @kaapstorm và rõ ràng là câu trả lời phù hợp hơn. Lạm dụng filter()cách này có thể dẫn đến những hậu quả không mong đợi, điều mà OP có lẽ không nhận ra (trừ khi tôi thiếu thứ gì đó ở đây)
sleepycal

1
@sleepycal Hậu quả không mong muốn nào phát sinh từ cách làm này?
HorseloverFat

12
Nếu các ràng buộc cơ sở dữ liệu của bạn không quản lý chính xác tính duy nhất giữa các đối tượng, thì bạn có thể gặp phải các trường hợp cạnh mà logic của bạn mong đợi chỉ có một đối tượng duy nhất (được chọn bởi đầu tiên ()), nhưng trong thực tế tồn tại nhiều đối tượng. Sử dụng get () thay vì first () cung cấp cho bạn một lớp bảo vệ bổ sung, bằng cách nâng cao MultipleObjectsReturned(). Nếu kết quả được trả về không mong muốn trả về nhiều đối tượng, thì nó không nên được xử lý như vậy. Đã có một cuộc tranh luận dài về điều này ở đây
sleepycal

139

Có hai cách để làm điều này;

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

Hoặc bạn có thể sử dụng một trình bao bọc:

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

Gọi nó như thế này

foo = get_or_none(Foo, baz=bar)

8
Tôi không thích thực tế là nó sử dụng các ngoại lệ cho chức năng - đây là một thực tế không tốt trong hầu hết các ngôn ngữ. Làm thế nào đó là trong python?
pcv

18
Đó là cách hoạt động của phép lặp Python (tăng StopIteration) và đó là một phần cốt lõi của ngôn ngữ. Tôi cũng không phải là một fan hâm mộ lớn của chức năng thử / ngoại trừ, nhưng nó dường như được coi là Pythonic.
Matt Luongo

4
@pcv: không có sự hiểu biết thông thường đáng tin cậy về các ngôn ngữ "hầu hết". Tôi cho rằng bạn nghĩ về một số ngôn ngữ cụ thể mà bạn tình cờ sử dụng, nhưng hãy cẩn thận với những định lượng như vậy. Các ngoại lệ có thể chậm (hoặc "đủ nhanh" cho vấn đề đó), như bất kỳ thứ gì trong Python. Quay trở lại câu hỏi - đó là một thiếu sót của khuôn khổ, đó là họ đã không cung cấp bất kỳ querysetphương pháp nào để thực hiện điều này trước 1.6! Nhưng đối với cấu trúc này - nó chỉ là vụng về. Nó không phải là "xấu xa" bởi vì một số tác giả hướng dẫn của ngôn ngữ yêu thích của bạn đã nói như vậy. Hãy thử google: "xin sự tha thứ hơn là xin phép".
Tomasz Gandor,

@TomaszGandor: Vâng, nó vụng về và khó đọc, bất kể hướng dẫn ngôn ngữ yêu thích của bạn nói gì. Khi tôi thấy một ngoại lệ, tôi cho rằng điều gì đó không chuẩn đang xảy ra. Số lượng khả năng đọc. Nhưng tôi đồng ý khi không có lựa chọn hợp lý nào khác, bạn nên đi và sẽ không có gì xấu xảy ra.
pcv

@pcv getPhương thức không phải trả về None, vì vậy một ngoại lệ có ý nghĩa. Bạn phải sử dụng nó trong các trường hợp mà bạn chắc chắn rằng đối tượng sẽ ở đó / đối tượng được "cho là" ở đó. Ngoài ra, việc sử dụng các ngoại lệ như thế này được coi là 'được' bởi vì, tốt, nó có ý nghĩa. Bạn muốn getcó một hợp đồng khác, vì vậy bạn nắm bắt ngoại lệ và loại bỏ nó, đó là thay đổi bạn đang theo đuổi.
Dan Passaro

83

Để thêm một số mã mẫu vào câu trả lời của sorki (tôi muốn thêm điều này làm nhận xét, nhưng đây là bài đăng đầu tiên của tôi và tôi không có đủ danh tiếng để để lại nhận xét), tôi đã triển khai trình quản lý tùy chỉnh get_or_none như vậy:

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

Và bây giờ tôi có thể làm điều này:

bob_or_none = Person.objects.get_or_none(name='Bob')

Điều này rất thanh lịch.
NotSimon

13

Bạn cũng có thể thử sử dụng django gây phiền nhiễu (nó có một chức năng hữu ích khác!)

cài đặt nó với:

pip install django-annoying

from annoying.functions import get_object_or_None
get_object_or_None(Foo, bar=baz)

9

Cung cấp cho Foo trình quản lý tùy chỉnh của nó . Nó khá dễ dàng - chỉ cần đặt mã của bạn vào chức năng trong trình quản lý tùy chỉnh, đặt trình quản lý tùy chỉnh trong mô hình của bạn và gọi nó bằng Foo.objects.your_new_func(...).

Nếu bạn cần hàm chung (để sử dụng nó trên bất kỳ mô hình nào, không chỉ với trình quản lý tùy chỉnh), hãy viết hàm của riêng bạn và đặt nó ở đâu đó trên đường dẫn python của bạn và nhập, không lộn xộn nữa.


4

Cho dù thực hiện nó thông qua trình quản lý hay hàm chung, bạn cũng có thể muốn bắt 'MultipleObjectsReturned' trong câu lệnh TRY, vì hàm get () sẽ nâng cao điều này nếu kwargs của bạn truy xuất nhiều hơn một đối tượng.

Xây dựng trên chức năng chung:

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

và trong trình quản lý:

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

Điều này dường như nắm bắt tất cả các điểm tốt nhất của các câu trả lời khác. Tuyệt vời :)
Edward Newell

@emispowder, điều gì sẽ xảy ra nếu tôi không muốn trả lại Không có, tôi muốn trả lại thông báo cảnh báo như "không có dữ liệu phù hợp" hiển thị trên trang CÙNG?
Héléna

0

Đây là một biến thể của hàm trợ giúp cho phép bạn tùy chọn chuyển trong một QuerySetphiên bản, trong trường hợp bạn muốn lấy đối tượng duy nhất (nếu có) từ một bộ truy vấn khác với bộ truy vấn allđối tượng của mô hình (ví dụ: từ một tập hợp con các mục con thuộc một cá thể mẹ):

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

Điều này có thể được sử dụng theo hai cách, ví dụ:

  1. obj = get_unique_or_none(Model, *args, **kwargs) như đã thảo luận trước
  2. obj = get_unique_or_none(Model, parent.children, *args, **kwargs)

-1

Tôi nghĩ rằng trong hầu hết các trường hợp, bạn chỉ có thể sử dụng:

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

Chỉ khi không quá quan trọng thì mục nhập mới sẽ được thêm vào bảng Foo (các cột khác sẽ có giá trị Không có / mặc định)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.