在没有自定义 HTML 的 Django Admin 中,有没有一种方法可以添加自定义 autocomplete_field,在保存时为模型字段提供查询集?

Is there a way in Django Admin, without custom HTML, to add a custom autocomplete_field that upon save provides a queryset to a model field?

目前,在我的 Django 管理模型视图之一中,我能够单独 select 多个可用国家(申请国家),如我的数据库中可用:

但是当我实际上只与少数国家/地区分组合作时,这非常令人厌烦。但我确实需要添加或减去奇数国家/地区,而无需管理和考虑每组国家/地区的类别名称。

我的想法是使用关键字(在应用程序中也有其他用途):

我想做的是通过 admin.ModelAdmin 添加一个自定义字段,它也是一个 autocomplete_field,在下拉列表中显示与国家相关的关键字。如果此下拉列表中的项目是 selected,它将 return 一个国家查询集,模型字段(申请国家/地区)具有 selected 关键字。不需要保存任何其他内容,不需要跟踪过去使用了哪个国家/地区相关的关键字。这只是一次批量 select 多个国家的捷径(因此在 add/edit 后,自定义字段将始终显示为空)。

我希望尽可能以 Django 管理方式做事,而不涉及自定义 HTML 之类的事情。如果这不可能,我将探索涉及 Django-Q 任务的解决方案。

下面是一些解释关系的代码:

models.py -> 关键词:

class Keyword(models.Model):
    name = models.TextField()

models.py -> 国家:

class Country(models.Model):
    """
    More info: https://en.wikipedia.org/wiki/ISO_3166-1
    """

    name = models.TextField()
    alpha_2_code = models.CharField(max_length=2)
    alpha_3_code = models.CharField(max_length=3)
    independent = models.BooleanField()
    keywords = models.ManyToManyField(Keyword)

models.py -> 资助计划

class FundingProgram(models.Model):
    # Unimportant fields redacted
    applicant_countries = models.ManyToManyField(Country, blank=True)

admin.py -> FundingProgramAdmin

@admin.register(models.FundingProgram)
class FundingProgramAdmin(admin.ModelAdmin, DynamicArrayMixin):
    autocomplete_fields = (
        "applicant_countries",
    )
    search_fields = (
        "applicant_countries__keywords__name",
    )

    # Here is where I think I should make a custom field,
    # that acts as a multiple choice (autocomplete) dropdown where
    # a function takes input and returns a queryset to applicant_countries field

 

我设法使用 Django Autocomplete Light 库解决了这个问题。

这就是我所做的(这里只包括相关的补充,阅读关于如何安装库等的文档)。

views.py(待办事项:让下面的代码块更干)

from dal import autocomplete

from my_app import models


class KeywordsSelect(autocomplete.Select2QuerySetView):
    def get_queryset(self):
        if not self.request.user.is_authenticated:
            return models.Keyword.objects.none()
        qs = models.Keyword.objects.all()
        if self.q:
            # Todo: Probably worth sorting the queryset for consistency
            qs = qs.filter(name__istartswith=self.q)
        return qs

    def get_result_value(self, result):
        return result.pk


class CountriesSelect(autocomplete.Select2QuerySetView):
    def get_queryset(self):
        if not self.request.user.is_authenticated:
            return models.Country.objects.none()
        qs = models.Country.objects.all()
        if self.q:
            # Todo: Probably worth sorting the queryset for consistency
            qs = qs.filter(name__istartswith=self.q)
        return qs

    def get_result_value(self, result):
        return result.pk

urls.py

from django.conf.urls import url
from my_app import views
urlpatterns = [
    url(r"^keywords-select/$", views.KeywordsSelect.as_view(), name="keywords-select",),
    url(r"^countries-select/$", views.CountriesSelect.as_view(), name="countries-select",),
 ]

上面创建了一个端点,可以像这样查询:http://localhost:8000/countries-select/?q=Austri,返回一个输出,如:

{
  "results": [
    {
      "id": 9,
      "text": "Austria",
      "selected_text": "Austria"
    }
  ],
  "pagination": {
    "more": false
  }
}

现在我们可以修改 admin 以包含修改后的表单,该表单将在自动选择框中调用此端点。请注意,我需要明确列出所有字段并让两个字段并排存在,它们需要包含在自己的元组中,如下 ("applicant_countries", "bulk_add_countries_by_keyword",)

admin.py

from dal import autocomplete
from django import forms

class FundingProgramForm(forms.ModelForm):
    # Newly created field not retrieved from models.py
    bulk_add_countries_by_keyword = forms.ModelMultipleChoiceField(
        queryset=models.Keyword.objects.all(),
        widget=autocomplete.ModelSelect2Multiple(url="keywords-select"),
        required=False,
    )

    class Meta:
        # applicant_countries is a field from models.py which gets overridden here
        widgets = {
            "applicant_countries": autocomplete.ModelSelect2Multiple(
                url="countries-select"
            )
        }

    def save(self, commit=True):
        qs = self.cleaned_data["applicant_countries"].union(
            models.Country.objects.filter(
                keywords__in=self.cleaned_data["bulk_add_countries_by_keyword"]
            )
        )
        self.cleaned_data["applicant_countries"] = qs
        return super(FundingProgramForm, self).save(commit=commit)


@admin.register(models.FundingProgram)
class FundingProgramAdmin(admin.ModelAdmin):
    form = FundingProgramForm
    # Other fields hardcoded below, otherwise they won't appear in model form
    fields = (
        ("applicant_countries", "bulk_add_countries_by_keyword",),
    )
    # Warning: Remove the following from autocomplete_fields, as it is already overidden with a django autocomplete light widget
    #     autocomplete_fields = (
    #         "applicant_countries",
    #     )