3👍
Is there any particular reason you expect the related fields in list_filter
to care about the queryset on the admin class? Such filters do not generally remove values that don’t have a related object present.
If you don’t want that behavior, you can write a single filter class that limits the choices however you like as a subset of django.contrib.admin.filter.RelatedFieldListFilter
and use that instead, either overriding its choices
method (the queryset as you defined it on the model admin will be available as cl.root_query_set
within that method) or overriding its __init__
method to create self.lookup_choices
differently – perhaps based on the request. I see no reason you should have to keep redefining the class – one definition should work with as many related fields as you like.
Here’s a simple one that should only include items in the filter if the admin’s filtered queryset has at least one object for the filter value:
class RelatedFieldRestrictedListFilter(RelatedFieldListFilter):
def choices(self, cl):
from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
yield {
'selected': self.lookup_val is None and not self.lookup_val_isnull,
'query_string': cl.get_query_string({},
[self.lookup_kwarg, self.lookup_kwarg_isnull]),
'display': _('All'),
}
for pk_val, val in self.lookup_choices:
if cl.root_query_set.filter(**{self.lookup_kwarg: pk_val}).exists():
yield {
'selected': self.lookup_val == smart_unicode(pk_val),
'query_string': cl.get_query_string({
self.lookup_kwarg: pk_val,
}, [self.lookup_kwarg_isnull]),
'display': val,
}
if (isinstance(self.field, models.related.RelatedObject)
and self.field.field.null or hasattr(self.field, 'rel')
and self.field.null):
yield {
'selected': bool(self.lookup_val_isnull),
'query_string': cl.get_query_string({
self.lookup_kwarg_isnull: 'True',
}, [self.lookup_kwarg]),
'display': EMPTY_CHANGELIST_VALUE,
}
This does a separate query against the DB for each possible value in the list_filter
field, so it’s a bit inefficient – if that turns into a problem you should customize __init__
instead.
Followup:
Ok, so the correct filter choices depend on the request session. That means you need to establish them in the __init__
method of a subclass. Your example shows a related field, so I’ll use RelatedFieldListFilter
again – I’m honestly not sure the concept of a restricted queryset makes sense for any other kind of filter. To do that, the lazy method (shorter to write, less efficient) would be to call the superclass’s __init__
, then change self.lookup_choices
. The less lazy method would be to entirely override __init__
.
The lazy method is something like this:
from django.utils.encoding import smart_unicode
class RelatedFieldRestrictedListFilter(RelatedFieldListFilter):
def __init__(self, field, request, params, model, model_admin, field_path):
super(RelatedFieldRestrictedListFilter, self).__init__(field, request, params, model, model_admin, field_path)
if 'site' in request.session:
self.lookup_choices = [(instance.pk, smart_unicode(instance) for instance in model.objects.filter(site=request.session['site'])]
else:
# something else here
The less lazy method would involve copying in the base code from the superclass’s __init__
method and replacing self.lookup_choices = field.get_choices(include_blank=False)
line with the above.
Note that I’m allowing for the possibility that the session may not have a site
– you should think about what you want to happen if that’s the case. And maybe not bother changing lookup_choices
if the user is a superuser.