diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index bb51875..d35d963 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -28,8 +28,28 @@ get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
 class IncorrectLookupParameters(Exception):
     pass
 
+# Defaults for dbfield_formfield_overrides. ModelAdmin subclasses can change this
+# by adding to ModelAdmin.dbfield_formfield_overrides.
+    
+FORMFIELD_FOR_DBFIELD_DEFAULTS = {
+    models.DateTimeField: { 
+        'form_class': forms.SplitDateTimeField,
+        'widget': widgets.AdminSplitDateTime 
+    },
+    models.DateField:    {'widget': widgets.AdminDateWidget},
+    models.TimeField:    {'widget': widgets.AdminTimeWidget},
+    models.TextField:    {'widget': widgets.AdminTextareaWidget},
+    models.URLField:     {'widget': widgets.AdminURLFieldWidget},
+    models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget},
+    models.CharField:    {'widget': widgets.AdminTextInputWidget},
+    models.ImageField:   {'widget': widgets.AdminFileWidget},
+    models.FileField:    {'widget': widgets.AdminFileWidget},
+}
+
+
 class BaseModelAdmin(object):
     """Functionality common to both ModelAdmin and InlineAdmin."""
+    
     raw_id_fields = ()
     fields = None
     exclude = None
@@ -39,6 +59,10 @@ class BaseModelAdmin(object):
     filter_horizontal = ()
     radio_fields = {}
     prepopulated_fields = {}
+    formfield_overrides = {}
+    
+    def __init__(self):
+        self.formfield_overrides = dict(FORMFIELD_FOR_DBFIELD_DEFAULTS, **self.formfield_overrides)
     
     def formfield_for_dbfield(self, db_field, **kwargs):
         """
@@ -51,97 +75,87 @@ class BaseModelAdmin(object):
         # If the field specifies choices, we don't need to look for special
         # admin widgets - we just need to use a select widget of some kind.
         if db_field.choices:
-            if db_field.name in self.radio_fields:
-                # If the field is named as a radio_field, use a RadioSelect
+            return self.formfield_for_choice_field(db_field, **kwargs)
+        
+        # ForeignKey or ManyToManyFields
+        if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
+            # Combine the field kwargs with any options for formfield_overrides.
+            # Make sure the passed in **kwargs override anything in 
+            # formfield_overrides because **kwargs is more specific, and should
+            # always win.
+            if db_field.__class__ in self.formfield_overrides:
+                kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
+            
+            # Get the correct formfield.
+            if isinstance(db_field, models.ForeignKey):
+                formfield = self.formfield_for_foreignkey(db_field, **kwargs)
+            elif isinstance(db_field, models.ManyToManyField):
+                formfield = self.formfield_for_manytomany(db_field, **kwargs)
+            
+            # For non-raw_id fields, wrap the widget with a wrapper that adds
+            # extra HTML -- the "add other" interface -- to the end of the
+            # rendered output. formfield can be None if it came from a 
+            # OneToOneField with parent_link=True or a M2M intermediary.
+            if formfield and db_field.name not in self.raw_id_fields:
+                formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
+
+            return formfield
+                    
+        # If we've got overrides for the formfield defined, use 'em. **kwargs
+        # passed to formfield_for_dbfield override the defaults.
+        if db_field.__class__ in self.formfield_overrides:
+            kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
+            return db_field.formfield(**kwargs)
+                        
+        # For any other type of field, just call its formfield() method.
+        return db_field.formfield(**kwargs)
+        
+    def formfield_for_choice_field(self, db_field, **kwargs):
+        """
+        Get a form Field for a database Field that has declared choices.
+        """
+        # If the field is named as a radio_field, use a RadioSelect
+        if db_field.name in self.radio_fields:
+            # Avoid stomping on custom widget/choices arguments.
+            if 'widget' not in kwargs:
                 kwargs['widget'] = widgets.AdminRadioSelect(attrs={
                     'class': get_ul_class(self.radio_fields[db_field.name]),
                 })
+            if 'choices' not in kwargs:
                 kwargs['choices'] = db_field.get_choices(
                     include_blank = db_field.blank,
                     blank_choice=[('', _('None'))]
                 )
-                return db_field.formfield(**kwargs)
-            else:
-                # Otherwise, use the default select widget.
-                return db_field.formfield(**kwargs)
-        
-        # For DateTimeFields, use a special field and widget.
-        if isinstance(db_field, models.DateTimeField):
-            kwargs['form_class'] = forms.SplitDateTimeField
-            kwargs['widget'] = widgets.AdminSplitDateTime()
-            return db_field.formfield(**kwargs)
-        
-        # For DateFields, add a custom CSS class.
-        if isinstance(db_field, models.DateField):
-            kwargs['widget'] = widgets.AdminDateWidget
-            return db_field.formfield(**kwargs)
-        
-        # For TimeFields, add a custom CSS class.
-        if isinstance(db_field, models.TimeField):
-            kwargs['widget'] = widgets.AdminTimeWidget
-            return db_field.formfield(**kwargs)
-        
-        # For TextFields, add a custom CSS class.
-        if isinstance(db_field, models.TextField):
-            kwargs['widget'] = widgets.AdminTextareaWidget
-            return db_field.formfield(**kwargs)
-        
-        # For URLFields, add a custom CSS class.
-        if isinstance(db_field, models.URLField):
-            kwargs['widget'] = widgets.AdminURLFieldWidget
-            return db_field.formfield(**kwargs)
-        
-        # For IntegerFields, add a custom CSS class.
-        if isinstance(db_field, models.IntegerField):
-            kwargs['widget'] = widgets.AdminIntegerFieldWidget
-            return db_field.formfield(**kwargs)
-        
-        # For CommaSeparatedIntegerFields, add a custom CSS class.
-        if isinstance(db_field, models.CommaSeparatedIntegerField):
-            kwargs['widget'] = widgets.AdminCommaSeparatedIntegerFieldWidget
-            return db_field.formfield(**kwargs)
-        
-        # For TextInputs, add a custom CSS class.
-        if isinstance(db_field, models.CharField):
-            kwargs['widget'] = widgets.AdminTextInputWidget
-            return db_field.formfield(**kwargs)
+        return db_field.formfield(**kwargs)
+    
+    def formfield_for_foreignkey(self, db_field, **kwargs):
+        """
+        Get a form Field for a ForeignKey.
+        """
+        if db_field.name in self.raw_id_fields:
+            kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
+        elif db_field.name in self.radio_fields:
+            kwargs['widget'] = widgets.AdminRadioSelect(attrs={
+                'class': get_ul_class(self.radio_fields[db_field.name]),
+            })
+            kwargs['empty_label'] = db_field.blank and _('None') or None
         
-        # For FileFields and ImageFields add a link to the current file.
-        if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField):
-            kwargs['widget'] = widgets.AdminFileWidget
-            return db_field.formfield(**kwargs)
+        return db_field.formfield(**kwargs)
+
+    def formfield_for_manytomany(self, db_field, **kwargs):
+        """
+        Get a form Field for a ManyToManyField.
+        """
+        # If it uses an intermediary model, don't show field in admin.
+        if db_field.rel.through is not None:
+            return None
         
-        # For ForeignKey or ManyToManyFields, use a special widget.
-        if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
-            if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
-                kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel)
-            elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_fields:
-                kwargs['widget'] = widgets.AdminRadioSelect(attrs={
-                    'class': get_ul_class(self.radio_fields[db_field.name]),
-                })
-                kwargs['empty_label'] = db_field.blank and _('None') or None
-            else:
-                if isinstance(db_field, models.ManyToManyField):
-                    # If it uses an intermediary model, don't show field in admin.
-                    if db_field.rel.through is not None:
-                        return None
-                    elif db_field.name in self.raw_id_fields:
-                        kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
-                        kwargs['help_text'] = ''
-                    elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
-                        kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
-            # Wrap the widget's render() method with a method that adds
-            # extra HTML to the end of the rendered output.
-            formfield = db_field.formfield(**kwargs)
-            # Don't wrap raw_id fields. Their add function is in the popup window.
-            if not db_field.name in self.raw_id_fields:
-                # formfield can be None if it came from a OneToOneField with
-                # parent_link=True
-                if formfield is not None:
-                    formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
-            return formfield
+        if db_field.name in self.raw_id_fields:
+            kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel)
+            kwargs['help_text'] = ''
+        elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
+            kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
         
-        # For any other type of field, just call its formfield() method.
         return db_field.formfield(**kwargs)
     
     def _declared_fieldsets(self):
diff --git a/docs/ref/contrib/admin.txt b/docs/ref/contrib/admin.txt
index a50aa13..178e9c0 100644
--- a/docs/ref/contrib/admin.txt
+++ b/docs/ref/contrib/admin.txt
@@ -597,6 +597,47 @@ with an operator:
     Performs a full-text match. This is like the default search method but uses
     an index. Currently this is only available for MySQL.
 
+``formfield_overrides``
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This provides a quick-and-dirty way to override some of the
+:class:`~django.forms.Field` options for use in the admin.
+``formfield_overrides`` is a dictionary mapping a field class to a dict of
+arguments to pass to the field at construction time.
+
+Since that's a bit abstract, let's look at a concrete example. The most common
+use of ``formfield_overrides`` is to add a custom widget for a certain type of
+field. So, imagine we've written a ``RichTextEditorWidget`` that we'd like to
+use for large text fields instead of the default ``<textarea>``. Here's how we'd
+do that::
+
+    from django.db import models
+    from django.contrib import admin
+    
+    # Import our custom widget and our model from where they're defined
+    from myapp.widgets import RichTextEditorWidget
+    from myapp.models import MyModel
+    
+    class MyModelAdmin(admin.ModelAdmin):
+        formfield_overrides = {
+            models.TextField: {'widget': RichTextEditorWidget},
+        }
+        
+Note that the key in the dictionary is the actual field class, *not* a string.
+The value is another dictionary; these arguments will be passed to
+:meth:`~django.forms.Field.__init__`. See :ref:`ref-forms-api` for details.
+
+.. warning::
+
+    If you want to use a custom widget with a relation field (i.e.
+    :class:`~django.db.models.ForeignKey` or
+    :class:`~django.db.models.ManyToManyField`), make sure you haven't included
+    that field's name in ``raw_id_fields`` or ``radio_fields``.
+
+    ``formfield_overrides`` won't let you change the widget on relation fields
+    that have ``raw_id_fields`` or ``radio_fields`` set. That's because
+    ``raw_id_fields`` and ``radio_fields`` imply custom widgets of their own.
+
 ``ModelAdmin`` methods
 ----------------------
 
diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
index ddfc6c2..35d4b5b 100644
--- a/tests/regressiontests/admin_widgets/models.py
+++ b/tests/regressiontests/admin_widgets/models.py
@@ -5,6 +5,8 @@ from django.core.files.storage import default_storage
 
 class Member(models.Model):
     name = models.CharField(max_length=100)
+    birthdate = models.DateTimeField(blank=True, null=True)
+    gender = models.CharField(max_length=1, blank=True, choices=[('M','Male'), ('F', 'Female')])
 
     def __unicode__(self):
         return self.name
@@ -40,6 +42,14 @@ class Inventory(models.Model):
 
    def __unicode__(self):
       return self.name
+      
+class Event(models.Model):
+    band = models.ForeignKey(Band)
+    date = models.DateField(blank=True, null=True)
+    start_time = models.TimeField(blank=True, null=True)
+    description = models.TextField(blank=True)
+    link = models.URLField(blank=True)
+    min_age = models.IntegerField(blank=True, null=True)
 
 __test__ = {'WIDGETS_TESTS': """
 >>> from datetime import datetime
diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
new file mode 100644
index 0000000..1043e39
--- /dev/null
+++ b/tests/regressiontests/admin_widgets/tests.py
@@ -0,0 +1,99 @@
+from django import forms
+from django.contrib import admin
+from django.contrib.admin import widgets
+from unittest import TestCase
+import models
+
+class AdminFormfieldForDBFieldTests(TestCase):
+    """
+    Tests for correct behavior of ModelAdmin.formfield_for_dbfield
+    """
+
+    def assertFormfield(self, model, fieldname, widgetclass, **admin_overrides):
+        """
+        Helper to call formfield_for_dbfield for a given model and field name
+        and verify that the returned formfield is appropriate.
+        """
+        # Override any settings on the model admin
+        class MyModelAdmin(admin.ModelAdmin): pass
+        for k in admin_overrides:
+            setattr(MyModelAdmin, k, admin_overrides[k])
+        
+        # Construct the admin, and ask it for a formfield
+        ma = MyModelAdmin(model, admin.site)
+        ff = ma.formfield_for_dbfield(model._meta.get_field(fieldname))
+        
+        # "unwrap" the widget wrapper, if needed
+        if isinstance(ff.widget, widgets.RelatedFieldWidgetWrapper):
+            widget = ff.widget.widget
+        else:
+            widget = ff.widget
+        
+        # Check that we got a field of the right type
+        self.assert_(
+            isinstance(widget, widgetclass), 
+            "Wrong widget for %s.%s: expected %s, got %s" % \
+                (model.__class__.__name__, fieldname, widgetclass, type(widget))
+        )
+            
+        # Return the formfield so that other tests can continue
+        return ff
+    
+    def testDateField(self):
+        self.assertFormfield(models.Event, 'date', widgets.AdminDateWidget)
+        
+    def testDateTimeField(self):
+        self.assertFormfield(models.Member, 'birthdate', widgets.AdminSplitDateTime)
+        
+    def testTimeField(self):
+        self.assertFormfield(models.Event, 'start_time', widgets.AdminTimeWidget)
+
+    def testTextField(self):
+        self.assertFormfield(models.Event, 'description', widgets.AdminTextareaWidget)
+    
+    def testURLField(self):
+        self.assertFormfield(models.Event, 'link', widgets.AdminURLFieldWidget)
+
+    def testIntegerField(self):
+        self.assertFormfield(models.Event, 'min_age', widgets.AdminIntegerFieldWidget)
+        
+    def testCharField(self):
+        self.assertFormfield(models.Member, 'name', widgets.AdminTextInputWidget)
+        
+    def testFileField(self):
+        self.assertFormfield(models.Album, 'cover_art', widgets.AdminFileWidget)
+        
+    def testForeignKey(self):
+        self.assertFormfield(models.Event, 'band', forms.Select)
+        
+    def testRawIDForeignKey(self):
+        self.assertFormfield(models.Event, 'band', widgets.ForeignKeyRawIdWidget,
+                             raw_id_fields=['band'])
+    
+    def testRadioFieldsForeignKey(self):
+        ff = self.assertFormfield(models.Event, 'band', widgets.AdminRadioSelect, 
+                                  radio_fields={'band':admin.VERTICAL})
+        self.assertEqual(ff.empty_label, None)
+        
+    def testManyToMany(self):
+        self.assertFormfield(models.Band, 'members', forms.SelectMultiple)
+    
+    def testRawIDManyTOMany(self):
+        self.assertFormfield(models.Band, 'members', widgets.ManyToManyRawIdWidget,
+                             raw_id_fields=['members'])
+    
+    def testFilteredManyToMany(self):
+        self.assertFormfield(models.Band, 'members', widgets.FilteredSelectMultiple,
+                             filter_vertical=['members'])
+    
+    def testFormfieldOverrides(self):
+        self.assertFormfield(models.Event, 'date', forms.TextInput, 
+                             formfield_overrides={'widget': forms.TextInput})
+                             
+    def testFieldWithChoices(self):
+        self.assertFormfield(models.Member, 'gender', forms.Select)
+        
+    def testChoicesWithRadioFields(self):
+        self.assertFormfield(models.Member, 'gender', widgets.AdminRadioSelect, 
+                             radio_fields={'gender':admin.VERTICAL})
+        
\ No newline at end of file
