如果没有autocomplete_fields,我可以使用formfield_for_foreignkey来更改ForeignKeyField的查询集。
但是将两者结合在一起就行不通 - 搜索框中的选项列表似乎是动态的,并从不同的URL而非当前表单中获取。
问题是 -
如何在自动完成小部件中更改查询集?
ManyToManyField
使用autocomplete_fields
,则此示例将排除当前对象。get_form
来获取当前对象的ID:field_for_autocomplete = None
def get_form(self, request, obj=None, **kwargs):
if obj:
self.field_for_autocomplete = obj.pk
return super(MyAdmin, self).get_form(request, obj, **kwargs)
get_search_results
方法。仅修改您模型的自动完成 URI 的查询集:def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
# Exclude only for autocomplete
if request.path == '/admin/myapp/mymodel/autocomplete/':
queryset = queryset.exclude(field=self.field_for_autocomplete)
return queryset, use_distinct
覆盖ModelAdmin的get_search_results
方法以使用您想要的查询。您可以在为自动完成字段提供数据的视图的get_queryset
方法中看到它用于获取查询集-答案的源代码是https://github.com/django/django/blob/03dbdfd9bbbbd0b0172aad648c6bbe3f39541137/django/contrib/admin/views/autocomplete.py#L42。
get_search_results
的 request
参数来实现。它应该有你正在编辑的对象的 PK,所以你可以排除它。而且它会有 URL,所以如果你只想让自动完成限制到该管理视图中的顶级实例,那么你可以这样做。如果你想要一个带有这些约束的自动完成字段和另一个不带这些约束的自动完成字段,那么这就比较困难了 - 你可能需要设置一个子类的 JSON 视图来处理它。不过将其连接到管理员可能会很困难。 - Peter DeGlopper简短回答:
您可以尝试使用django-admin-autocomlete-all解决问题,或者创建类似的解决方案。
详细回答:
一个困扰是:源外键的limit_choices_to还未实现 :(
我能够在目标ModelAdmin的get_search_results()中实现过滤器。
但这里有另一个严重的问题。
我们可以检查request.is_ajax and '/autocomplete/' in request.path
。
此外,我们只有request.headers['Referer']。通过这个,我们可以将影响的外键限制为1个模型。 但如果我们有2个或更多的外键进入相同的目标(比如:同一模型实例中的两个用户角色),我们不知道其中哪一个调用了ajax。
我的想法是修改url。对于Request url我没有成功(在DOM和js中长时间尝试查找select2元素并扩展url之后)。
但是我通过修改Referer url(即源管理页面url)使用window.history.replaceState()取得了一些成功。我可以暂时修改url,如/?key=author
- 如果您使用django-admin-autocomplete-all,它将始终运行,我可以使用其他自定义javascript将几乎所有内容添加到Referer url中。特别是添加其他表单字段的当前值可能有助于实现动态过滤(字段依赖关系)。
所以,这是一个hack,当然。但您可以尝试使用django-admin-autocomplete-all。 - 更多信息请查看文档。
我曾经遇到类似的问题,当使用autocomplete_fields时,limit_choices_to没有生效,后来我找到了一种解决方法,希望对其他人也有所帮助。 这是我的一种解决方案,每个人都应该根据自己的需求更改代码。
假设我们有两个模型model_A和model_B:
我们要重写model_A的管理界面中的"get_search_results"方法
(因为model_B有一个外键或m2m关系指向它)
在我的情况下,我只想将选择限制为所有当前未连接model_B对象的model_A对象,
或者在更新model_B对象时,仅限于以前的model_A对象,
所以我们采取以下措施:
# moodels.py
class model_A(models.Model):
name = models.CharField()
class model_B(models.Model):
name = models.CharField()
fk_field = models.OneToOneField( #ManyToManyField or ForeignKey
model_A,
related_name='fk_reverse',
on_delete=models.CASCADE)
# admin.py
class model_A_Admin(admin.ModelAdmin):
search_fields = ('name', )
def get_search_results(self, request, queryset, search_term):
import re
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
# note: str(request.META.get('HTTP_REFERER')) is the url from which the request had come/previous url.
if "model_b/add/" in str(request.META.get('HTTP_REFERER')):
# if we were in creating new model_B instanse page
# note: the url is somehow containing model_Bs calss name then / then "add"
# so there is no related object(of model_A) for non exsisting object(of model_B)
queryset = self.model.objects.filter(fk_reverse=None)
elif re.search(r"model_b/\d/change/", str(request.META.get('HTTP_REFERER'))):
# if we were in updatineg page of an exsisting model_B instanse
# the calling page url contains the id of the model_B instanse
# we are extracting the id and use it for limitaion proccess
pk = int(re.findall(r'\d+', str(str(request.META.get('HTTP_REFERER')).split('/')[-3: ]))[-1])
queryset = self.model.objects.filter(fk_reverse=pk)
return queryset, use_distinct
您可以通过覆盖 get_search_results 方法来修改自动完成的查询集,如下所示:
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
if 'autocomplete' in request.path:
queryset = queryset.exclude(field_name='foo')
return queryset, use_distinct
这个函数在 admin.py 中,需要与你所引用的模型一起使用。如果自动完成中使用的模型是 Bar,则 get_search_results 应该在类 BarAdmin(admin.ModelAdmin) 中。
此外,如果您在多个自动完成字段中使用相同的模型,可以根据调用位置更改 queryset。例如:
@admin.register(Foo)
class FooAdmin(admin.ModelAdmin):
fields = (
'the_field',
)
autocomplete_fields = ('the_field',)
@admin.register(Bar)
class BarAdmin(admin.ModelAdmin):
fields = (
'the_same_field',
)
autocomplete_fields = ('the_same_field',)
@admin.register(Key)
class KeyAdmin(admin.ModelAdmin):
fields = (
'name',
)
ordering = [
'-id',
]
search_fields = [
'name',
]
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(request, queryset, search_term)
if 'autocomplete' in request.path:
if 'foo' in request.headers['referer']:
queryset = queryset.exclude(name='foo')
elif 'bar' in request.headers['referer']:
queryset = queryset.exclude(name='bar')
return queryset, use_distinct
limit_choices_to
参数,可以另一种方式来过滤自动完成字段的选项。
class Location(models.Model):
...
cities = models.ManyToManyField(City, limit_choices_to=Q(region="Europe"))
...
@admin.register(Location)
class LocationAdmin(admin.ModelAdmin):
...
autocomplete_fields = ["cities"]
request.path==
可以在管理页面源代码中找到,搜索:data-ajax--url
。我没有使用/admin/作为我的管理员URL路径。所以一开始它没有起作用。 - C.K.