Можно ли ограничить фильтры ManyToMany / Foreign Key в администраторе Django для модели, где отношения определены в другой модели? - PullRequest
2 голосов
/ 25 марта 2011

Так что название немного тупое, я знаю, но я не мог придумать более краткий способ заявить это. Вот проблема:

Я создал две модели прокси для «пользовательских типов», обе наследуются от django.contrib.auth.User. У каждого есть собственный менеджер, ограничивающий набор запросов элементами, принадлежащими определенной группе. В частности, существует PressUser, который является любым пользователем, принадлежащим к группе «Press», и StaffUser, который является любым пользователем в любой другой группе, кроме «Press».

Проблема заключается в том, что когда я добавляю 'groups' в list_filters на моем modeladmin StaffUsers, в качестве параметров фильтра используются все доступные группы, включая «Нажмите», а не только группы, доступные для StaffUsers.

Я немного потренировался в сети и придумал пользовательскую спецификацию фильтров, которая должна создавать желаемое поведение, но проблема в том, что атрибут 'groups' в модели пользователя на самом деле является related_name, применяемым из модели Group. В результате я не могу прикрепить мои фильтры к «группам» в моей модели прокси.

Есть ли другой способ применения фильтров? В качестве альтернативы, есть ли лучший подход к фильтрации элементов, возвращаемых спецификацией фильтра по умолчанию?

Ответы [ 2 ]

3 голосов
/ 25 марта 2011

Итак, я смог решить свою проблему. Для тех, кто может столкнуться с подобной ситуацией, вот шаги:

Подход, который я выбрал, состоит в том, чтобы изменить шаблон change_list.html и вручную отфильтровать элементы, которые я не хотел включать. Впрочем, нужно внести целый ряд изменений.

Сначала добавьте метод changelist_view в ModelAdmin:

# myproject/account/admin.py

class StaffUserAdmin(models.ModelAdmin):
    ...
    def changelist_view(self, request, extra_context=None):
        groups = Group.objects.exclude(name__in=['Press',]).values_list('name')
        extra_context = {
            'groups': [x[0] for x in groups],
        }
        return super(StaffUserAdmin, self).changelist_view(request,
            extra_context=extra_context)

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

Во-вторых, создайте шаблон change_list.html для своего приложения.

# myproject/templates/admin/auth/staffuser/change_list.html

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

{% load admin_list %}
{% load i18n %}
{% load account_admin %}

{% block filters %}

    {% if cl.has_filters %}
    <div id="changelist-filter">
        <h2>{% trans 'Filter' %}</h2>
        {% for spec in cl.filter_specs %}
            {% ifequal spec.title 'group' %}
                {% admin_list_group_filter cl spec groups %}
            {% else %}
                {% admin_list_filter cl spec %}
            {% endifequal %}
        {% endfor %}
    </div>
    {% endif %}

{% endblock filters %}

Этот заслуживает небольшого объяснения. Сначала загружается тег шаблона: admin_list используется для стандартного тега шаблона Django, отвечающего за рендеринг фильтров, admin_list_filter, i18n используется для trans, а account_admin для моего пользовательского тега шаблона (обсуждено в секунду) admin_list_group_filter.

Переменная spec.title содержит заголовок фильтруемого поля. Поскольку я пытаюсь изменить способ отображения фильтра групп, я проверяю, равен ли он «группам». Если это так, то я использую свой пользовательский тег шаблона, в противном случае он возвращается к стандартному тегу шаблона Django.

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

# myproject/account/templatetags/account_admin.py

from django.template import Library

register = Library()

def admin_list_group_filter(cl, spec, groups):
    return {'title': spec.title, 'choices' : list(spec.choices(cl)), 'groups': groups }
admin_list_group_filter = register.inclusion_tag('admin/auth/group_filter.html')(admin_list_group_filter)

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

В-четвертых, создайте шаблон для тега шаблона.

# myproject/templates/admin/auth/group_filter.html

{% load i18n %}
<h3>{% blocktrans with title as filter_title %} By {{ filter_title }} {% endblocktrans %}</h3>
<ul>
{% for choice in choices %}
    {% if choice.display in groups %}
    <li{% if choice.selected %} class="selected"{% endif %}>
        <a href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
    {% endif %}
{% endfor %}
</ul>

Здесь нет больших сюрпризов. Все, что мы делаем, это собираем все кусочки вместе. Каждый choice представляет собой словарь со всеми значениями, необходимыми для построения ссылки фильтра. В частности, choice.display содержит фактическое имя экземпляра, по которому будет выполняться фильтрация. Очевидно, я настроил проверку, чтобы увидеть, есть ли это значение в моем фильтрованном списке групп, которые я хочу показать, и отображать ссылку, только если она есть.

Итак, это немного запутанно, но работает замечательно хорошо. Точно так же у вас есть список фильтров, который вам нужен, а не фильтры по умолчанию, сгенерированные Django.

1 голос
/ 25 марта 2011

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

Я бы предложил переопределить get_changelist на вашем ModelAdmin, чтобы вернуть пользовательский класс ChangeList, который вы можете определить где-то в вашем admin модуле.

Ваш пользовательский класс ChangeList просто переопределит get_filters, так что вы можете отобразить свой пользовательский FilterSpec для поля group.

Еще одна вещь, которая может вас заинтересовать, это исправления из запроса функции ticket для указания пользовательских фильтров. Последний патч пока не работает для Django 1.3rc1, хотя @ bendavis78 недавно объявил, что работает над новым, но в зависимости от вашей версии Django он может применяться корректно.

Похоже, он едва не пропустил разрез, чтобы попасть в 1,3-мильный этап, поэтому я полагаю, что он попадет в ствол, как только начнут работать над Django 1.4.

...