更改Django autocomplete_fields标签

gkl3eglg  于 2023-06-07  发布在  Go
关注(0)|答案(2)|浏览(183)

我正在尝试为autocomplete_fields中某个类型的所有项设置自定义标签。
到目前为止,对于下拉列表,人们会使用

...
class CustomDisplay(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return "Some custom text: {}".format(obj.name)
...
somethings = CustomDisplay(queryset=Something.object.all())
...

但是在autocomplete_fields = (somethings,)中使用这个会导致自动完成取消,并向我显示一个带有自定义文本的下拉列表。

7hiiyaii

7hiiyaii1#

字段显示普通select小部件的原因是,在定义自定义字段时,没有将小部件设置为AutocompleteSelect
在指定autocomplete_fieldsModelAdmin类中,导入CustomDisplayAutocompleteSelect并添加以下方法:

from django.contrib.admin.widgets import AutocompleteSelect

class YourModelAdmin(admin.ModelAdmin):
    autocomplete_fields = ['something']

    ...

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        db = kwargs.get('using')

        if db_field.name == 'something':
            return CustomDisplay(queryset=Something.object.all(), widget=AutocompleteSelect(db_field.remote_field, self.admin_site, using=db))
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

这将仅在查看现有示例时显示自定义文本。当您查看autocomplete下拉列表并选择一个条目时,标签不是从label_from_instance()生成的,而是从AutocompleteJsonView内部的一个直接的str()调用生成的。
因此,假设您只想更改autocomplete小部件中的标签(要全面更改标签,您显然只需更改模型__str()__方法),您还需要在admin.py中创建一个自定义类,以修改AutocompleteJsonView中的get()方法:

from django.contrib.admin.options import AutocompleteJsonView
from django.http import Http404, JsonResponse

class CustomAutocompleteJsonView(AutocompleteJsonView):
    def get(self, request, *args, **kwargs):
        if not self.model_admin.get_search_fields(request):
            raise Http404(
                '%s must have search_fields for the autocomplete_view.' %
                type(self.model_admin).__name__
            )
        if not self.has_perm(request):
            return JsonResponse({'error': '403 Forbidden'}, status=403)

        self.term = request.GET.get('term', '')
        self.paginator_class = self.model_admin.paginator
        self.object_list = self.get_queryset()
        context = self.get_context_data()

        # Replace this with the code below.
        #
        # return JsonResponse({
        #     'results': [
        #         {'id': str(obj.pk), 'text': str(obj)}
        #         for obj in context['object_list']
        #     ],
        #     'pagination': {'more': context['page_obj'].has_next()},
        # })

        return JsonResponse({
            'results': [
                {'id': str(obj.pk), 'text': 'Some custom text: {}'.format(obj.name)}
                for obj in context['object_list']
            ],
            'pagination': {'more': context['page_obj'].has_next()},
    })

现在,在自动完成显示结果的ModelAdmin类(而不是指定autocomplete_fields的ModelAdmin类)上设置autocomplete_view

def autocomplete_view(self, request):
    return CustomAutocompleteJsonView.as_view(model_admin=self)(request)

因此,如果您有一个名为YourModelAdminModelAdmin类,其中包含autocomplete_fields = ['something'],则可以为Something模型的相应ModelAdmin类设置autocomplete_view
Django 4.2更新:

from django.contrib.admin.sites import AdminSite
from django.contrib.admin.widgets import AutocompleteSelect
from django.contrib.admin.views.autocomplete import AutocompleteJsonView
from django.core.exceptions import PermissionDenied

class CustomAdminSite(AdminSite):
    # This will change the view for all autocompletes.
    # You can handle specific cases within the autocomplete get() method.
    def autocomplete_view(self, request):
        return CustomAutocompleteJsonView.as_view(admin_site=self)(request)

admin_site = CustomAdminSite()

class CustomAutocompleteJsonView(AutocompleteJsonView):
    def get(self, request, *args, **kwargs):
        (
            self.term,
            self.model_admin,
            self.source_field,
            to_field_name,
        ) = self.process_request(request)

        if not self.has_perm(request):
            raise PermissionDenied

        self.object_list = self.get_queryset()
        context = self.get_context_data()
        if type(self.model_admin) == SomethingModelAdmin:
            return JsonResponse(
                {
                    "results": [
                        {'id': str(obj.pk), 'text': 'Some custom text: {}'.format(obj.name)}
                        for obj in context['object_list']
                    ],
                    "pagination": {"more": context["page_obj"].has_next()},
                }
            )
        else:
            return super().get(request, *args, **kwargs)

class YourModelAdmin(admin.ModelAdmin):
    autocomplete_fields = ['something']

    ...

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        db = kwargs.get('using')

        if db_field.name == 'something':
            return CustomDisplay(queryset=Something.objects.all(),
                                           widget=AutocompleteSelect(db_field,
                                                                     self.admin_site,
                                                                     using=db))
        return super().formfield_for_foreignkey(db_field, request, **kwargs)
i5desfxk

i5desfxk2#

这就是我最后的工作:
在父管理上使用autocomplete_fields

class ParentAdmin(models.ModelAdmin):
    autocomplete_fields = (
       "sub_account_stuff",
       "other_sub_items",
    )

autocomplete_fields在子管理模型上使用search_fields,但是,您可以使用get_search_results进行覆盖,因此我创建了一个mixin,其中解析请求引用者url可以解析父模型:

class AutoCompleteSearchParentIdsMixin:
    def get_parent_object_from_auto_complete_search(self, request):
        from urllib.parse import urlparse
        path_info = urlparse(request.headers.get('Referer')).path
        resolved = resolve(path_info)
        if resolved.kwargs:
            return MyParentModel.objects.get(pk=resolved.kwargs["object_id"])
        return None

    def get_search_results(self, request, queryset, search_term):
        parent = self.get_parent_object_from_auto_complete_search(request)
        if parent:
            queryset = queryset.filter(
                parent_ids__contained_by=parent.ids
            )
            if search_term.isnumeric():
                queryset = queryset.filter(Q(id=int(search_term)) | Q(parent_id=int(search_term)))
            else:
                queryset = queryset.filter(Q(payload__Name__icontains=search_term))
        else:
            queryset = queryset.none()
        return queryset.distinct(), False

并混合在一起覆盖get_search_results,如下所示:

class SubAccountAdmin(AutoCompleteSearchParentIdsMixin, admin.ModelAdmin):
    ...

class OtherSubItemsAdmin(AutoCompleteSearchParentIdsMixin, admin.ModelAdmin):
    ...

为了改变它的显示方式,我在模型中添加了一个自定义的__str__

class OtherSubItemsModel(models.Model):
   ...
   def __str__(self, obj):
      return f"<OtherSubItem id={self.id} name={self.payload.get('Name')}>"

引用:

相关问题