django admin override filter_horizontal - PullRequest
       35

django admin override filter_horizontal

4 голосов
/ 24 марта 2019

Я стремлюсь сделать «расширенный» фильтр_горизонтальный, один с большим количеством фильтров, но я не могу найти виджет для переопределения. Я знаю, что он использует 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'))`

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

1 Ответ

2 голосов
/ 02 апреля 2019

Администратор Django Model переопределяет 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 или 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

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

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

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

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

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 классе.

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

...