Фильтр администратора Django с использованием выражений F () - PullRequest
12 голосов
/ 24 ноября 2010

знает ли кто-нибудь, как фильтровать в админке, основываясь на сравнении полей модели - выражений F ()?

Предположим, у нас есть следующая модель:

class Transport(models.Model):
    start_area = models.ForeignKey(Area, related_name='starting_transports')
    finish_area = models.ForeignKey(Area, related_name='finishing_transports')

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

Я пытался сделать это путем создания пользовательского FilterSpec, но есть две проблемы:

  • FilterSpec привязан только к одному полю.
  • FilterSpec не поддерживает выражения F () и исключает.

Вторая проблема может быть решена путем определения пользовательского класса ChangeList, но я не вижу способа решить первый.

Я также пытался "эмулировать" фильтр прямо вэкземпляр ModelAdmin путем перегрузки метода queryset и отправки дополнительного контекста в шаблон списка изменений, где сам фильтр будет жестко задан и напечатан вручную.К сожалению, похоже, проблема в том, что Django извлекает мои параметры GET (используемые в ссылке фильтра), поскольку они неизвестны экземпляру ModelAdmin, и вместо этого он помещает только? E = 1, что должно сигнализировать о некоторой ошибке.

Спасибо всем заранее.

РЕДАКТИРОВАТЬ : Кажется, что функциональность, которая позволила бы это, запланирована для следующего выпуска Django, см. http://code.djangoproject.com/ticket/5833. Тем не менее, кто-то делаетесть подсказка, как этого добиться в Django 1.2?

Ответы [ 3 ]

3 голосов
/ 09 мая 2011

это не самый лучший способ *, но он должен работать

class TransportForm(forms.ModelForm):
    transports = Transport.objects.all()
    list = []
    for t in transports:
        if t.start_area.pk == t.finish_area.pk:
            list.append(t.pk)
    select = forms.ModelChoiceField(queryset=Page.objects.filter(pk__in=list))

    class Meta:
        model = Transport
1 голос
/ 14 мая 2011

Решение включает в себя добавление вашей FilterSpec и, как вы сказали, реализацию собственного ChangeList.Поскольку имя фильтра проверено, вы должны присвоить своему фильтру имя с именем поля модели.Ниже вы увидите хак, позволяющий использовать фильтр по умолчанию для того же поля.

Вы добавляете свой FilterSpec перед стандартными FilterSpecs.

Ниже приведена рабочая реализация, работающая на Django 1.3

from django.contrib.admin.views.main import *
from django.contrib import admin
from django.db.models.fields import Field
from django.contrib.admin.filterspecs import FilterSpec
from django.db.models import F
from models import Transport, Area
from django.contrib.admin.util import get_fields_from_path
from django.utils.translation import ugettext as _


# Our filter spec
class InAreaFilterSpec(FilterSpec):

    def __init__(self, f, request, params, model, model_admin, field_path=None):
        super(InAreaFilterSpec, self).__init__(
            f, request, params, model, model_admin, field_path=field_path)
        self.lookup_val = request.GET.get('in_area', None)

    def title(self):
        return 'Area'

    def choices(self, cl):
        del self.field._in_area
        yield {'selected': self.lookup_val is None,
               'query_string': cl.get_query_string({}, ['in_area']),
               'display': _('All')}
        for pk_val, val in (('1', 'In Area'), ('0', 'Trans Area')):
            yield {'selected': self.lookup_val == pk_val,
                   'query_string': cl.get_query_string({'in_area' : pk_val}),
                   'display': val}

    def filter(self, params, qs):
        if 'in_area' in params:
            if params['in_area'] == '1':
                qs = qs.filter(start_area=F('finish_area'))
            else:
                qs = qs.exclude(start_area=F('finish_area'))
            del params['in_area']
        return qs

def in_area_test(field):
    # doing this so standard filters can be added with the same name
    if field.name == 'start_area' and not hasattr(field, '_in_area'):
        field._in_area = True
        return True    
    return False

# we add our special filter before standard ones
FilterSpec.filter_specs.insert(0, (in_area_test, InAreaFilterSpec))


# Defining my own change list for transport
class TransportChangeList(ChangeList):

    # Here we are doing our own initialization so the filters
    # are initialized when we request the data
    def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin):
        #super(TransportChangeList, self).__init__(request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin)
        self.model = model
        self.opts = model._meta
        self.lookup_opts = self.opts
        self.root_query_set = model_admin.queryset(request)
        self.list_display = list_display
        self.list_display_links = list_display_links
        self.list_filter = list_filter
        self.date_hierarchy = date_hierarchy
        self.search_fields = search_fields
        self.list_select_related = list_select_related
        self.list_per_page = list_per_page
        self.model_admin = model_admin

        # Get search parameters from the query string.
        try:
            self.page_num = int(request.GET.get(PAGE_VAR, 0))
        except ValueError:
            self.page_num = 0
        self.show_all = ALL_VAR in request.GET
        self.is_popup = IS_POPUP_VAR in request.GET
        self.to_field = request.GET.get(TO_FIELD_VAR)
        self.params = dict(request.GET.items())
        if PAGE_VAR in self.params:
            del self.params[PAGE_VAR]
        if TO_FIELD_VAR in self.params:
            del self.params[TO_FIELD_VAR]
        if ERROR_FLAG in self.params:
            del self.params[ERROR_FLAG]

        if self.is_popup:
            self.list_editable = ()
        else:
            self.list_editable = list_editable
        self.order_field, self.order_type = self.get_ordering()
        self.query = request.GET.get(SEARCH_VAR, '')
        self.filter_specs, self.has_filters = self.get_filters(request)
        self.query_set = self.get_query_set()
        self.get_results(request)
        self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
        self.pk_attname = self.lookup_opts.pk.attname


    # To be able to do our own filter,
    # we need to override this
    def get_query_set(self):

        qs = self.root_query_set
        params = self.params.copy()

        # now we pass the parameters and the query set 
        # to each filter spec that may change it
        # The filter MUST delete a parameter that it uses
        if self.has_filters: 
            for filter_spec in self.filter_specs:
                if hasattr(filter_spec, 'filter'):
                    qs = filter_spec.filter(params, qs)

        # Now we call the parent get_query_set()
        # method to apply subsequent filters
        sav_qs = self.root_query_set
        sav_params = self.params

        self.root_query_set = qs
        self.params = params

        qs = super(TransportChangeList, self).get_query_set()

        self.root_query_set = sav_qs
        self.params = sav_params

        return qs


class TransportAdmin(admin.ModelAdmin):
    list_filter = ('start_area','start_area')

    def get_changelist(self, request, **kwargs):
        """
        Overriden from ModelAdmin
        """
        return TransportChangeList


admin.site.register(Transport, TransportAdmin)
admin.site.register(Area)
0 голосов
/ 11 мая 2011

К сожалению, FilterSpecs в настоящее время очень ограничены в Django. Просто они не были созданы с учетом настроек.

К счастью, многие уже давно работают над патчем для FilterSpec. Он пропустил этап в 1.3, но похоже, что теперь он наконец-то в багажнике и должен выйти в следующем выпуске.

# 5833 (пользовательские параметры фильтра)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...