diff --git a/django/contrib/admin/__init__.py b/django/contrib/admin/__init__.py
index f8e634e..77c1143 100644
--- a/django/contrib/admin/__init__.py
+++ b/django/contrib/admin/__init__.py
@@ -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
diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py
index 965b32b..8580255 100644
--- a/django/contrib/admin/filterspecs.py
+++ b/django/contrib/admin/filterspecs.py
@@ -17,27 +17,8 @@ import datetime
 
 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,7 +27,20 @@ class FilterSpec(object):
         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 = []
@@ -61,7 +55,42 @@ class FilterSpec(object):
             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__(request, params, model, model_admin)
+        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
+        else:
+            self.field_path = field_path
+
+    def title(self):
+        return self.field.verbose_name
+
+    @classmethod
+    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))
+
+    @classmethod
+    def create(cls, f, request, params, model, model_admin, field_path=None):
+        for test, factory in cls.field_filter_specs:
+            if test(f):
+                return factory(f, request, params, model, model_admin,
+                               field_path=field_path)
+
+        
+class RelatedFilterSpec(FieldFilterSpec):
     def __init__(self, f, request, params, model, model_admin,
                  field_path=None):
         super(RelatedFilterSpec, self).__init__(
@@ -117,11 +146,11 @@ class RelatedFilterSpec(FilterSpec):
                                    [self.lookup_kwarg]),
                    'display': EMPTY_CHANGELIST_VALUE}
 
-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 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,
@@ -132,9 +161,6 @@ class BooleanFieldFilterSpec(FilterSpec):
         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,
@@ -149,11 +175,11 @@ class BooleanFieldFilterSpec(FilterSpec):
                                    [self.lookup_kwarg]),
                    'display': _('Unknown')}
 
-FilterSpec.register(lambda f: isinstance(f, models.BooleanField)
+FieldFilterSpec.register(lambda f: isinstance(f, models.BooleanField)
                               or isinstance(f, models.NullBooleanField),
-                                 BooleanFieldFilterSpec)
+                                 BooleanFieldFilterSpec, 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,
@@ -172,9 +198,9 @@ class ChoicesFilterSpec(FilterSpec):
                                     {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,
@@ -205,9 +231,6 @@ class DateFieldFilterSpec(FilterSpec):
             (_('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,
@@ -216,14 +239,14 @@ class DateFieldFilterSpec(FilterSpec):
                                    [self.field_generic]),
                    'display': title}
 
-FilterSpec.register(lambda f: isinstance(f, models.DateField),
-                              DateFieldFilterSpec)
+FieldFilterSpec.register(lambda f: isinstance(f, models.DateField),
+                              DateFieldFilterSpec, 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,
@@ -245,9 +268,6 @@ class AllValuesFilterSpec(FilterSpec):
         self.lookup_choices = \
             queryset.distinct().order_by(f.name).values_list(f.name, flat=True)
 
-    def title(self):
-        return self.field.verbose_name
-
     def choices(self, cl):
         from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
         yield {'selected': self.lookup_val is None
@@ -276,4 +296,4 @@ class AllValuesFilterSpec(FilterSpec):
                                     [self.lookup_kwarg]),
                     'display': EMPTY_CHANGELIST_VALUE}
 
-FilterSpec.register(lambda f: True, AllValuesFilterSpec)
+FieldFilterSpec.register(lambda f: True, AllValuesFilterSpec, False)
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py
index 159afa4..4fdb8a1 100644
--- a/django/contrib/admin/validation.py
+++ b/django/contrib/admin/validation.py
@@ -3,6 +3,7 @@ from django.db import models
 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,
     HORIZONTAL, VERTICAL)
@@ -54,15 +55,37 @@ def validate(cls, model):
     # 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):
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 170d168..5ce6fc3 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -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.exceptions import SuspiciousOperation
@@ -59,22 +59,31 @@ class ChangeList(object):
             self.list_editable = ()
         else:
             self.list_editable = list_editable
+        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.model, 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)
@@ -166,6 +175,19 @@ class ChangeList(object):
             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):
         use_distinct = False
 
@@ -174,6 +196,7 @@ class ChangeList(object):
         for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR, TO_FIELD_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
@@ -210,6 +233,9 @@ class ChangeList(object):
                     "Filtering by %s not allowed" % key
                 )
 
+        # Let every filter spec modify the qs and params to its liking
+        qs = self.apply_filter_specs(qs, lookup_params)
+        
         # Apply lookup parameters from the query string.
         try:
             qs = qs.filter(**lookup_params)
