Generic compact list_filter with counts

 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# utils/admin.py
from operator import itemgetter
from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec
from django.db.models import Count
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext_lazy as _

def add_compact_filter(field_name,
                       field_lookup='%s__exact',
                       field_detector=lambda f: getattr(f, 'compact_filter', False),
                       field_value_getter=lambda value: value,
                       filtered_value_getter=None,
                       filtered_display_getter=None,):
    
    # the simplest case, but they rely on field_name so can't provide as default args
    if filtered_value_getter is None:
        filtered_value_getter = lambda obj: getattr(obj, field_name)
    if filtered_display_getter is None:
        filtered_display_getter = lambda obj, count: '%s (%s)' % (getattr(obj, field_name), count)
        
    class CustomChoiceFilterSpec(ChoicesFilterSpec):
        def __init__(self, f, request, params, model, model_admin, field_path=None):
            super(CustomChoiceFilterSpec, self).__init__(f, request, params, model, model_admin, field_path)
            self.lookup_kwarg = field_lookup % f.name
            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')}
            
            # get the compact list of filter values
            filtered_values = []
            counts = {}
            for obj in self.objects:
                value = filtered_value_getter(obj)
                if value in counts:
                    counts[value] += 1
                else:
                    counts[value] = 1
                    filtered_values.append((obj, filtered_value_getter(obj)))
            filtered = [(value, filtered_display_getter(obj, counts[value])) for obj, value in filtered_values]
            
            for value, display in filtered:
                yield {'selected': smart_unicode(value) == self.lookup_val,
                       'query_string': cl.get_query_string({self.lookup_kwarg: field_value_getter(value)}),
                       'display': display}
    
    FilterSpec.filter_specs.insert(0, (field_detector, CustomChoiceFilterSpec))


# EXAMPLE usage:

# myapp/admin.py
from utils.admin import add_compact_filter
from .fields import CountryField

add_compact_filter(
    field_name='country',
    field_lookup='%s__exact',
    field_detector=lambda f: isinstance(f, CountryField),
    filtered_display_getter=lambda obj, count: '%s (%s)' % (obj.get_country_display(), count),
)

More like this

  1. Compact list_filter with counter by fab10m 3 years, 2 months ago
  2. Compact list_filter by onlinehero 4 years, 2 months ago
  3. Accordion changelist admin by djm 3 years, 4 months ago
  4. Generic CSV export admin action factory by anentropic 3 years, 11 months ago
  5. Admin action for a generic "CSV Export" (fix for unicode) by __alexander__ 4 months, 2 weeks ago

Comments

(Forgotten your password?)