Django2的autocomplete_fields如何过滤选项?

30
在Django 2.0中,添加了autocomplete_fields,非常好用。
如果没有autocomplete_fields,我可以使用formfield_for_foreignkey来更改ForeignKeyField的查询集。
但是将两者结合在一起就行不通 - 搜索框中的选项列表似乎是动态的,并从不同的URL而非当前表单中获取。
问题是 -
如何在自动完成小部件中更改查询集?
6个回答

11
如果您正在为“self”上的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

1
我使用了你的答案的一部分,它有效了,非常感谢。顺便说一下,request.path==可以在管理页面源代码中找到,搜索:data-ajax--url。我没有使用/admin/作为我的管理员URL路径。所以一开始它没有起作用。 - C.K.
你让我开心 - SlyDeath

8

2
请纠正我,当覆盖get_search_results时,每个ForeignKey到该模型都会受到影响?在我的情况下,我有一个FK父项指向“self”,我想防止选择某个模型作为其自己的父项,并且仅允许顶级模型(parent=None)作为父项。同时,其他模型应该能够将每个条目分配为FK。 - kelvan
1
我还没有深入研究过这个问题,但是你可以通过检查 get_search_resultsrequest 参数来实现。它应该有你正在编辑的对象的 PK,所以你可以排除它。而且它会有 URL,所以如果你只想让自动完成限制到该管理视图中的顶级实例,那么你可以这样做。如果你想要一个带有这些约束的自动完成字段和另一个不带这些约束的自动完成字段,那么这就比较困难了 - 你可能需要设置一个子类的 JSON 视图来处理它。不过将其连接到管理员可能会很困难。 - Peter DeGlopper

3

简短回答:

您可以尝试使用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。 - 更多信息请查看文档。


3

我曾经遇到类似的问题,当使用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

1

您可以通过覆盖 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

我们有Foo和Bar模型,它们都有外键指向Key Model,并且与自动完成一起使用。 现在,当我们打开自动完成并更改Foo时,我们只会看到名称等于“foo”的查询集,同样地,当我们在Bar中打开自动完成时,我们只会看到名称等于“bar”的查询集。这样,您可以根据自动完成的调用位置修改查询集。

0
通过模型中关系字段的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"]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接