#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.