django - 当用户单击 Django admin 中选定的自动完成字段选项时,如何显示相关对象弹出窗口?
问题描述
我想在用户单击 Django 管理员自动完成多选字段中的选定选项时显示标准相关对象弹出窗口,就像单击ForeignKey
字段铅笔图标时一样。
型号如下:
class Author(models.Model):
name = models.CharField(_('name'), max_length=160)
class Book(models.Model):
authors = models.ManyToManyField(Author, verbose_name=_('authors'), blank=True)
...
是否可以通过扩展 Django admin 来做到这一点?
解决方案
我发现使用ModelSelect2Multiple
来自django-autocomplete-light (DAL) 的小部件更容易添加所需的自定义 HTML。
管理员配置如下:
from dal import autocomplete
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
class Media:
js = [
'/static/books/js/book-admin.js',
# other required JS files, see https://github.com/yourlabs/django-autocomplete-light/issues/1143#issuecomment-632755326
]
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == 'authors':
return forms.ModelMultipleChoiceField(
required=False,
label=Author._meta.verbose_name_plural.title(),
queryset=Author.objects.all(),
widget=autocomplete.ModelSelect2Multiple(
url='author-autocomplete',
attrs={'data-html': True}))
return super().formfield_for_foreignkey(db_field, request, **kwargs)
DAL 视图如下:
from django.utils.safestring import mark_safe
from dal import autocomplete
from .models import Author
from django.urls import reverse
class AuthorAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_staff:
return Author.objects.none()
qs = Author.objects.all()
if self.q:
qs = qs.filter(name__icontains=self.q)
return qs
def get_selected_result_label(self, item):
change_url = reverse('admin:books_author_change', kwargs={'object_id': item.id})
return mark_safe('<span onclick="event.stopPropagation(); showRelatedObjectPopup({'
f"href: '{change_url}?_popup=1', id: 'change_id_author'"
f'}})">{item.name}</span>')
在 admin 中的Book change 视图的Authors字段中选择新作者时,HTML 元素由 管理,因此存在自定义 HTML,单击新选择的作者会按预期打开弹出窗口。但是自定义 HTML 不会出现在现有选择中,因此必须使用 jQuery 添加点击处理程序:ModelSelect2Multiple
book-admin.js
'use strict';
window.addEventListener("load", function () {
/**
* Show related object popup when user clicks on selected author name.
*/
(function ($) {
var $authorsSelect2Selections = $('div.form-row.field-authors .select2-selection__choice > span:nth-child(2)');
var $authorOptions = $('#id_authors > option');
$authorsSelect2Selections.click(function ($event) {
$event.stopPropagation();
var self = this;
// Find corresponding option by text comparison, assuming that author name is unique
var $result = $authorOptions.filter(function() {
return $(this).text() === self.textContent;
});
showRelatedObjectPopup({
href: '/admin/books/author/' + $result.val() + '/change/?_popup=1',
id: 'change_id_other_authors'
});
});
})(django.jQuery);
});
event.stopPropagation()
防止 Select2 下拉菜单打开。
您还需要覆盖dismissChangeRelatedObjectPopup
并避免出现问题,这是一个不完整的版本dismissAddRelatedObjectPopup
:book-admin.js
/**
* Override Django related object popup dismissal functions with DAL amendments.
* Incomplete.
*/
(function ($) {
function dismissChangeRelatedObjectPopupForDAL(win, objId, newRepr, newId) {
var elem = document.getElementById(win.name);
if (elem && elem.options && elem.dataset.autocompleteLightUrl) { // this is a DAL element
$(elem.options).each(function () {
if (this.value === objId) {
this.textContent = newRepr;
// this.value = newId;
}
});
// FIXME: trigger('change') does not update the element as it should and removes popup code
// $(elem).trigger('change');
win.close();
} else {
dismissChangeRelatedObjectPopupOriginal(win, objId, newRepr, newId);
}
}
window.dismissChangeRelatedObjectPopupOriginal = window.dismissChangeRelatedObjectPopup;
window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopupForDAL;
function dismissAddRelatedObjectPopupForDAL(win, newId, newRepr) {
var elem = document.getElementById(win.name);
if (elem && elem.options && elem.dataset.autocompleteLightUrl) { // this is a DAL element
elem.options[elem.options.length] = new Option(newRepr, newId, true, true);
// FIXME: trigger('change') adds the new element, but removes popup code
$(elem).trigger('change');
win.close();
} else {
dismissAddRelatedObjectPopupOriginal(win, newId, newRepr);
}
}
window.dismissAddRelatedObjectPopupOriginal = window.dismissAddRelatedObjectPopup
window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopupForDAL
})(django.jQuery);
推荐阅读
- python - 组 id 基于另一列的条件
- node.js - 使用 Docker 构建为生产创建 React 应用程序?
- c# - 错误:无法在 Linux 上加载文件或程序集
- greenplum - Greenplum 连接器在 ibm Datas 阶段给出主机名解析错误
- geode - Apache Geode Native Client 日志在启动本机客户端时显示连接池错误
- apache - 使用 ubuntu 托管 OSM 矢量切片服务器
- javascript - 为什么第二个点显示在错误的位置?
- python - 更新包时意外关闭anaconda提示后无法更新conda包
- c++ - 推断返回值的类型
- amazon-web-services - Terraform - AWS - CreateSecurityGroup - 参数 GroupName 无效。组名可能不是 sg-* 格式