How to show related object popup when the user clicks on selected autocomplete field options in Django admin?

青春壹個敷衍的年華 提交于 2020-06-17 09:43:26

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!