Login

ForeignKey filterspec

Author:
luc_j
Posted:
September 13, 2010
Language:
Python
Version:
1.2
Score:
0 (after 0 ratings)

Unfortunately, it is not possible currently to use foreign keys in list filter of the admin website. list_filter=['city__country'] doesn't work.

This filter spec tries to workaround this problem.

It is also possible to have 2 filters for a foreign-key field but it requires to add a dummy field to the model. Set the fk_filterspec dictionnary on this dummy field and add 'fk':'real-field' to the dict.

 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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#-*- coding: utf-8 -*- 
#!/usr/bin/env python

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

class FkFilterSpec(ChoicesFilterSpec):
    
    def __init__(self, f, request, params, model, model_admin):
        super(FkFilterSpec, self).__init__(f, request, params, model, model_admin)
        
        # ******* Extract parameters ********
        the_args = f.fk_filterspec.copy() 
        #The field of the related table
        fk_field = the_args['fk_field'] 
        
        #The name in the related table to use as label in the choices
        label = the_args.pop('label', '')
        
        #a title: by default the lookup arg
        self.filter_title = the_args.pop('title', '')
        
        #the foreign key field. By default the field the filter is assigned
        fk = the_args.pop('fk', f.name) 
        
        # ******* Build the filter definition ********
        
        self.lookup_kwarg = '{0}__{1}'.format(fk, fk_field)
        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
        self.lookup_labels = {}
        #get the list of values
        values_list = model.objects.values_list(self.lookup_kwarg, flat=True)
        #get the 
        if label:
            label_field = '{0}__{1}__{2}'.format(fk, fk_field, label)
        else:
            label_field = '{0}__{1}'.format(fk, fk_field)
        labels = model.objects.values_list(label_field, flat=True)
        for (v, l) in zip(values_list, labels):
            self.lookup_labels[v] = l
        self.lookup_choices = self.lookup_labels.keys()

    def choices(self, cl):
        yield {'selected': self.lookup_val is None,
                'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
                'display': _('All')}
        for val in self.lookup_choices:
            yield {'selected': smart_unicode(val) == self.lookup_val,
                    'query_string': cl.get_query_string({self.lookup_kwarg: val}),
                    'display': smart_unicode(self.lookup_labels[val])}
    
    def title(self):
        if self.filter_title:
            return self.filter_title
        else:
            return super(FkFilterSpec, self).title()

    @classmethod
    def register_filterspec(cls):
        """register the filter. To be called in the models.py"""
        FilterSpec.filter_specs.insert(0,
            (lambda f: len(getattr(f, 'fk_filterspec', [])), cls)
        )


### In the models.py
from filterspecs import FkFilterSpec
FkFilterSpec.register_filterspec()

class Country(models.Model):
    name = models.CharField(max_length=100)

class City(models.Model):
    name = models.CharField(max_length=100)
    country = models.ForeignKey(Country)
    
class Company(models.Model):
    name = models.CharField(max_length=100)
    city = models.ForeignKey(City)
    city.fk_filterspec = {'fk_field':'country', 
                          'label':'name', 
                          'title':'Country'}

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week 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

gmandx (on April 24, 2011):

I had to change the __init__() method to make it work with Django 1.3

    def __init__(self, f, request, params, model, *args, **kwargs):
        super(FkFilterSpec, self).__init__(f, request, params, model, *args, **kwargs)

It was giving errors because an unexpected field_path keyword argument.

#

daiana (on May 10, 2011):

@gmandx

Thanks for your help! I had the same problem and this solved it.

#

Please login first before commenting.