Login

Compact list_filter

Author:
onlinehero
Posted:
January 21, 2010
Language:
Python
Version:
1.1
Score:
3 (after 3 ratings)

When doing a list_filter on some object in the Django interface, every single item will be displayed. This is not always smart when very long list of choices can be displayed, even though the majority of these choices might not even exist in the database at all.

Using this script will only display a filtering possibility for those items actually present in the database.

This script is not generic, and you will need to change the lines noted "Change this", since I cannot know what exactly you need to filter.

There is possibly a way to make this completely generic, and I hope this will inspire someone to make it!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# admin.py

# The list of objects can get extremely long. This method will 
# override the filter-method for objects by specifying an extra
# attribute on the list of choices, thus only displaying a filter
# method to those objects actually existing in the database.
class CustomChoiceFilterSpec(ChoicesFilterSpec):
    def __init__(self, f, request, params, model, model_admin):
        super(CustomChoiceFilterSpec, self).__init__(f, request, params, model, model_admin)
        self.lookup_kwarg = '%s__id__exact' % f.name # Change this to match the search of your object
        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
        self.objects = model.objects.all()

    def choices(self, cl):
        yield {'selected': self.lookup_val is None,
               'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
               'display': _('All')}

        # Filter the call - change i.item to whatever you are filtering on
        items = [i.item for i in self.objects]

        for k in items:
            yield {'selected': smart_unicode(k) == self.lookup_val,
                    'query_string': cl.get_query_string({self.lookup_kwarg: k.id}), # Change .id to match what you are searching for
                    'display': k}

FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'compact_filter', False), CustomChoiceFilterSpec))

# models.py
# This is just an example
origin = models.CharField(max_length=2, choices=COUNTRIES, verbose_name='Country of origin')

# Add this to the item
origin.compact_filter = True

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

fab10m (on February 15, 2011):

Hi onlinehero,

thank you very much for your fantastic snippets. In my Django installation (1.1) it seems that your code had a bug. I changed the line number 20 in this way (items = set([i.item for i in self.objects])). I also make some changes to add a counter beside the label of the filter and to manage objects without foreignkey value set.

You can find below my code:

class CustomChoiceFilterSpec(ChoicesFilterSpec):
    def __init__(self, f, request, params, model, model_admin):    
        super(CustomChoiceFilterSpec, self).__init__(f, request, params, model, model_admin)
        self.lookup_kwarg = '%s__id__exact' % f.name # Change this to match the search of your object
        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
        self.objects = model.objects.all()
        self.foreign_key = f.name
        self.foreign_key_count = {} 
        for item in model.objects.values(f.name).annotate(count=Count('pk')):
            self.foreign_key_count[item[f.name]] = item['count']

    def choices(self, cl):
        yield {'selected': self.lookup_val is None,
               'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
               'display': ('All')}
        items = set([getattr(i, self.foreign_key) for i in self.objects])
        for k in items:
            if k is None:
                kk = None
            else:
                kk = k.id
            yield {'selected': smart_unicode(k) == self.lookup_val,
                    'query_string': cl.get_query_string({self.lookup_kwarg: kk}), # Change .id to match what you are searching for
                    'display': '%s (%s)' % (k, self.foreign_key_count[kk])}

FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'compact_filter', False), CustomChoiceFilterSpec))

#

Please login first before commenting.