Login

Limit ForeignKey filter values to those that have a relationship with current model

Author:
overclocked
Posted:
November 12, 2010
Language:
Python
Version:
1.2
Score:
0 (after 0 ratings)

This is an updated snippet based on http://djangosnippets.org/snippets/2260/

The updated snippet can limit the filtering options for a foreign key field to only those entries that are related to the current model. I.e. if you have an Author model with a FK to Institution model, you can configure Author's changelist to include a filter on Institution, but only allow you to select institutions that have authors. Institutions that do not have authors won't show up on the list.

To enable this, in your model's ModelAdmin class, set

<fieldname>_fk_filter_related_only=True <fieldname>_fk_filter_name_field=<display this field of the related model in filter list>

For example, in your AuthorAdmin class, you can do

institution_fk_filter_related_only=True institution_fk_filter_name_field='name'

Note that for the effect described above to work, you just need the last few lines of the big else clause in init, so if you don't care about filtering by FK property, you can just grab those few lines and create a simpler FilterSpec.

 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
from django.contrib.admin.filterspecs import FilterSpec
from django.contrib.admin.filterspecs import RelatedFilterSpec


class FKFilterSpec(RelatedFilterSpec):
    def __init__(self, f, request, params, model, model_admin):
        filter_by_key = f.name+'_fk_filter_by'
        filter_by_val = getattr(model_admin, filter_by_key, None)
        if filter_by_val != None:
            self.fk_filter_on = True
            # we call FilterSpec constructor, not RelatedFilterSpec
            # constructor; RelatedFilterSpec constructor will try to          
            # get all the pk values on the related models, which we
            # won't need.
            FilterSpec.__init__(self, f, request, params, model, model_admin)
            filter_name_key = f.name+'_fk_filter_name'
            filter_name_val = getattr(model_admin, filter_name_key, None)
            if filter_name_val == None:
                self.lookup_title = f.verbose_name
            else:
                self.lookup_title = f.verbose_name+' '+filter_name_val
            self.lookup_kwarg = f.name+'__'+filter_by_val+'__exact'
            self.lookup_val = request.GET.get(self.lookup_kwarg, None)
            values_list = f.rel.to.objects.values_list(filter_by_val, flat=True).distinct()
            self.lookup_choices = list(values_list)
        else:
            RelatedFilterSpec.__init__(self, f, request, params, model, model_admin)
            self.fk_filter_on = False
            filter_related_key = f.name+'_fk_filter_related_only'
            filter_related_val = getattr(model_admin, filter_related_key, False)
            filter_nf_key = f.name+'_fk_filter_name_field'
            filter_nf_val = getattr(model_admin, filter_nf_key, 'pk')
            if filter_related_val:
                values_list = model_admin.queryset(request).distinct().values_list(f.name+'__pk',f.name+'__'+filter_nf_val).order_by(f.name+'__'+filter_nf_val).distinct()
                self.lookup_choices = list(values_list)


    def choices(self, cl):
        yield {'selected': self.lookup_val is None,
               'query_string': cl.get_query_string({}, [self.lookup_kwarg]),
               'display': _('All')}
        if self.fk_filter_on:
            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': val}
        else:
            for pk_val,val in self.lookup_choices:
                yield {'selected': self.lookup_val == smart_unicode(pk_val),
                       'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}),
                       'display': val}

FilterSpec.filter_specs.insert(0, (lambda f: bool(f.rel), FKFilterSpec))

More like this

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

Comments

overclocked (on November 12, 2010):

I merged the changes described here into 2260.

#

Please login first before commenting.