Django管理过滤器使用F()表达式

时间:2010-11-24 10:23:53

标签: django filter admin

是否有人知道如何根据模型字段的比较来过滤管理员--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是相同的,trans-area是其他

我试图通过创建自定义FilterSpec来实现这一目标,但有两个问题:

  • FilterSpec仅绑定到一个字段。
  • FilterSpec不支持F()表达式并排除。

第二个问题可能是通过定义自定义ChangeList类来解决的,但我认为无法解决第一个问题。

我还尝试通过重载查询集方法并将更多上下文发送到更改列表模板来直接在模型管理实例中“模拟”过滤器,其中过滤器本身将被硬编码并手动打印。不幸的是,似乎存在问题,Django取出了我的GET参数(用于过滤器链接),因为它们对于ModelAdmin实例是未知的,而是仅放置?e = 1,它应该发出一些错误信号。

提前感谢任何人。

编辑:似乎计划用于下一个Django版本的功能,请参阅http://code.djangoproject.com/ticket/5833。还是有人知道如何在Django 1.2中实现这一目标吗?

3 个答案:

答案 0 :(得分:3)

这不是最好的方式*,但应该有效

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 :(得分:1)

解决方案涉及添加您的FilterSpec,正如您所说的实现您自己的ChangeList。在验证过滤器名称时,必须使用模型字段名称命名过滤器。下面你会看到一个hack,允许在同一个字段中使用默认过滤器。

在标准FilterSpecs之前添加FilterSpec。

以下是在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)

答案 2 :(得分:0)

不幸的是,目前在Django中,FilterSpecs非常有限。简单来说,它们不是为了定制而创建的。

值得庆幸的是,很多人一直在为FilterSpec打补丁。它错过了1.3里程碑,但看起来它现在终于在后备箱了,应该在下一个版本中出现。

#5833 (Custom FilterSpecs)

如果要在主干上运行项目,可以立即利用它,或者也可以修补当前安装。否则,你将不得不等待,但至少它即将到来。