Django Admin change_list, фильтрующий несколько ManyToMany

В Django-Admin у вас есть возможность определить list_filter для полей модели. Это работает и для ManyToMany-Fields.

class ModelA(models.Model):
    name = models.CharField(max_length=100, verbose_name="Name")

class ModelB(models.Model):
    model_a_relation = models.ManyToManyField(ModelA)

class ModelBAdmin(ModelAdmin):
    list_filter = [model_a_relation, ]

admin.site.register(ModelB, ModelBAdmin)

Теперь я могу отфильтровать свой список элементов ModelB по отношению к ModelA в Admin object_list ModelB.

Теперь мой вопрос: можно ли фильтровать по нескольким объектам ModelA?

В change_view ModelB я использую django-autocomplete-light для определения отношений. Могу ли я также использовать этот виджет для фильтрации в списке изменений?

Я представляю запрос на фоне этого фильтра как ModelB.objects.filter(model_a_relation__in=names), где имена — это список выбранных объектов ModelA.

Спасибо, Хорст.


person Horst Schrödinger    schedule 15.01.2014    source источник


Ответы (2)


Я сделал очень грязную попытку решить свою проблему. Это работает, и я хочу поделиться этим с вами.

Для лучшего понимания я использую новые модели в этом примере:

class Tag(models.Model):
    name = models.CharField(max_length=100, verbose_name="Name")

class Book(models.Model):
    tags = models.ManyToManyField(Tag)
    read = models.BooleanField()

class BookAdmin(ModelAdmin):
    list_filter = ['read', ]

admin.site.register(Book, BookAdmin)

Сначала я перезаписал changelist_view файла BookAdmin.

def changelist_view(self, request, extra_context=None):

    extra_context = extra_context if extra_context else {}

    q = request.GET.copy()

    tags = Tag.objects.all().values('id', 'name')

    current_tags = q.get('tags__id__in', [])
    tag_query = request.GET.copy()
    if current_tags:
        tag_query.pop('tags__id__in')
        current_tags = current_tags.split(',')
        all_tag = False
    else:
        all_tag = True
    for tag in tags:
        if str(tag['id']) in current_tags:
            tag['selected'] = True
            temp_list = list(current_tags)
            temp_list.remove(str(tag['id']))
            tag['tag_ids'] = ','.join(temp_list)
        else:
            tag['selected'] = False
            tag['tag_ids'] = ','.join(current_tags)

    extra_context['tag_query'] = '?' if len(tag_query.urlencode()) == 0 else '?' + tag_query.urlencode() + '&'
    extra_context['all_tag'] = all_tag
    extra_context['tags'] = tags

    return super(BookAdmin, self).changelist_view(request, extra_context=extra_context)

Как видите, я смотрю в GET, выбраны ли там какие-то теги или нет. Затем я создаю новый GET-параметр для каждого возможного тега.

И вот мой перезаписанный change_list.html

{% extends "admin/change_list.html" %}

{% block content %}
    {{ block.super }}

    <h3 id="custom_tag_h3"> Fancy Tag filter</h3>
    <ul id="custom_tag_ul">

        <li{% if all_tag %} class="selected"{% endif %}>
            <a href="{{ tag_query }}">All</a>
        </li>

        {% for tag in tags %}
            <li{% if tag.selected %} class="selected"{% endif %}>
                <a href="{{ tag_query }}tags__id__in={{ tag.tag_ids }}{% if not tag.selected %}{% if tag.tag_ids %},{% endif %}{{ tag.id }}{% endif %}">{{ tag.name }}</a>
            </li>
        {% endfor %}

    </ul>

    <script type="text/javascript">
        $('#changelist-filter').append($('#custom_tag_h3'));
        $('#custom_tag_h3').after($('#custom_tag_ul'));
    </script>

{% endblock content %}

Таким образом, у меня есть фильтр, похожий на логический read-фильтр, где я могу активировать более одного параметра. При нажатии на фильтр в запрос добавляется новый идентификатор. Еще один щелчок по уже выбранной опции удаляет идентификатор из запроса. Нажмите Все, чтобы удалить tags_in-параметр дыры из URL.

person Horst Schrödinger    schedule 17.01.2014
comment
Действительно потрясающий плюс, который мог бы показать путь, если бы django-autocomplete-light обеспечивал поддержку автозаполнения в фильтрах списка изменений из коробки! - person jpic; 18.01.2014
comment
Большое спасибо за это! Это именно то, чего мне не хватало в интерфейсе SimpleListFilter. - person Moritz; 09.10.2014

Другой вариант, позволяющий пользователям использовать автозаполнение в представлении списка изменений:

  • настроить ModelAdmin.search_fields для поиска в полях, обслуживаемых автозаполнением, т.е.

    class ModelBAdmin(ModelAdmin):
        search_fields = ['model_a_relation__name']
    
  • переопределить шаблон списка изменений на порождает автозаполнение на входе поиска:

    <script type="text/javascript">
    $(document).ready(function() {
        $('#searchbar').yourlabsAutocomplete({
            url: '{% url 'autocomplete_light_autocomplete' 'YourAutocompleteName' %}',
            choiceSelector: '[data-value]',
        }).input.bind('selectChoice', function(e, choice, autocomplete) {
            $(this).val(choice.text())
                   .parents('form').submit();
        });
    });
    </script>
    
  • убедитесь, что после добавления этого скрипта нет ошибки JS!

person jpic    schedule 15.01.2014
comment
Большое спасибо за вашу идею, но я хочу сосредоточиться не на автозаполнении, а на возможности множественного выбора для фильтрации. Также у меня уже есть панель поиска с функцией автозаполнения для других атрибутов ModelB, и я не хочу, чтобы они смешивались с этим новым материалом. Я подумал об использовании виджета автозаполнения в правой области фильтра. - person Horst Schrödinger; 16.01.2014