Django管理员覆盖filter_horizo​​ntal

时间:2019-03-24 20:48:39

标签: django django-admin django-admin-filters

我的目标是制作一个具有更多过滤器的“高级” filter_horizo​​ntal,但是我似乎找不到要覆盖的小部件。 我知道它使用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_horizo​​ntal.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_horizo​​ntal的左侧)进行了

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 个答案:

答案 0 :(得分:2)

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_verticalfilter_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类中覆盖这些选项。

我希望以上信息对您有用。