这与这个问题非常相似,但不幸的是,我仍然无法使它工作。
我有一个模型,其属性结合了几个字段:
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}"
在Django Admin中,当有人进行搜索时,我可以使用Model Admin中的search_fields
属性来指定实际字段,但不能使用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')
使用上面的代码进行搜索,它将搜索单个字段,但如果我试图在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
就我所知,我不能做一个queryset.filter(specimen_name=SOMETHING)
。.filter
不会将@property
方法识别为需要搜索的字段。这就是为什么我编写了自己的循环来进行搜索。
上面的代码显然不能工作。您不能只是向查询集添加列表。我如何返回一个实际的查询集?
对属性进行过滤的正确方法是为该属性创建一个等效的注释,并对其进行过滤。看看你的属性,它所做的就是连接一些字段,对应于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"
然后你可以改变你的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
注意:上面可能会停止给你结果,除非你设置search_fields
为空元组/列表。
继续这一行,也许用注释,你可以通过覆盖get_queryset
在search_fields
中拥有specimen_name
,因此跳过覆盖get_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
根据Abdul的回答,这帮助很大,我能够稍微修改它以得到我想要的:
def get_search_results(self, request, queryset, search_term):
# The results of the built-in search, based on search_fields
queryset_a, may_have_duplicates = super().get_search_results(request, queryset, search_term)
# Queryset B starts off equal to the original queryset with
# anotations
queryset_b = queryset.alias(
speci_name=Concat(
"lab_number",
Value("_"),
Replace("patient_name", Value(" "), Value(".")),
Value("_"),
Cast("alberta_health_number", CharField())
)
)
# Filter out queryset_b on every search term
for bit in smart_split(search_term):
if bit.startswith(('"', "'")) and bit[0] == bit[-1]:
bit = unescape_string_literal(bit)
queryset_b = queryset_b.filter(Q(speci_name__icontains=bit))
# Return both querysets
# Since we're doing 2 separate searches and combining them, it's
# not impossible for there to be duplicates, so we set
# may_have_duplicates return value to True, which will have Django
# filter out the duplicates
return (queryset_a | queryset_b), True
我对Abdul的代码有一个小问题,而不是使用search_fields
进行搜索,并将这些结果添加到基于这个新的自定义字段的搜索结果中,它结合了过滤器。
如果您基于完整的specimen_name
字段进行搜索,super()
将返回一个空查询集,并且在该点进一步过滤将返回另一个空查询集。
这里,我的代码通过调用super()
进行默认搜索,然后根据新的自定义字段进行搜索,并将结果添加在一起。
当你在Django Admin中进行搜索时,默认情况下,它会搜索任何与你的搜索条件匹配的字段。Abdul的代码使得搜索词必须匹配自定义字段和任何搜索字段。只匹配自定义字段的记录被忽略。我的代码修复了这个问题。
谢谢Abdul -我从你的代码中学到了很多。