【问题标题】:How to use the admin autocomplete field in a custom form?Django,如何在自定义表单中使用管理自动完成字段
【发布时间】:2019-11-30 06:11:24
【问题描述】:

在 Django 管理界面中,您可以将字段设置为 autocomplete field,例如:

autocomplete_fields = ('countries', )

这对管理页面非常有用,如何在自定义视图/表单中使用自动完成字段?我的研究指向 django-autocomplete-light,但是当 Django 已经具有内置功能时,安装 3rd 方包似乎并不理想。

【问题讨论】:

  • 下面的答案是您要找的答案吗?还是您需要别的东西?
  • 由于距离答案有很长一段时间,我没有尝试过,我去了Django-AutoComplete-light。您的解决方案似乎不错,我希望在标记为正确答案之前先尝试一下,谢谢!

标签: django


【解决方案1】:

之前提出的 FakeRelation 解决方案不再适用于 Django 2.2,它需要 AutocompleteSelect 构造函数的字段实例。
我的项目中有一个合适的字段,所以我可以使用它,但必须传递一个字段实例而不是一个关系。这是自定义 AutocompleteSelect 的代码,它还添加了将特定占位符传递给 Select2 的选项:

class CustomAutocompleteSelect(AutocompleteSelect):
    def __init__(self, field, prompt="", admin_site=None, attrs=None, choices=(), using=None):
        self.prompt = prompt
        super().__init__(field, admin_site, attrs=attrs, choices=choices, using=using)

    def build_attrs(self, base_attrs, extra_attrs=None):
        attrs = super().build_attrs(base_attrs, extra_attrs=extra_attrs)
        attrs.update({
            'data-ajax--delay': 250,
            'data-placeholder': self.prompt,
            'style': 'width: 30em;'
        })
        return attrs


class AddLittermateForm(forms.Form):
    new_littermate = forms.ModelChoiceField(
        queryset=Dog.objects.all(),
        widget=CustomAutocompleteSelect(LitterDog._meta.get_field(
            'dog'), "Search for a littermate here", admin.site)
    )

如果没有具有合适关系的模型,他们将不得不声明一个不受管理的模型:

...
class Meta:
    managed = False

【讨论】:

    【解决方案2】:

    确实可以使用管理员的 select2。 您只需要使用AutocompleteSelect 小部件。 AutocompleteSelect 小部件需要一个关系和一个管理站点。

    如果您的模型 A 具有指向模型 B 的 ForeignKey 字段,您希望使用 AutocompleteSelect,您可以简单地使用(如 cuto 建议的那样)。

    from django.contrib.admin.widgets import AutocompleteSelect
    from myapp.model import ModelA, ModelB
    from django.contrib import admin
    
    
    class MyForm(form.Form):
        model_b = forms.ModelChoiceField(
            queryset=ModelB.objects.all(),
            widget=AutocompleteSelect(ModelA._meta.get_field('model_b').remote_field, admin.AdminSite)
        )
    
    

    因为我没有关系,所以我使用了FakeRelation 类,因为get_url(self) 函数只使用了模型属性。 AutocompleteSelect 小部件的使用与 autocomplete_fields 小部件的使用绑定到相同的条件。

    from django.contrib.admin.widgets import AutocompleteSelect
    from django.contrib import admin
    from django import forms
    from myapp.models import countries
    
    class FakeRelation:
        def __init__(self, model):
            self.model = model
    
    
    class CustomAutocompleteSelect (AutocompleteSelect):
        def __init__(self, model, admin_site, attrs=None, choices=(), using=None):
            rel = FakeRelation(model)
            super().__init__(rel, admin_site, attrs=attrs, choices=choices, using=using)
    
    class PreventionPlanForm(form.Form):
        DateFrom = forms.DateField(label="From")
        DateTo = forms.DateField(label="To")
        PE1_Name = forms.ModelChoiceField(
            queryset=countries.objects.all(),
            widget=CustomAutocompleteSelect(countries, admin.AdminSite)
        )
    

    正如jenniwren 指出的那样:确保在您的模板中加载正确的 Javascripts/CSS 文件(路径可能会在不同的 django 版本中发生变化):

    • admin/css/vendor/select2/select2.css
    • admin/js/vendor/select2/select2.full.js
    • admin/css/autocomplete.css
    • admin/js/autocomplete.js

    【讨论】:

    • 您也可以使用 ForeignKey 字段中的实际关系,而不是创建 FakeRelation:<model>._meta.get_field('<foreign key>').remote_field
    • 这是一个很好的解决方案,可能对某些人有帮助。在我的情况下,我只有一个模型,该模型具有我想要自动选择小部件的模型的外键字段。我只是想选择和处理它。因此我无法使用您建议的解决方案。仍然是个好主意,我会编辑答案。
    • 这(第一个代码)对我有用,但我不得不将 admin.AdminSite 更改为 admin.site
    • 您使用的是哪个 django 版本?与 admin.AdminSite 的虚假关系解决方案对 Django 2.2 有效。
    • 感谢您的解决方案!这只是花了我一点时间才弄明白,尽管现在看起来很明显:确保将适当的 css/js 文件添加到非管理页面的模板中。对于 Django 2.2,这包括 admin/css/vendor/select2/select2.cssadmin/js/vendor/select2/select2.full.jsadmin/css/autocomplete.cssadmin/js/autocomplete.js
    【解决方案3】:

    我花了几个小时试图理解为什么我的代码不起作用,直到我偶然发现@jenniwren 关于引用 css/js 文件的评论。这是一个完整的工作解决方案。

    要在任何自定义表单中为对给定模型具有“员工”和“查看”访问权限的登录用户使用 AutocompleteSelect 小部件,您可以声明以下内容:

    from django.urls import reverse
    from django.contrib.admin.widgets import AutocompleteSelect
    from django.contrib import admin
    
    class UserAutocompleteSelect(AutocompleteSelect):
        def get_url(self):
            model = CustomUser
            return reverse(self.url_name % (self.admin_site.name, model._meta.app_label, model._meta.model_name))
    
    class UserChoiceField(forms.ModelChoiceField):
        def __init__(self, queryset=None, widget=None, **kwargs):
            if queryset is None:
                queryset = CustomUser.objects.all()
            if widget is None:
                widget = UserAutocompleteSelect(None, admin.site)  # pass `None` for `rel`
            super().__init__(queryset, widget=widget, **kwargs)
    
    class UserAutocompleteSelectForm(forms.ModelForm):
        """
        for changing user on Play objects
        using amdin module autocomplete
        """
        user = UserChoiceField(
            # queryset=CustomUser.objects.all(),
            help_text=_('Select the user to replace the current one')
        )
    
        class Meta:
            model = Play
            fields = ('user', )
    

    您可以使用相同的方法,将 CustomUserPlay 替换为您自己的模型

    如果这不适用于您正在使用的 html 模板开箱即用,这意味着您需要将所需的 css/js 文件包含到您的模板中。这是一个简单的方法:

    如果表单在视图中声明为这样:

    form = UserAutocompleteSelectForm()
    ...
    context = {
                'form': form, 
                ...
                }
    return render(request, 'users/change_user.html', context)
    

    您应该将以下行添加到 html 模板以包含所需的 css/js 文件:

    {% block extrahead %}
    {{ block.super }}
    {{ form.media }}
    {% endblock %}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-28
      • 2017-11-16
      • 2018-10-27
      • 2010-10-03
      • 2011-03-29
      • 1970-01-01
      • 2018-10-27
      • 1970-01-01
      相关资源
      最近更新 更多