Index: django/contrib/admin/validation.py
===================================================================
--- django/contrib/admin/validation.py	(revision 14865)
+++ django/contrib/admin/validation.py	(working copy)
@@ -3,6 +3,7 @@
 from django.db.models.fields import FieldDoesNotExist
 from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
     _get_foreign_key)
+from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec
 from django.contrib.admin.util import get_fields_from_path, NotRelationField
 from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
 from django.contrib.admin.options import HORIZONTAL, VERTICAL
@@ -49,21 +50,43 @@
             fetch_attr(cls, model, opts, 'list_display_links[%d]' % idx, field)
             if field not in cls.list_display:
                 raise ImproperlyConfigured("'%s.list_display_links[%d]'"
-                        "refers to '%s' which is not defined in 'list_display'."
+                        " refers to '%s' which is not defined in 'list_display'."
                         % (cls.__name__, idx, field))
 
     # list_filter
     if hasattr(cls, 'list_filter'):
         check_isseq(cls, 'list_filter', cls.list_filter)
-        for idx, fpath in enumerate(cls.list_filter):
-            try:
-                get_fields_from_path(model, fpath)
-            except (NotRelationField, FieldDoesNotExist), e:
-                raise ImproperlyConfigured(
-                    "'%s.list_filter[%d]' refers to '%s' which does not refer to a Field." % (
-                        cls.__name__, idx, fpath
-                    )
-                )
+        for idx, item in enumerate(cls.list_filter):
+            # There are three methods of specifying a filter:
+            #   1: 'field' - a simple field filter, poss. w/ relationships (eg, 'field__rel')
+            #   2: ('field', SomeFieldFilterSpec) - a field-based filter spec
+            #   3: SomeFilterSpec - a non-field filter spec
+            if callable(item) and not isinstance(item, models.Field):
+                # If item is option 3, it should be a FilterSpec, but not a FieldFilterSpec
+                if not issubclass(item, FilterSpec) or issubclass(item, FieldFilterSpec):
+                    raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
+                            " which is not of type FilterSpec." 
+                            % (cls.__name__, idx, item))
+            else:
+                try:
+                    # Check for option #2 (tuple)
+                    field, factory = item
+                except (TypeError, ValueError):
+                    # item is option #1
+                    field = item
+                else:
+                    # item is option #2
+                    if not issubclass(factory, FieldFilterSpec):
+                        raise ImproperlyConfigured("'%s.list_filter[%d][1]'"
+                            " refers to '%s' which is not of type FieldFilterSpec."
+                            % (cls.__name__, idx, factory.__name__))
+                # Validate the field string
+                try:
+                    get_fields_from_path(model, field)
+                except (NotRelationField, FieldDoesNotExist), e:
+                    raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'"
+                            " which does not refer to a Field."
+                            % (cls.__name__, idx, field))
 
     # list_per_page = 100
     if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int):
Index: django/contrib/admin/__init__.py
===================================================================
--- django/contrib/admin/__init__.py	(revision 14865)
+++ django/contrib/admin/__init__.py	(working copy)
@@ -1,5 +1,6 @@
 # ACTION_CHECKBOX_NAME is unused, but should stay since its import from here
 # has been referenced in documentation.
+from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec
 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
 from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
 from django.contrib.admin.options import StackedInline, TabularInline
Index: django/contrib/admin/views/main.py
===================================================================
--- django/contrib/admin/views/main.py	(revision 14865)
+++ django/contrib/admin/views/main.py	(working copy)
@@ -1,4 +1,4 @@
-from django.contrib.admin.filterspecs import FilterSpec
+from django.contrib.admin.filterspecs import FilterSpec, FieldFilterSpec
 from django.contrib.admin.options import IncorrectLookupParameters
 from django.contrib.admin.util import quote, get_fields_from_path
 from django.core.paginator import Paginator, InvalidPage
@@ -57,22 +57,33 @@
         if ERROR_FLAG in self.params:
             del self.params[ERROR_FLAG]
 
+        self.filter_specs, self.has_filters = self.get_filters(request)
         self.order_field, self.order_type = self.get_ordering()
         self.query = request.GET.get(SEARCH_VAR, '')
         self.query_set = self.get_query_set()
         self.get_results(request)
         self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
-        self.filter_specs, self.has_filters = self.get_filters(request)
         self.pk_attname = self.lookup_opts.pk.attname
 
     def get_filters(self, request):
         filter_specs = []
         if self.list_filter:
-            for filter_name in self.list_filter:
-                field = get_fields_from_path(self.model, filter_name)[-1]
-                spec = FilterSpec.create(field, request, self.params,
-                                         self.model, self.model_admin,
-                                         field_path=filter_name)
+            for item in self.list_filter:
+                if callable(item):
+                    spec = item(request, self.params, self.model, self.model_admin)
+                else:
+                    field_path = None
+                    try:
+                        field, factory = item
+                    except (TypeError, ValueError):
+                        field, factory = item, FieldFilterSpec.create
+                    if not isinstance(field, models.Field):
+                        field_path = field
+                        field = get_fields_from_path(self.mode, field_path)[-1]
+                    spec = factory(field, request, self.params, self.model, 
+                            self.model_admin, field_path=field_path)
+                        
+
                 if spec and spec.has_output():
                     filter_specs.append(spec)
         return filter_specs, bool(filter_specs)
@@ -164,12 +175,26 @@
             order_type = params[ORDER_TYPE_VAR]
         return order_field, order_type
 
+    def apply_filter_specs(self, qs, lookup_params):
+        for filter_spec in self.filter_specs:
+            new_qs = filter_spec.get_query_set(self, qs)
+            if new_qs is not None and new_qs is not False:
+                qs = new_qs
+                # Only consume params if we got a new queryset
+                for param in filter_spec.consumed_params():
+                    try:
+                        del lookup_params[param]
+                    except KeyError:
+                        pass
+        return qs
+
     def get_query_set(self):
         qs = self.root_query_set
         lookup_params = self.params.copy() # a dictionary of the query string
         for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR):
             if i in lookup_params:
                 del lookup_params[i]
+        key = ''
         for key, value in lookup_params.items():
             if not isinstance(key, str):
                 # 'key' will be used as a keyword argument later, so Python
@@ -187,6 +212,9 @@
                     lookup_params[key] = False
                 else:
                     lookup_params[key] = True
+                
+        # Let every filter spec modify the qs and params to its liking
+        qs = self.apply_filter_specs(self, qs, lookup_params)
 
         # Apply lookup parameters from the query string.
         try:
Index: django/contrib/admin/filterspecs.py
===================================================================
--- django/contrib/admin/filterspecs.py	(revision 14865)
+++ django/contrib/admin/filterspecs.py	(working copy)
@@ -17,28 +17,9 @@
 
 class FilterSpec(object):
     filter_specs = []
-    def __init__(self, f, request, params, model, model_admin,
-                 field_path=None):
-        self.field = f
+    def __init__(self, request, params, model, model_admin):
         self.params = params
-        self.field_path = field_path
-        if field_path is None:
-            if isinstance(f, models.related.RelatedObject):
-                self.field_path = f.var_name
-            else:
-                self.field_path = f.name
 
-    def register(cls, test, factory):
-        cls.filter_specs.append((test, factory))
-    register = classmethod(register)
-
-    def create(cls, f, request, params, model, model_admin, field_path=None):
-        for test, factory in cls.filter_specs:
-            if test(f):
-                return factory(f, request, params, model, model_admin,
-                               field_path=field_path)
-    create = classmethod(create)
-
     def has_output(self):
         return True
 
@@ -46,8 +27,21 @@
         raise NotImplementedError()
 
     def title(self):
-        return self.field.verbose_name
+        raise NotImplementedError()
 
+    def get_query_set(self, cl, qs):
+        return False
+    
+    def consumed_params(self):
+        """
+        Return a list of parameters to consume from the change list querystring.
+        
+        Override this for non-field based FilterSpecs subclasses in order
+        to consume custom GET parameters, as any GET parameters that are not
+        consumed and are not a field name raises an exception.
+        """
+        return []
+
     def output(self, cl):
         t = []
         if self.has_output():
@@ -61,7 +55,40 @@
             t.append('</ul>\n\n')
         return mark_safe("".join(t))
 
-class RelatedFilterSpec(FilterSpec):
+
+class FieldFilterSpec(FilterSpec):
+    field_filter_specs = []
+    _high_priority_index = 0
+
+    def __init__(self, f, request, params, model, model_admin, field_path=None):
+        super(FieldFilterSpec, self).__init__(f, request, params, model, model_admin, field_path=None)
+        self.field = f
+        if field_path is None:
+            if isinstance(f, models.related.RelatedObject):
+                self.field_path = f.var_name
+            else:
+                self.field_path = f.name
+
+    def title(self):
+        return self.field.verbose_name
+
+    def register(cls, test, factory, high_priority=True):
+        if high_priority:
+            cls.field_filter_Specs.insert(cls._high_priority_index, (test, factory))
+            cls._high_priority_index += 1
+        else:
+            cls.field_filter_specs.append((test, factory))
+    register = classmethod(register)
+
+    def create(cls, f, request, params, model, model_admin, field_path=None):
+        for test, factory in cls.filter_specs:
+            if test(f):
+                return factory(f, request, params, model, model_admin,
+                               field_path=field_path)
+    create = classmethod(create)
+
+        
+class RelatedFilterSpec(FieldFilterSpec):
     def __init__(self, f, request, params, model, model_admin,
                  field_path=None):
         super(RelatedFilterSpec, self).__init__(
@@ -94,11 +121,11 @@
                    'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}),
                    'display': val}
 
-FilterSpec.register(lambda f: (
+FieldFilterSpec.register(lambda f: (
         hasattr(f, 'rel') and bool(f.rel) or
-        isinstance(f, models.related.RelatedObject)), RelatedFilterSpec)
+        isinstance(f, models.related.RelatedObject)), RelatedFilterSpec, False)
 
-class ChoicesFilterSpec(FilterSpec):
+class ChoicesFilterSpec(FieldFilterSpec):
     def __init__(self, f, request, params, model, model_admin,
                  field_path=None):
         super(ChoicesFilterSpec, self).__init__(f, request, params, model,
@@ -116,9 +143,9 @@
                     'query_string': cl.get_query_string({self.lookup_kwarg: k}),
                     'display': v}
 
-FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec)
+FieldFilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec, False)
 
-class DateFieldFilterSpec(FilterSpec):
+class DateFieldFilterSpec(FieldFilterSpec):
     def __init__(self, f, request, params, model, model_admin,
                  field_path=None): 
         super(DateFieldFilterSpec, self).__init__(f, request, params, model,
@@ -146,18 +173,15 @@
             (_('This year'), {'%s__year' % self.field_path: str(today.year)})
         )
 
-    def title(self):
-        return self.field.verbose_name
-
     def choices(self, cl):
         for title, param_dict in self.links:
             yield {'selected': self.date_params == param_dict,
                    'query_string': cl.get_query_string(param_dict, [self.field_generic]),
                    'display': title}
 
-FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec)
+FieldFilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec, False)
 
-class BooleanFieldFilterSpec(FilterSpec):
+class BooleanFieldFilterSpec(FieldFilterSpec):
     def __init__(self, f, request, params, model, model_admin,
                  field_path=None):
         super(BooleanFieldFilterSpec, self).__init__(f, request, params, model,
@@ -168,9 +192,6 @@
         self.lookup_val = request.GET.get(self.lookup_kwarg, None)
         self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None)
 
-    def title(self):
-        return self.field.verbose_name
-
     def choices(self, cl):
         for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')):
             yield {'selected': self.lookup_val == v and not self.lookup_val2,
@@ -181,12 +202,12 @@
                    'query_string': cl.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]),
                    'display': _('Unknown')}
 
-FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec)
+FieldFilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec, False)
 
 # This should be registered last, because it's a last resort. For example,
 # if a field is eligible to use the BooleanFieldFilterSpec, that'd be much
 # more appropriate, and the AllValuesFilterSpec won't get used for it.
-class AllValuesFilterSpec(FilterSpec):
+class AllValuesFilterSpec(FieldFilterSpec):
     def __init__(self, f, request, params, model, model_admin,
                  field_path=None):
         super(AllValuesFilterSpec, self).__init__(f, request, params, model,
@@ -204,9 +225,6 @@
         self.lookup_choices = \
             queryset.distinct().order_by(f.name).values(f.name)
 
-    def title(self):
-        return self.field.verbose_name
-
     def choices(self, cl):
         yield {'selected': self.lookup_val is None,
                'query_string': cl.get_query_string({}, [self.field_path]),
@@ -216,4 +234,4 @@
             yield {'selected': self.lookup_val == val,
                    'query_string': cl.get_query_string({self.field_path: val}),
                    'display': val}
-FilterSpec.register(lambda f: True, AllValuesFilterSpec)
+FieldFilterSpec.register(lambda f: True, AllValuesFilterSpec, False)
