问题
I want to show the standard related object popup when the user clicks on a selected option in a Django admin autocomplete multi-select field, like it works when clicking the ForeignKey
field pencil icon 🖉.
The models are as follows:
class Author(models.Model):
name = models.CharField(_('name'), max_length=160)
class Book(models.Model):
authors = models.ManyToManyField(Author, verbose_name=_('authors'), blank=True)
...
Is it possible to do this by extending Django admin?
回答1:
I found that adding the required custom HTML was easier using the ModelSelect2Multiple
widget from django-autocomplete-light (DAL).
The admin configuration is as follows:
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)
The DAL view is as follows:
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>')
When selecting a new author in the Authors field in the Book change view in admin, the HTML element is managed by ModelSelect2Multiple
, so the custom HTML is present and clicking on the newly selected author opens the popup as intended. But the custom HTML will not be present in existing selections, so the click handlers have to be added with jQuery in 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()
prevents the Select2 dropdown from opening.
You also need to override dismissChangeRelatedObjectPopup
and dismissAddRelatedObjectPopup
in book-admin.js
to avoid problems, here is an incomplete version:
/**
* 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);
来源:https://stackoverflow.com/questions/61446990/how-to-show-related-object-popup-when-the-user-clicks-on-selected-autocomplete-f