diff options
| author | Jacob Kaplan-Moss <jacob@jacobian.org> | 2009-01-16 15:32:31 +0000 |
|---|---|---|
| committer | Jacob Kaplan-Moss <jacob@jacobian.org> | 2009-01-16 15:32:31 +0000 |
| commit | f212b24b6469b66424354bf970f3051df180b88d (patch) | |
| tree | 379b8102f38ddde424cb0005cf2f468d409f691e | |
| parent | d579e716fef9f06f04861815cf949630d8633271 (diff) | |
Cleaned up and refactored `ModelAdmin.formfield_for_dbfield`:
* The new method uses an admin configuration option (`formfield_overrides`); this makes custom admin widgets especially easy.
* Refactored what was left of `formfield_for_dbfield` into a handful of smaller methods so that it's easier to hook in and return custom fields where needed.
* These `formfield_for_*` methods now pass around `request` so that you can easily modify fields based on request (as in #3987).
Fixes #8306, #3987, #9148.
Thanks to James Bennet for the original patch; Alex Gaynor and Brian Rosner also contributed.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@9760 bcc190cf-cafb-0310-a4f2-bffc1f526a37
| -rw-r--r-- | django/contrib/admin/options.py | 182 | ||||
| -rw-r--r-- | docs/ref/contrib/admin.txt | 58 | ||||
| -rw-r--r-- | tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml | 43 | ||||
| -rw-r--r-- | tests/regressiontests/admin_widgets/models.py | 25 | ||||
| -rw-r--r-- | tests/regressiontests/admin_widgets/tests.py | 112 | ||||
| -rw-r--r-- | tests/regressiontests/admin_widgets/urls.py | 7 | ||||
| -rw-r--r-- | tests/regressiontests/admin_widgets/widgetadmin.py | 22 | ||||
| -rw-r--r-- | tests/urls.py | 3 |
8 files changed, 369 insertions, 83 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index bb51875fba..ed41c7ff64 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -13,6 +13,7 @@ from django.shortcuts import get_object_or_404, render_to_response from django.utils.functional import update_wrapper from django.utils.html import escape from django.utils.safestring import mark_safe +from django.utils.functional import curry from django.utils.text import capfirst, get_text_list from django.utils.translation import ugettext as _ from django.utils.encoding import force_unicode @@ -28,8 +29,28 @@ get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') class IncorrectLookupParameters(Exception): pass +# Defaults for formfield_overrides. ModelAdmin subclasses can change this +# by adding to ModelAdmin.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 +60,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): """ @@ -47,101 +72,92 @@ class BaseModelAdmin(object): If kwargs are given, they're passed to the form Field's constructor. """ + request = kwargs.pop("request", None) # 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, request, **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, request, **kwargs) + elif isinstance(db_field, models.ManyToManyField): + formfield = self.formfield_for_manytomany(db_field, request, **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, request=None, **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, request=None, **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, request=None, **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): @@ -292,7 +308,7 @@ class ModelAdmin(BaseModelAdmin): "form": self.form, "fields": fields, "exclude": exclude + kwargs.get("exclude", []), - "formfield_callback": self.formfield_for_dbfield, + "formfield_callback": curry(self.formfield_for_dbfield, request=request), } defaults.update(kwargs) return modelform_factory(self.model, **defaults) @@ -837,7 +853,7 @@ class InlineModelAdmin(BaseModelAdmin): "fk_name": self.fk_name, "fields": fields, "exclude": exclude + kwargs.get("exclude", []), - "formfield_callback": self.formfield_for_dbfield, + "formfield_callback": curry(self.formfield_for_dbfield, request=request), "extra": self.extra, "max_num": self.max_num, } diff --git a/docs/ref/contrib/admin.txt b/docs/ref/contrib/admin.txt index a50aa13da9..b004e17bfd 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 ---------------------- @@ -675,6 +716,23 @@ Notice the wrapped view in the fifth line above:: This wrapping will protect ``self.my_view`` from unauthorized access. +``formfield_for_foreignkey(self, db_field, request, **kwargs)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``formfield_for_foreignkey`` method on a ``ModelAdmin`` allows you to +override the default formfield for a foreign key field. For example, to +return a subset of objects for this foreign key field based on the user:: + + class MyModelAdmin(admin.ModelAdmin): + def formfield_for_foreignkey(self, db_field, request, **kwargs): + if db_field.name == "car": + kwargs["queryset"] = Car.object.filter(owner=request.user) + return db_field.formfield(**kwargs) + return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) + +This uses the ``HttpRequest`` instance to filter the ``Car`` foreign key field +to only the cars owned by the ``User`` instance. + ``ModelAdmin`` media definitions -------------------------------- diff --git a/tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml b/tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml new file mode 100644 index 0000000000..b851562a1c --- /dev/null +++ b/tests/regressiontests/admin_widgets/fixtures/admin-widgets-users.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + <object pk="100" model="auth.user"> + <field type="CharField" name="username">super</field> + <field type="CharField" name="first_name">Super</field> + <field type="CharField" name="last_name">User</field> + <field type="CharField" name="email">super@example.com</field> + <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field> + <field type="BooleanField" name="is_staff">True</field> + <field type="BooleanField" name="is_active">True</field> + <field type="BooleanField" name="is_superuser">True</field> + <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field> + <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field> + <field to="auth.group" name="groups" rel="ManyToManyRel"></field> + <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field> + </object> + <object pk="101" model="auth.user"> + <field type="CharField" name="username">testser</field> + <field type="CharField" name="first_name">Add</field> + <field type="CharField" name="last_name">User</field> + <field type="CharField" name="email">auser@example.com</field> + <field type="CharField" name="password">sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158</field> + <field type="BooleanField" name="is_staff">True</field> + <field type="BooleanField" name="is_active">True</field> + <field type="BooleanField" name="is_superuser">False</field> + <field type="DateTimeField" name="last_login">2007-05-30 13:20:10</field> + <field type="DateTimeField" name="date_joined">2007-05-30 13:20:10</field> + <field to="auth.group" name="groups" rel="ManyToManyRel"></field> + <field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field> + </object> + + <object pk="1" model="admin_widgets.car"> + <field to="auth.user" name="owner" rel="ManyToOneRel">100</field> + <field type="CharField" name="make">Volkswagon</field> + <field type="CharField" name="model">Passat</field> + </object> + <object pk="2" model="admin_widgets.car"> + <field to="auth.user" name="owner" rel="ManyToOneRel">101</field> + <field type="CharField" name="make">BMW</field> + <field type="CharField" name="model">M3</field> + </object> + +</django-objects>
\ No newline at end of file diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py index ddfc6c2cb6..345d73b0ea 100644 --- a/tests/regressiontests/admin_widgets/models.py +++ b/tests/regressiontests/admin_widgets/models.py @@ -2,9 +2,12 @@ from django.conf import settings from django.db import models from django.core.files.storage import default_storage +from django.contrib.auth.models import User 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 +43,28 @@ 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) + +class Car(models.Model): + owner = models.ForeignKey(User) + make = models.CharField(max_length=30) + model = models.CharField(max_length=30) + + def __unicode__(self): + return u"%s %s" % (self.make, self.model) + +class CarTire(models.Model): + """ + A single car tire. This to test that a user can only select their own cars. + """ + car = models.ForeignKey(Car) __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 0000000000..2794b3fb80 --- /dev/null +++ b/tests/regressiontests/admin_widgets/tests.py @@ -0,0 +1,112 @@ +from django import forms +from django.contrib import admin +from django.contrib.admin import widgets +from unittest import TestCase +from django.test import TestCase as DjangoTestCase +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), request=None) + + # "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}) + + +class AdminFormfieldForDBFieldWithRequestTests(DjangoTestCase): + fixtures = ["admin-widgets-users.xml"] + + def testFilterChoicesByRequestUser(self): + """ + Ensure the user can only see their own cars in the foreign key dropdown. + """ + self.client.login(username="super", password="secret") + response = self.client.get("/widget_admin/admin_widgets/cartire/add/") + self.assert_("BMW M3" not in response.content) + self.assert_("Volkswagon Passat" in response.content) diff --git a/tests/regressiontests/admin_widgets/urls.py b/tests/regressiontests/admin_widgets/urls.py new file mode 100644 index 0000000000..af73d5351d --- /dev/null +++ b/tests/regressiontests/admin_widgets/urls.py @@ -0,0 +1,7 @@ + +from django.conf.urls.defaults import * +import widgetadmin + +urlpatterns = patterns('', + (r'^', include(widgetadmin.site.urls)), +) diff --git a/tests/regressiontests/admin_widgets/widgetadmin.py b/tests/regressiontests/admin_widgets/widgetadmin.py new file mode 100644 index 0000000000..bc28dac86a --- /dev/null +++ b/tests/regressiontests/admin_widgets/widgetadmin.py @@ -0,0 +1,22 @@ +""" + +""" +from django.contrib import admin + +import models + +class WidgetAdmin(admin.AdminSite): + pass + + +class CarTireAdmin(admin.ModelAdmin): + def formfield_for_foreignkey(self, db_field, request, **kwargs): + if db_field.name == "car": + kwargs["queryset"] = models.Car.objects.filter(owner=request.user) + return db_field.formfield(**kwargs) + return super(CarTireAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) + +site = WidgetAdmin() + +site.register(models.Car) +site.register(models.CarTire, CarTireAdmin) diff --git a/tests/urls.py b/tests/urls.py index b6c9d6e030..43806de920 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -24,6 +24,9 @@ urlpatterns = patterns('', # admin view tests (r'^test_admin/', include('regressiontests.admin_views.urls')), (r'^generic_inline_admin/', include('regressiontests.generic_inline_admin.urls')), + + # admin widget tests + (r'widget_admin/', include('regressiontests.admin_widgets.urls')), (r'^utils/', include('regressiontests.utils.urls')), |
