如何在Django管理后台显示ManyToMany关系的raw_id值?
我有一个应用程序,里面用到了原始ID(raw_id)来处理外键(ForeignKeyField)和多对多关系(ManyToManyField)。在管理界面中,外键的值会显示在编辑框的右侧。
可惜的是,这个功能在多对多关系中并不好用。我检查过代码,觉得这应该是正常的表现。不过,我想知道有没有简单的方法可以改变这种情况?
提前谢谢大家!
更新:我尝试去扩展(subclass)ManyToManyRawIdWidget,但我不知道怎么让原始ID字段使用我自定义的控件。formfield_overrides好像对原始ID字段不起作用。
2 个回答
1
这个方法适用于Django 1.11及更高版本。
from django.contrib.admin.sites import site
from django.contrib.admin.widgets import ManyToManyRawIdWidget
from django.core.urlresolvers import reverse, NoReverseMatch
from django.utils.safestring import mark_safe
class VerboseManyToManyRawIdWidget(ManyToManyRawIdWidget):
def label_and_url_for_value(self, value):
result = []
for v in value:
key = self.rel.get_related_field().name
try:
obj = self.rel.model._default_manager.using(self.db).get(**{key: v})
except (ValueError, self.rel.model.DoesNotExist):
return '', ''
try:
url = reverse(
'{}:{}_{}_change'.format(self.admin_site.name, obj._meta.app_label,
obj._meta.object_name.lower()),
args=(obj.pk,))
except NoReverseMatch:
url = '' # Admin not registered for target model.
result.append('<strong><a href="{}">{}</a></strong>'.format(url, str(obj)))
return mark_safe('; '.join(result)), ''
class VerboseRawIdManyToManyAdminMixin:
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name in self.raw_id_fields:
kwargs.pop('request', None)
if db_field.rel.__class__.__name__ == 'ManyToManyRel':
kwargs['widget'] = VerboseManyToManyRawIdWidget(db_field.rel, site)
return db_field.formfield(**kwargs)
return super().formfield_for_dbfield(db_field, **kwargs)
另外,还有一个Django应用可以实现这个功能 django-salmonella
10
最后我终于让它工作了。这里是更新后的Django 2.0版本
from django.contrib.admin.widgets import ManyToManyRawIdWidget
from django.utils.encoding import smart_str
from django.urls import reverse
from django.utils.html import escape, mark_safe
class VerboseManyToManyRawIdWidget(ManyToManyRawIdWidget):
"""
A Widget for displaying ManyToMany ids in the "raw_id" interface rather
than in a <select multiple> box. Display user-friendly value like the ForeignKeyRawId widget
"""
def __init__(self, remote_field, attrs=None, *args, **kwargs):
super().__init__(remote_field, attrs, *args, **kwargs)
def label_and_url_for_value(self, value):
values = value
str_values = []
field = self.rel.get_related_field()
key = field.name
fk_model = self.rel.model
app_label = fk_model._meta.app_label
class_name = fk_model._meta.object_name.lower()
for the_value in values:
try:
obj = fk_model._default_manager.using(self.db).get(**{key: the_value})
url = reverse('admin:{0}_{1}_change'.format(app_label, class_name), args=[obj.id])
label = escape(smart_str(obj))
elt = '<a href="{0}" {1}>{2}</a>'.format(
url,
'onclick="return showAddAnotherPopup(this);" target="_blank"',
label
)
str_values += [elt]
except fk_model.DoesNotExist:
str_values += [u'???']
return mark_safe(', '.join(str_values)), ''
class MyAdmin(admin.ModelAdmin):
...
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name in ('groups', ):
kwargs['widget'] = VerboseManyToManyRawIdWidget(db_field.remote_field, self.admin_site)
else:
return super().formfield_for_dbfield(db_field, **kwargs)
kwargs.pop('request')
return db_field.formfield(**kwargs)
可惜,我花了钱却没得到什么好处 ;-)
更新:这个代码片段现在可以和Django 2.0兼容了。你也可以看看 http://djangosnippets.org/snippets/2108/