Django管理界面添加自定义筛选器

19

我正在使用Django 1.10,我需要显示数据并创建一个基于另一个模型的值的过滤器(该模型具有引用用于管理模板的模型的外键)。 这是我的两个模型: 这个模型用于生成模板:

class Job(models.Model):
    company = models.ForeignKey(Company)
    title = models.CharField(max_length=100, blank=False)
    description = models.TextField(blank=False, default='')
    store = models.CharField(max_length=100, blank=True, default='')
    phone_number = models.CharField(max_length=60, null=True, blank=True)

这是另一个引用我第一个对象的外键的对象:

class JobAdDuration(models.Model):
    job = models.ForeignKey(Job)
    ad_activated = models.DateTimeField(auto_now_add=True)
    ad_finished = models.DateTimeField(blank=True, null=True)

在我的模板中,我已经能够显示(最新的)开始和结束时间。

def start_date(self,obj):
    if JobAdDuration.objects.filter(job=obj.id).exists():
        tempad = JobAdDuration.objects.filter(job=obj).order_by("-id")[0]
        return tempad.ad_activated

然后我只需在list_display中调用它,这样就可以正常工作了。但是,我尝试使用这些条件设置筛选字段时遇到了问题。

如果我只是将其添加到list_filter中,那么我会收到一个错误,说我的模型中没有这样的字段,这是正确的(因为那个字段在另一个引用我的job表的表中)。所以我想知道解决这个问题的正确方法是什么?我需要为筛选器本身创建另一个函数吗?但即使如此,我也不确定应该如何在list_filter中调用它。

这是我的Django管理页面的一部分代码片段。

class JobAdmin(admin.OSMGeoAdmin, ImportExportModelAdmin):
    inlines = [
    ]

    readonly_fields = ( 'id', "start_date", )

    raw_id_fields = ("company",)

    list_filter = (('JobAdDuration__ad_activated', DateRangeFilter), 'recruitment', 'active', 'deleted', 'position', ('created', DateRangeFilter), 'town')
    search_fields = ('title', 'description', 'company__name', 'id', 'phone_number', 'town')
    list_display = ('title', 'id', 'description', 'active', 'transaction_number', 'company', 'get_position', 'town','created', 'expires', 'views', 'recruitment', 'recruits', 'paid', 'deleted', "start_date", "end_Date", "ad_consultant")


    def start_date(self,obj):
        if JobAdDuration.objects.filter(job=obj.id).exists():
            tempad = JobAdDuration.objects.filter(job=obj).order_by("-id")[0]
            return tempad.ad_activated

编辑: 同时,我试图使用简单的列表过滤器来解决它,但是我无法使其工作。我想放置2个带有日历的输入字段(类似于默认的DateRangeFilter),表示开始和结束时间,然后根据这些值返回数据。这是我对简单过滤器的“原型”功能,它可以工作,但返回硬编码数据。

class StartTimeFilter(SimpleListFilter):
    title = ('Start date')
    parameter_name = 'ad_finished'

    def lookups(self, request, model_admin):
       #return JobAdDuration.objects.values_list("ad_finished")
       return (
       ('startDate', 'stest1'),
       ('startDate1', 'test2')
       )

    def queryset(self, request, queryset):
        if not self.value():
            return queryset

 
        assigned = JobAdDuration.objects.filter(ad_finished__range=(datetime.now() - timedelta(minutes=45000), datetime.now()))
        allJobs = Job.objects.filter(pk__in=[current.job.id for current in assigned])
        return allJobs

 

2
你需要一个自定义的过滤器类,请参考文档示例教程 - shad0w_wa1k3r
是的,我做到了,但我无法在过滤器中添加日期选择器,从那里我将获得用户提供的信息。我想有两个字段,我可以输入开始日期和结束日期,取这些值并根据它来过滤我的数据。我忘记更新我的问题了,现在会做的。 - Proxy
1
只是为了明确,您希望在JobAdDurationJob之间建立ManyToOne关系还是OneToOne关系? - shad0w_wa1k3r
它是多对一。 - Proxy
3个回答

9

我建议使用定制的FieldListFilter,因为它允许根据您的要求将筛选器绑定到不同的模型字段上。

实现此筛选器的步骤如下:

  • 构建lookup_kwargs gte和lte,并将其指定为expected_parameters
  • 定义choices,如果为空列表则返回NotImplementedError
  • 创建表单以验证字段
  • 创建自定义模板,只需输出表单,例如{{spec.form}}
  • 如果表单有效,则取其已清理数据,过滤出Nones并过滤查询集,否则对错误进行处理(在下面的代码中,将忽略错误)

筛选器代码:

class StartTimeFilter(admin.filters.FieldListFilter):
    # custom template which just outputs form, e.g. {{spec.form}}
    template = 'start_time_filter.html'

    def __init__(self, *args, **kwargs):
        field_path = kwargs['field_path']
        self.lookup_kwarg_since = '%s__gte' % field_path
        self.lookup_kwarg_upto = '%s__lte' % field_path
        super(StartTimeFilter, self).__init__(*args, **kwargs)
        self.form = StartTimeForm(data=self.used_parameters, field_name=field_path)

    def expected_parameters(self):
        return [self.lookup_kwarg_since, self.lookup_kwarg_upto]

    # no predefined choices
    def choices(self, cl):
        return []

    def queryset(self, request, queryset):
        if self.form.is_valid():
            filter_params = {
                p: self.form.cleaned_data.get(p) for p in self.expected_parameters()
                if self.form.cleaned_data.get(p) is not None
            }
            return queryset.filter(**filter_params)
        else:
            return queryset

表单可以非常简单,如下所示:

class StartTimeForm(forms.Form):

    def __init__(self, *args, **kwargs):
        self.field_name = kwargs.pop('field_name')
        super(StartTimeForm, self).__init__(*args, **kwargs)
        self.fields['%s__gte' % self.field_name] = forms.DateField()
        self.fields['%s__lte' % self.field_name] = forms.DateField()

谢谢,我还有其他事情要忙,但我会在周末尝试你的解决方案并在那时报告。感谢你的帮助。 - Proxy
嗯,我还没有时间测试它,但看起来它可能会起作用...稍后我会联系你帮忙的 :P - Proxy
抱歉,我忘记给你悬赏了。 - Proxy

6

虽然不是你要求的准确答案,但你可以在JobAdDuration模型管理中添加筛选器。这样,您可以根据ad_activatedad_finished字段筛选相应的工作,并且我已经将job字段添加了链接,以便您可以直接点击进行更轻松的导航。

为了将其转换为日期html5过滤器,我使用了django-admin-rangefilter库。

from django.urls import reverse
from django.contrib import admin
from .models import Job, JobAdDuration
from django.utils.html import format_html
from rangefilter.filter import DateRangeFilter


@admin.register(JobAdDuration)
class JobAdDurationAdmin(admin.ModelAdmin):

    list_filter = (('ad_activated', DateRangeFilter), ('ad_finished', DateRangeFilter))
    list_display = ('id', 'job_link', 'ad_activated', 'ad_finished')

    def job_link(self, obj):
        return format_html('<a href="{}">{}</a>', reverse('admin:job_job_change', args=[obj.job.id]), obj.job.title)
    job_link.short_description = 'Job'

如果你确实想按照现有的路线(在JobAdmin内部过滤),那么事情就会变得非常复杂。


在这种情况下,我必须使用JobAdmin内部的过滤器。 - Proxy

3

最近我遇到了类似的问题,需要根据另一个模型中的值来过滤数据。这可以使用SimpleListFilter完成。你只需要在lookup和queryset函数中进行一些小的调整。我建议你安装django debug工具栏,这样你就可以知道django内部执行的sql查询是什么。

#import your corresponding models first

class StartTimeFilter(SimpleListFilter):
title = ('Start date')
parameter_name = 'ad_finished'

  def lookups(self, request, model_admin):

   data = []
   qs = JobAdDuration.objects.filter()   # Note : if you do not have distinct values of ad_activated apply distinct filter here to only get distinct values
   print qs
   for c in qs:
       data.append([c.ad_activated, c.ad_activated])  # The first c.activated is the queryset condition your filter will execute on your Job model to filter data ... and second c.ad_activated is the data that will be displayed in dropdown in StartTimeFilter
   return data

  def queryset(self, request, queryset):
     if self.value():
       assigned = JobAdDuration.objects.filter(ad_activated__exact = self.value())  # add your custom filter function based on your requirement
       return Job.objects.filter(pk__in=[current.job.id for current in assigned])
     else:
       return queryset

在list_filter中。
list_filter = (StartTimeFilter) # no quotes else it will search for a field in the model 'job'.

这样不会给我一个包含所有可能日期的列表吗?我想要两个“输入框”,在那里我可以输入我的日期并根据它进行筛选。 - Proxy
在这种情况下,@bellum的解决方案已经足够好了。自定义FieldListFilter,创建一个datetimerangeform,将解决您的问题。 - trigo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接