如何过滤 Django2 的 autocomplete_fields 中的选项?

How to filter choices in Django2's autocomplete_fields?

在 Django 2.0 中,添加了 autocomplete_fields,非常棒。

没有 autocomplete_fields,我可以使用 formfield_for_foreignkey 更改 ForeignKeyField 的查询集。

但是将两者组合在一起不起作用 - 自动完成选项列表似乎是动态的,并且来自不同的 url,而不是当前的形式。

所以问题是 -

如何更改自动完成小部件中的查询集?

覆盖 ModelAdmin 的 get_search_results method to use the query you want. You can see in the get_queryset method for the view providing the data for autocomplete fields that it's used to get the queryset - the source as of this answer is https://github.com/django/django/blob/03dbdfd9bbbbd0b0172aad648c6bbe3f39541137/django/contrib/admin/views/autocomplete.py#L42

如果您在 'self' 上使用 autocomplete_fields 作为 ManyToManyField, 此示例将排除当前对象。

通过重写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

简而言之:您可以在 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)。

但我在使用 window.history.replaceState() 修改 Referer url(即源管理页面 url)方面取得了一些成功。我可以临时修改 url 像 /?key=author - 如果你使用 django-admin-autocomplete-all 并且我能够将几乎所有内容添加到 Referer url 以及额外的自定义 javascript。特别是添加其他表单字段的当前值可能有助于实现动态过滤(字段的依赖性)。

所以,这是一个 hack,当然。但是你可以试试 django-admin-autocomplete-all。 - 更多内容在它的文档中。

我遇到了同样的问题,当使用 autocomplete_fields 时 limit_choices_to 没有生效,然后我找到了适合我的案例的解决方案,这也可能对其他人有所帮助。 这是针对我的案例的想法和解决方案,任何人都应该更改代码以供 his/her 使用。

假设我们有两个模型 model_A 和 modle_B: 我们将覆盖
的“get_search_results” model_A 的模型管理员(因为 model_B 有一个 foreign_key(或 m2m) 在我的例子中,我只想将选择限制为所有 model_A 个对象
目前没有 model_B 个连接的对象 或者在更新 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