3 回答
TA贡献1851条经验 获得超5个赞
触发 http referer 很丑,所以我做了一个更好的版本:子类化 AutocompleteSelect 并发送额外的查询参数以允许 get_search_results 自动查找正确的 limit_choices_to。只需将这个 mixin 包含在您的 ModelAdmin 中(对于源模型和目标模型)。作为奖励,它还增加了 ajax 请求的延迟,因此您在过滤器中键入时不会向服务器发送垃圾邮件,使选择更宽并设置 search_fields 属性(为对我的系统正确的“translations__name”,自定义您的或省略并像以前一样在 ModelAdmins 上单独设置):
from django.contrib.admin import widgets
from django.utils.http import urlencode
from django.contrib.admin.options import ModelAdmin
class AutocompleteSelect(widgets.AutocompleteSelect):
"""
Improved version of django's autocomplete select that sends an extra query parameter with the model and field name
it is editing, allowing the search function to apply the appropriate filter.
Also wider by default, and adds a debounce to the ajax requests
"""
def __init__(self, rel, admin_site, attrs=None, choices=(), using=None, for_field=None):
super().__init__(rel, admin_site, attrs=attrs, choices=choices, using=using)
self.for_field = for_field
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,
'style': 'width: 50em;'
})
return attrs
def get_url(self):
url = super().get_url()
url += '?' + urlencode({
'app_label': self.for_field.model._meta.app_label,
'model_name': self.for_field.model._meta.model_name,
'field_name': self.for_field.name
})
return url
class UseAutocompleteSelectMixin():
"""
To avoid ForeignKey fields to Event (such as on ReportColumn) in admin from pre-loading all events
and thus being really slow, we turn them into autocomplete fields which load the events based on search text
via an ajax call that goes through this method.
Problem is this ignores the limit_choices_to of the original field as this ajax is a general 'search events'
without knowing the context of what field it is populating. Someone else has exact same problem:
https://stackoverflow.com/questions/55344987/how-to-filter-modeladmin-autocomplete-fields-results-with-the-context-of-limit-c
So fix this by adding extra query parameters on the autocomplete request,
and use these on the target ModelAdmin to lookup the correct limit_choices_to and filter with it.
"""
# Overrides django.contrib.admin.options.ModelAdmin#formfield_for_foreignkey
# Is identical except in case db_field.name is in autocomplete fields it constructs our improved AutocompleteSelect
# instead of django's and passes it extra for_field parameter
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name in self.get_autocomplete_fields(request):
db = kwargs.get('using')
kwargs['widget'] = AutocompleteSelect(db_field.remote_field, self.admin_site, using=db, for_field=db_field)
if 'queryset' not in kwargs:
queryset = self.get_field_queryset(db, db_field, request)
if queryset is not None:
kwargs['queryset'] = queryset
return db_field.formfield(**kwargs)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
# In principle we could add this override in a different mixin as adding the formfield override above is needed on
# the source ModelAdmin, and this is needed on the target ModelAdmin, but there's do damage adding everywhere so combine them.
def get_search_results(self, request, queryset, search_term):
if 'app_label' in request.GET and 'model_name' in request.GET and 'field_name' in request.GET:
from django.apps import apps
model_class = apps.get_model(request.GET['app_label'], request.GET['model_name'])
limit_choices_to = model_class._meta.get_field(request.GET['field_name']).get_limit_choices_to()
if limit_choices_to:
queryset = queryset.filter(**limit_choices_to)
return super().get_search_results(request, queryset, search_term)
search_fields = ['translations__name']
TA贡献1828条经验 获得超6个赞
我有同样的问题。这有点hacky,但这是我的解决方案:
覆盖您正在搜索并要过滤的 ModelAdmin 的 get_search_results
使用请求引用标头获取您需要根据关系来源应用适当过滤器的神奇上下文
从适当的 ForeignKey 的 _meta 中获取 limit_choices_to
预过滤查询集,然后传递给超级方法。
所以对于你的模型:
@register(models.Collection)
class CollectionAdmin(ModelAdmin):
...
search_fields = ['name']
def get_search_results(self, request, queryset, search_term):
if '<app_name>/scheduledcollection/' in request.META.get('HTTP_REFERER', ''):
limit_choices_to = ScheduledCollection._meta.get_field('collection').get_limit_choices_to()
queryset = queryset.filter(**limit_choices_to)
return super().get_search_results(request, queryset, search_term)
这种方法的一个缺点是我们唯一的上下文是在 admin 中编辑的模型,而不是模型的哪个字段,所以如果您的 ScheduledCollection 模型有 2 个集合自动完成字段(比如personal_collection 和协作_collection),我们不能从引用标头中推断出这一点,并以不同的方式对待它们。此外,内联管理员将拥有基于他们内联的父事物的引用网址,而不是反映他们自己的模型。但它适用于基本情况。
希望新版本的 Django 将有一个更清晰的解决方案,例如自动完成选择小部件发送一个带有正在编辑的模型和字段名称的额外查询参数,以便 get_search_results 可以准确地查找所需的过滤器,而不是(可能不准确地)推断引用标头。
TA贡献2011条经验 获得超2个赞
这是另一种在自动完成字段中仅获取选择子集的解决方案。此解决方案不会更改主模型 ( Collection)的默认行为,因此您仍然可以在应用程序中使用带有完整集的自动完成功能来拥有其他视图。
下面是它的工作原理:
带管理器的集合的代理模型
创建一个代理模型来表示 的一个子集Collection,例如CollaborativeCollection表示“协作”类型的集合。您还需要一个管理器来将代理模型的初始查询集限制为预期的子集。
class CollaborativeCollectionManager(models.Manager):
def get_queryset(self):
return (
super()
.get_queryset()
.filter(kind="collaborative")
)
class CollaborativeCollection(models.Model):
class Meta:
proxy = True
objects = CollaborativeCollectionManager()
更新外键以使用代理模型
接下来更新外键ScheduledCollection以使用代理模型。请注意,limit_choices_to如果您不需要其他任何功能,则可以删除该功能。
class ScheduledCollection(models.Model):
...
collection = models.ForeignKey(CollaborativeCollection)
start_date = models.DateField()
end_date = models.DateField()
...
为代理定义管理模型
最后定义代理的管理模型。
@admin.register(CollaborativeCollection)
class CollaborativeCollectionAdmin(admin.ModelAdmin):
search_fields = ["name"]
请注意,您还可以get_search_results()在管理模型中定义自定义,而不是管理器。但是,我发现经理方法似乎更高效。从概念上讲,它也有更多的声音,因为所有查询都CollaborativeCollection将只返回协作集合。
添加回答
举报