администратор django переопределяет filter_horizontal

#django #django-admin #django-admin-filters

#django #django-администратор #django-admin-фильтры

Вопрос:

Я собираюсь создать «продвинутый» filter_horizontal, с большим количеством фильтров, но, похоже, я не могу найти виджет для переопределения. Я знаю, что он использует related_widget_wrapper.html но если я хочу добавить к нему функциональные возможности понятным способом, какой виджет нужно переопределить.

На данный момент мое решение для резервного копирования — создать полное решение на javascript, добавить к нему выпадающий список при загрузке формы (созданный на javascript) и выполнить вызовы ajax для изменения фильтра…но это кажется излишеством.

Что я сделал до сих пор :

 # Override filteredSelectMultiple, add javascript and add attributes on the tag to identify the element, and add parameter url that will contain the ajax call
class AjaxFilterHorizontalWidget(FilteredSelectMultiple):

    def __init__(self, url, verbose_name = '', is_stacked=False, attrs=None, choices=()):
        self.url = url
        super().__init__(verbose_name, is_stacked, attrs, choices)

    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        context['widget']['attrs']['data-url'] = self.url
        context['widget']['attrs']['data-ajax-select'] = '1'
        return context



    class Media:
        js = ['admin/js/ajax_filter_horizontal.js']
  

Ajax_filter_horizontal.js

 $(document).ready(function () {
    $('select[data-ajax-select=1]').each(function (index, item) {
        var currentRequest;
        var url = $(item).data('url')
        // var wrapper = $('#'   $(item).prop('id')).closest('.selector-available')
        $(document).on('keyup', $('.selector-filter input'), function () {
            if ($('.selector-filter input').val().length < 3) {
                $(item).empty()
                return
            }
            currentRequest = $.ajax({
                url: url,
                data: {q: $('.selector-filter input').val()},
                beforeSend : function()    {
                    if(currentRequest != null) {
                        currentRequest.abort();
                    }
                },
                success: function (data) {
                    $(item).empty()
                    let item_to = $('#'   $(item).prop('id').replace('_from', '_to'))
                    if (data.results.length > 500) {
                        $('#'   $(item).prop('id')).append('<option disabled value="" title="">Too many results, refine your search...</option>')
                        return
                    }

                    for (let instance of data.results) {
                        if ($('option[value=' instance.id ']', item_to).length == 0) {
                            $('#'   $(item).prop('id')).append('<option value="' instance.id '" title="' instance.text '">' instance.text '</option>')
                        }
                    }

                    SelectBox.init($(item).prop('id'))
                }
            })
        });
    });
});
  

Мне пришлось переопределить поле, просто чтобы удалить проверку (по какой-то причине проверка также выполняется для исходных значений, в левой части filter_horizontal)

 class AjaxMultipleChoiceField(MultipleChoiceField):
    widget = AjaxFilterHorizontalWidget

    def validate(self, value):
        pass
        """Validate that the input is a list or tuple."""
        # if self.required and not value:
        #     raise ValidationError(self.error_messages['required'], code='required')
  

Вот как я это называю :

     self.fields['person'] = `AjaxMultipleChoiceField(widget=AjaxFilterHorizontalWidget(url= '/person-autocomplete-advanced/', verbose_name='People to invite'))`
  

Мне не удается найти, где предварительно заполнить значения в разделе «кому», когда я редактирую существующее поле.

Ответ №1:

Администратор модели Django переопределяет BaseModelAdmin который содержит следующий код.

django.contrib.admin.options.py

 class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
    ...
    def formfield_for_dbfield(self, db_field, request, **kwargs):
        ...
        if db_field.name in self.raw_id_fields:
            kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.remote_field, self.admin_site, using=db)
        elif db_field.name in (list(self.filter_vertical)   list(self.filter_horizontal)):
            kwargs['widget'] = widgets.FilteredSelectMultiple(
                db_field.verbose_name,
                db_field.name in self.filter_vertical
            )
  

Можно заметить, что если передан аргумент filter_vertical or filter_horizontal
в ModelAdmin варианте, чем добавляет FilteredSelectMultiple виджет.

Ниже приведен источник FilteredSelectMultiple Вы можете переопределить это, если необходимо

django.contrib.admin.widgets.py

 class FilteredSelectMultiple(forms.SelectMultiple):
    """
    A SelectMultiple with a JavaScript filter interface.

    Note that the resulting JavaScript assumes that the jsi18n
    catalog has been loaded in the page
    """
    @property
    def media(self):     # override this property in your custom class
        js = ["core.js", "SelectBox.js", "SelectFilter2.js"]
        return forms.Media(js=["admin/js/%s" % path for path in js])
    ...
    def get_context(self, name, value, attrs):
        context = super(FilteredSelectMultiple, self).get_context(name, value, attrs)
        context['widget']['attrs']['class'] = 'selectfilter'
        if self.is_stacked:
            context['widget']['attrs']['class']  = 'stacked'
        context['widget']['attrs']['data-field-name'] = self.verbose_name
        context['widget']['attrs']['data-is-stacked'] = int(self.is_stacked)
        return context
  

Для переопределения JS или мультимедиа

Вы могли заметить, что в media свойство FilteredSelectMultiple класса включено несколько js, вы можете изменять их в соответствии с вашими потребностями.

Для модификации HTML-шаблона

FilteredSelectMultiple переопределяет, django.forms.widgets.SelectMultiple что в конечном итоге переопределяет django.forms.widgets.Select виджет.

Таким образом, можно сказать, что FilteredSelectMultiple использует следующие свойства Select виджета

 class Select(ChoiceWidget):
    input_type = 'select'
    template_name = 'django/forms/widgets/select.html'
    option_template_name = 'django/forms/widgets/select_option.html'
    add_id_index = False
    checked_attribute = {'selected': True}
    option_inherits_attrs = False
    ...

  

Вы можете переопределить эти параметры внутри вашего FilteredSelectMultiple класса.

Я надеюсь, что приведенная выше информация полезна для вас.

Комментарии:

1. Спасибо, это то, что я выбрал в итоге.

2. Я подготовил пользовательский виджет под названием AjaxFitlerHorizontal, просто у меня есть пара вопросов: где определить, что набор запросов должен быть пустым в init () и значения, которые будут установлены при редактировании формы?

3. Не могли бы вы, пожалуйста, приложить AjaxFitlerHorizontal свой вопрос или в отдельном вопросе?

4. Большое спасибо за помощь, я добавил свой код к вопросу, я не уверен, что то, что я выбрал, является правильным подходом, поскольку это в основном сторона javascript.