如何使用自定义搜索字段(模型属性)在 Django Admin 中进行搜索?

Joh*_*ohn 6 python django

这与这个问题非常相似,但不幸的是,我仍然无法让它工作。

我有一个模型,其属性组合了几个字段:

class Specimen(models.Model):
    lab_number = ...
    patient_name = ...
    specimen_type = ...

    @property
    def specimen_name(self):
        return f"{self.lab_number}_{self.patient_name}_{self.specimen_type}"
Run Code Online (Sandbox Code Playgroud)

在 Django Admin 中,当有人进行搜索时,我可以使用search_fieldsModel Admin 中的属性来指定真实字段,但不能指定specimen_name自定义字段:


def specimen_name(inst):
    return inst.specimen_name
specimen_name.short_description = "Specimen Name"

class SpecimenModelAdmin(admin.ModelAdmin):
    list_display = ('specimen_name', 'patient_name', 'lab_number', 'specimen_type')
    search_fields = ('patient_name', 'lab_number', 'specimen_type')
Run Code Online (Sandbox Code Playgroud)

使用上面的代码进行搜索,它将搜索各个字段,但如果我尝试在 Django Admin 中搜索完整的标本名称,它不会找到它,因为没有一个字段包含准确的完整标本名称。

我上面链接的 SO 问题为我指明了正确的方向 - 使用get_search_results. 我的代码现在看起来像这样:

class SpecimenModelAdmin(admin.ModelAdmin):
    ...
    search_fields = ('patient_name', 'lab_number', 'specimen_type')

    def get_search_results(self, request, queryset, search_term):
        if not search_term:
            return queryset, False

        queryset, may_have_duplicates = super().get_search_results(
            request, queryset, search_term,
        )

        search_term_list = search_term.split(' ')
        specimen_names = [q.specimen_name for q in queryset.all()]
        results = []

        for term in search_term_list:
            for name in specimen_names:
                if term in name:
                    results.append(name)
                    break

        # Return original queryset, AND any new results we found by searching the specimen_name field
        # The True indicates that it's possible that we will end up with duplicates
        # I assume that means Django will make sure only unique results are returned when that's set
        return queryset + results, True
Run Code Online (Sandbox Code Playgroud)

据我所知,我做不到queryset.filter(specimen_name=SOMETHING).filter不会将该@property方法识别为需要搜索的字段。这就是为什么我编写自己的循环来进行搜索。

上面的代码显然是行不通的。您不能只将列表添加到查询集中。我将如何返回实际的查询集?

Abd*_*kat 6

过滤属性的正确方法是为该属性创建等效注释并对其进行过滤。查看您的属性,它所做的只是连接一些字段,对应于 Django 具有Concat数据库功能。因此,您可以进行以下注释:

from django.db.models import Value
from django.db.models.functions import Concat


queryset = queryset.annotate(
    specimen_name=Concat("lab_number", Value("_"), "patient_name", Value("_"), "specimen_type")
)
# Note: If you use Django version >=3.2 you can use "alias" instead of "annotate"
Run Code Online (Sandbox Code Playgroud)

然后你可以改变你的get_search_results如下:

from django.db.models import Value, Q
from django.db.models.functions import Concat
from django.utils.text import (
    smart_split, unescape_string_literal
)


class SpecimenModelAdmin(admin.ModelAdmin):
    ...
    search_fields = ('patient_name', 'lab_number', 'specimen_type')

    def get_search_results(self, request, queryset, search_term):
        queryset = queryset.annotate(
            specimen_name=Concat(
                "lab_number",
                Value("_"),
                "patient_name",
                Value("_"),
                "specimen_type"
            )
        )
        queryset, may_have_duplicates = super().get_search_results(request, queryset, search_term)
        for bit in smart_split(search_term):
            if bit.startswith(('"', "'")) and bit[0] == bit[-1]:
                bit = unescape_string_literal(bit)
             queryset = queryset.filter(Q(specimen_name__icontains=bit))
        return queryset, may_have_duplicates
Run Code Online (Sandbox Code Playgroud)

注意search_fields:除非您设置为空元组/列表,否则上面的内容可能会停止给您结果。

继续沿着这条线,也许可以通过覆盖specimen_name添加注释,从而跳过覆盖:search_fieldsget_querysetget_search_results

class SpecimenModelAdmin(admin.ModelAdmin):
    ...
    search_fields = ('patient_name', 'lab_number', 'specimen_type', 'specimen_name')
    
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        qs = qs.annotate(
            specimen_name=Concat(
                "lab_number",
                Value("_"),
                "patient_name",
                Value("_"),
                "specimen_type"
            )
        )
        return qs
Run Code Online (Sandbox Code Playgroud)