Custom Filter in Django Admin on Django 1.3 or below
Asked Answered
D

8

74

How can I add a custom filter to django admin (the filters that appear on the right side of a model dashboard)? I know its easy to include a filter based on a field of that model, but what about a "calculated" field like this:

class NewsItem(models.Model):
    headline = models.CharField(max_length=4096, blank=False)
    byline_1 = models.CharField(max_length=4096, blank=True)
    dateline = models.DateTimeField(help_text=_("date/time that appears on article"))
    body_copy = models.TextField(blank=False)

    when_to_publish = models.DateTimeField(verbose_name="When to publish",  blank=True, null=True)

    # HOW CAN I HAVE "is_live" as part of the admin filter?  It's a calculated state!!
    def is_live(self):
        if self.when_to_publish is not None:
            if ( self.when_to_publish < datetime.now() ):
                return """ <img alt="True" src="/media/img/admin/icon-yes.gif"/> """
        else:
            return """ <img alt="False" src="/media/img/admin/icon-no.gif"/> """      

    is_live.allow_tags = True

class NewsItemAdmin(admin.ModelAdmin):
    form = NewsItemAdminForm
    list_display = ('headline', 'id', 'is_live')
    list_filter = ('is_live')  #  how can i make this work??
Debility answered 14/6, 2009 at 1:25 Comment(3)
Other people already said this feature is in the trunk (1.4 dev). Some more info: release note and documentation.Thetos
Here's a better link to the documentation; extending SimpleListFilter is the way to go here. FilterSpecs are out of date. docs.djangoproject.com/en/dev/ref/contrib/admin/…Daberath
See matley answer below, with a link to the official documentation.Pyelography
M
58

Thanks to gpilotino for giving me the push into the right direction for implementing this.

I noticed the question's code is using a datetime to figure out when its live . So I used the DateFieldFilterSpec and subclassed it.

from django.db import models
from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec,DateFieldFilterSpec
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext as _
from datetime import datetime

class IsLiveFilterSpec(DateFieldFilterSpec):
    """
    Adds filtering by future and previous values in the admin
    filter sidebar. Set the is_live_filter filter in the model field attribute
    'is_live_filter'.    my_model_field.is_live_filter = True
    """

    def __init__(self, f, request, params, model, model_admin):
        super(IsLiveFilterSpec, self).__init__(f, request, params, model,
                                               model_admin)
        today = datetime.now()
        self.links = (
            (_('Any'), {}),
            (_('Yes'), {'%s__lte' % self.field.name: str(today),
                       }),
            (_('No'), {'%s__gte' % self.field.name: str(today),
                    }),

        )


    def title(self):
        return "Is Live"

# registering the filter
FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'is_live_filter', False),
                               IsLiveFilterSpec))

To use you can put the above code into a filters.py, and import it in the model you want to add the filter to

Mackie answered 18/8, 2009 at 16:19 Comment(4)
Can you elaborate a bit more on that last part of what you do with this code?Levey
Rosarch, the last line of code registers the is_live_filter in django, then in your models.py in the model class lets say Article, you have a field called publish_date you would call publish_date.is_live_filterMackie
The last line filter_specs.insert is very important, otherwise your custom filter probably won't show up, one of the built-in filterspecs for that field type will show instead. (I didn't read the answer properly at first and was using the .register method like the built-in filterspecs use!)Shortterm
NB: in Django 1.4, filterspecs (which were always an internals hack) have been refactored to ListFilter and provide a cleaner path to customization: See code.djangoproject.com/ticket/5833Bautista
M
23

you have to write a custom FilterSpec (not documentend anywhere). Look here for an example:

http://www.djangosnippets.org/snippets/1051/

Mcatee answered 11/8, 2009 at 13:25 Comment(0)
S
9

In current django development version there is the support for custom filters: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter

Stalkinghorse answered 15/6, 2011 at 8:52 Comment(2)
To be a bit more specific: docs.djangoproject.com/en/dev/ref/contrib/admin/…Pig
@Pig Feel free to edit any answer in order to improve it. In this case, I've already incorporated your more specific URL into the answer.Pyelography
A
3

You can't, unfortunately. Currently non-field items can not be used as list_filter entries.

Note that your admin class wouldn't have worked even if it was a field, as a single-item tuple needs a comma: ('is_live',)

Analects answered 14/6, 2009 at 8:17 Comment(1)
FWIW, a fix for #5833 is now in django trunk for django 1.4Bautista
N
3

Just a sidenote: You can use the deafult ticks on Django admin more easily like this:

def is_live(self):
    if self.when_to_publish is not None:
        if ( self.when_to_publish < datetime.now() ):
            return True
    else:
        return False

is_live.boolean = True
Nikolai answered 19/12, 2013 at 9:49 Comment(0)
M
3

Not an optimal way (CPU-wise) but simple and will work, so I do it this way (for my small database). My Django version is 1.6.

In admin.py:

class IsLiveFilter(admin.SimpleListFilter):
    title = 'Live'
    parameter_name = 'islive'
    def lookups(self, request, model_admin):
        return (
            ('1', 'islive'),
        )
    def queryset(self, request, queryset):
        if self.value():
            array = []
            for element in queryset:
                if element.is_live.__call__() == True:
                    q_array.append(element.id)
            return queryset.filter(pk__in=q_array)

...

class NewsItemAdmin(admin.ModelAdmin):
    form = NewsItemAdminForm
    list_display = ('headline', 'id', 'is_live')
    list_filter = (IsLiveFilter)

Key idea here is to access custom fields in a QuerySet via __call__() function.

Monroemonroy answered 18/8, 2014 at 9:4 Comment(1)
Maybe not the most optimised way, but a straightforward and working one. It saved me headaches.Spock
P
2

The user supplies goods to some countries postage free. I wanted to filter those countries:

All - all countries, Yes - postage free, No - charged postage.

The main answer for this question did not work for me (Django 1.3) I think because there was no field_path parameter provided in the __init__ method. Also it subclassed DateFieldFilterSpec. The postage field is a FloatField

from django.contrib.admin.filterspecs import FilterSpec

class IsFreePostage(FilterSpec):

    def __init__(self, f, request, params, model, model_admin, field_path=None):
        super(IsFreePostage, self).__init__(f, request, params, model,
            model_admin, field_path)

        self.removes = {
            'Yes': ['postage__gt'],
            'No': ['postage__exact'],
            'All': ['postage__exact', 'postage__gt'] }

        self.links = (
            ('All', {}),
            ('Yes', {'postage__exact': 0}),
            ('No', {'postage__gt': 0}))

        if request.GET.has_key('postage__exact'):
            self.ttl = 'Yes'
        elif request.GET.has_key('postage__gt'):
            self.ttl = 'No'
        else:
            self.ttl = 'All'

    def choices(self, cl):
        for title, param_dict in self.links:
            yield {'selected': title == self.ttl,
                   'query_string': cl.get_query_string(param_dict,
                       self.removes[title]),
                   'display': title}
    def title(self):
        return 'Free Postage'

FilterSpec.filter_specs.insert(0,
    (lambda f: getattr(f, 'free_postage', False), IsFreePostage))

In self.links we supply dicts. used to construct HTTP query strings like ?postage__exact=0 for each of the possible filters. Filters I think are cumulative so if there was a previous request for 'No' and now we have a request for 'Yes' we have to remove the 'No' query. self.removes specifies what needs to be removed for each query. The choices method constructs the query strings, says which filter has been selected and sets the displayed name of the filter.

Piscatelli answered 10/10, 2011 at 12:47 Comment(0)
C
1

Here is the answer and implemented the custom filter as simple as possible this might help

Django admin date range filter

Chaulmoogra answered 10/2, 2017 at 5:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.