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
Tags:
ForeignKey Django-Admin FilterSpec
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. FilterSpec for ForeignKeys to auth.User with HTML input tag for ModelAdmin.list_filter by loic 3 years, 11 months ago
  2. ForeignKey filterspec by luc_j 4 years, 7 months ago
  3. Custom change_list filter based on SimpleListFilter shows only referenced (related, used) values by darklow 2 years, 2 months ago
  4. State Machine inspired by acts_as_state_machine by santuri 6 years, 11 months ago
  5. Fix for the bad behaviour of GenericForeignKey field by pinkeen 4 years, 3 months ago

Comments

overclocked (on November 12, 2010):

I merged the changes described here into 2260.

#

Please login first before commenting.