from django.contrib.admin.filterspecs import FilterSpec from django.db import models from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _ # FilterSpec.register places the new FilterSpec at the back # of the list. This can be a problem, because the first # matching FilterSpec is the one used. def _register_front(cls, test, factory): cls.filter_specs.insert(0, (test, factory)) FilterSpec.register_front = classmethod(_register_front) class RelatedNullFilterSpec(FilterSpec): def __init__(self, f, request, params, model, model_admin): super(RelatedNullFilterSpec, self).__init__(f, request, params, model, model_admin) if isinstance(f, models.ManyToManyField): self.lookup_title = f.rel.to._meta.verbose_name else: self.lookup_title = f.verbose_name self.null_lookup_kwarg = '%s__isnull' % f.name self.null_lookup_val = request.GET.get(self.null_lookup_kwarg, None) rel_name = f.rel.get_related_field().name self.lookup_kwarg = '%s__%s__exact' % (f.name, rel_name) self.lookup_val = request.GET.get(self.lookup_kwarg, None) self.lookup_choices = f.get_choices(include_blank=False) def title(self): return self.lookup_title def choices(self, cl): yield {'selected': self.lookup_val is None and self.null_lookup_val is None, 'query_string': cl.get_query_string({}, [self.lookup_kwarg,self.null_lookup_kwarg]), 'display': _('All')} yield {'selected': self.lookup_val is None and self.null_lookup_val=="True", 'query_string': cl.get_query_string({self.null_lookup_kwarg:True},[self.lookup_kwarg]), 'display': _('Null')} yield {'selected': self.lookup_val is None and self.null_lookup_val=="False", 'query_string': cl.get_query_string({self.null_lookup_kwarg:False},[self.lookup_kwarg]), 'display': _('Not Null')} 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},[self.null_lookup_kwarg]), 'display': val} FilterSpec.register_front(lambda f: bool(f.rel), RelatedNullFilterSpec)