Django admin - 没有外键或多对多关系的自动完成(_field)
Django admin - autocomplete(_field) without foreignkey or manytomany relationship
我想知道是否有一个函数可以让我实现一个 autocomplete_field 而无需通过外键将该变量链接到另一个关系。
即我有模型 Aaa、Bbb 和 Ccc。 Bbb 和 Ccc 使用外键关联,而 Aaa 甚至通过其他模型与其他两个模型关联。现在,我希望在管理员端有一个特定字段 Ccc,它由 Aaa 中的一个字段组成,可以使用 Aaa 中的值自动完成(或者至少是一个建议功能,以便将错误降至最低)。但是,Ccc 和 Aaa 没有直接关系;因此,我发现仅仅为这个项目分配一个外键是无效的。关于如何解决这个问题有什么建议吗?
正如您从我的问题中看出的那样,我是 django 的新手,非常感谢您的帮助。
您可以将它们子类化并进行一些猴子修补以重用 Django 的自动完成逻辑:
django.contrib.admin.widgets.AutocompleteSelect
django.contrib.admin.ModelAdmin
django.contrib.admin.views.autocomplete.AutocompleteJsonView
class MyAutocompleteSelectWidget(widgets.AutocompleteSelect):
url_name = 'my_autocomplete'
def get_url(self):
return reverse(self.url_name)
def optgroups(self, name, value, attr=None):
# Patch self.choices for non-ModelChoiceField.widget
to_field_name = getattr(self.field.remote_field, 'field_name')
self.choices = SimpleNamespace(
field=SimpleNamespace(empty_values=(), label_from_instance=lambda obj: getattr(obj, to_field_name)),
queryset=SimpleNamespace(using=lambda _: SimpleNamespace(filter=lambda **_: [SimpleNamespace(**{to_field_name: v}) for v in value])))
return super().optgroups(name, value, attr=attr)
class MyAutocompleteModelAdmin(admin.ModelAdmin):
my_autocomplete_fields = {}
def __init__(self, model, admin_site):
super().__init__(model, admin_site)
# Patch remote_field for AutocompleteJsonView.process_request
for field_name, deferred_remote_field in self.my_autocomplete_fields.items():
remote_field = deferred_remote_field.field
self.model._meta.get_field(field_name).remote_field = SimpleNamespace(field_name=remote_field.attname, model=remote_field.model)
def formfield_for_dbfield(self, db_field, request, **kwargs):
if 'widget' not in kwargs:
if db_field.name in self.my_autocomplete_fields:
kwargs['widget'] = MyAutocompleteSelectWidget(db_field, self.admin_site)
return super().formfield_for_dbfield(db_field, request, **kwargs)
def to_field_allowed(self, request, to_field):
# Allow search fields that are not referenced by foreign key fields
if to_field in self.search_fields:
return True
return super().to_field_allowed(request, to_field)
class MyAutocompleteJsonView(autocomplete.AutocompleteJsonView):
def get_queryset(self):
# Patch get_limit_choices_to for non-foreign key field
self.source_field.get_limit_choices_to = lambda: {}
return super().get_queryset()
def process_request(self, request):
term, model_admin, source_field, to_field_name = super().process_request(request)
# Store to_field_name for use in get_context_data
self.to_field_name = to_field_name
return term, model_admin, source_field, to_field_name
def get_context_data(self, *, object_list=None, **kwargs):
context_data = super().get_context_data(object_list=object_list, **kwargs)
# Patch __str__ to use to_field_name for `str(obj)` in AutocompleteJsonView.get
for obj in context_data['object_list']:
obj_type = type(obj)
new_obj_type = type(obj_type.__name__, (obj_type,), {'__str__': lambda _self: getattr(_self, self.to_field_name), '__module__': obj_type.__module__})
obj.__class__ = new_obj_type
return context_data
用法:
@admin.register(Aaa)
class AaaAdmin(MyAutocompleteModelAdmin):
search_fields = ('a_field',)
@admin.register(Ccc)
class CccAdmin(MyAutocompleteModelAdmin):
my_autocomplete_fields = {
'a_specific_field': Aaa.a_field,
}
path('my_autocomplete', MyAutocompleteJsonView.as_view(admin_site=admin.site), name='my_autocomplete')
我想知道是否有一个函数可以让我实现一个 autocomplete_field 而无需通过外键将该变量链接到另一个关系。
即我有模型 Aaa、Bbb 和 Ccc。 Bbb 和 Ccc 使用外键关联,而 Aaa 甚至通过其他模型与其他两个模型关联。现在,我希望在管理员端有一个特定字段 Ccc,它由 Aaa 中的一个字段组成,可以使用 Aaa 中的值自动完成(或者至少是一个建议功能,以便将错误降至最低)。但是,Ccc 和 Aaa 没有直接关系;因此,我发现仅仅为这个项目分配一个外键是无效的。关于如何解决这个问题有什么建议吗?
正如您从我的问题中看出的那样,我是 django 的新手,非常感谢您的帮助。
您可以将它们子类化并进行一些猴子修补以重用 Django 的自动完成逻辑:
django.contrib.admin.widgets.AutocompleteSelect
django.contrib.admin.ModelAdmin
django.contrib.admin.views.autocomplete.AutocompleteJsonView
class MyAutocompleteSelectWidget(widgets.AutocompleteSelect):
url_name = 'my_autocomplete'
def get_url(self):
return reverse(self.url_name)
def optgroups(self, name, value, attr=None):
# Patch self.choices for non-ModelChoiceField.widget
to_field_name = getattr(self.field.remote_field, 'field_name')
self.choices = SimpleNamespace(
field=SimpleNamespace(empty_values=(), label_from_instance=lambda obj: getattr(obj, to_field_name)),
queryset=SimpleNamespace(using=lambda _: SimpleNamespace(filter=lambda **_: [SimpleNamespace(**{to_field_name: v}) for v in value])))
return super().optgroups(name, value, attr=attr)
class MyAutocompleteModelAdmin(admin.ModelAdmin):
my_autocomplete_fields = {}
def __init__(self, model, admin_site):
super().__init__(model, admin_site)
# Patch remote_field for AutocompleteJsonView.process_request
for field_name, deferred_remote_field in self.my_autocomplete_fields.items():
remote_field = deferred_remote_field.field
self.model._meta.get_field(field_name).remote_field = SimpleNamespace(field_name=remote_field.attname, model=remote_field.model)
def formfield_for_dbfield(self, db_field, request, **kwargs):
if 'widget' not in kwargs:
if db_field.name in self.my_autocomplete_fields:
kwargs['widget'] = MyAutocompleteSelectWidget(db_field, self.admin_site)
return super().formfield_for_dbfield(db_field, request, **kwargs)
def to_field_allowed(self, request, to_field):
# Allow search fields that are not referenced by foreign key fields
if to_field in self.search_fields:
return True
return super().to_field_allowed(request, to_field)
class MyAutocompleteJsonView(autocomplete.AutocompleteJsonView):
def get_queryset(self):
# Patch get_limit_choices_to for non-foreign key field
self.source_field.get_limit_choices_to = lambda: {}
return super().get_queryset()
def process_request(self, request):
term, model_admin, source_field, to_field_name = super().process_request(request)
# Store to_field_name for use in get_context_data
self.to_field_name = to_field_name
return term, model_admin, source_field, to_field_name
def get_context_data(self, *, object_list=None, **kwargs):
context_data = super().get_context_data(object_list=object_list, **kwargs)
# Patch __str__ to use to_field_name for `str(obj)` in AutocompleteJsonView.get
for obj in context_data['object_list']:
obj_type = type(obj)
new_obj_type = type(obj_type.__name__, (obj_type,), {'__str__': lambda _self: getattr(_self, self.to_field_name), '__module__': obj_type.__module__})
obj.__class__ = new_obj_type
return context_data
用法:
@admin.register(Aaa)
class AaaAdmin(MyAutocompleteModelAdmin):
search_fields = ('a_field',)
@admin.register(Ccc)
class CccAdmin(MyAutocompleteModelAdmin):
my_autocomplete_fields = {
'a_specific_field': Aaa.a_field,
}
path('my_autocomplete', MyAutocompleteJsonView.as_view(admin_site=admin.site), name='my_autocomplete')