RelatedFieldWidgetWrapper

The RelatedFieldWidgetWrapper (found in django.contrib.admin.widgets) is used in the Admin pages to include the capability on a Foreign Key control to add a new related record. (In English: puts the little green plus sign to the right of the control.) In a new application of the FilteredSelectMultiple widget I discussed recently, I needed to add this functionality.

This wrapper is a little complicated, but it didn’t take too much Google searching to find an example.  A Google Code file from something called Crimson Online helped me understand the concept.

The wrapper takes three required parameters plus one optional:

  1. The widget to be wrapped. In my case the FilteredSelectMultiple widget
  2. A relation that defines the two models involved
  3. A reference to the admin site
  4. The Boolean can_add_related (optional)

For the first parameter, I used the widget with its parameters:

FilteredSelectMultiple(('entities'),False,)

The relation confused me at first, but the example made it easy.  The model linked to the form, in this case Item, provides it.

Item._meta.get_field('entities').rel

The third parameter, the admin site object, is used along with the relation to determine if the user has permission to add this related model.  The example used django.contrib.admin.site, which I found doesn’t work.  Instead, I captured the admin_site value from the ModelAdmin class in my admin.py and set it as a variable for the form.  Then I referenced that variable in the \__init__ method of the form to include it in the widget.  This means that the widget has to be assigned within the __init__ method of the form.

self.admin_site

The fourth parameter can be used to override the permission check performed by the widget.  However, when a user without the permission to add clicks that little green plus sign, an ugly 500 error is returned.

Here is the code used:

in admin.py:

class ItemAdmin(admin.ModelAdmin):
    form = ItemAdminForm

    def __init__(self, model, admin_site):
        super(ItemAdmin,self).__init__(model,admin_site)
        self.form.admin_site = admin_site # capture the admin_site

admin.site.register(Item, ItemAdmin)

in forms.py

from django.contrib.admin.widgets import FilteredSelectMultiple, RelatedFieldWidgetWrapper

class ItemAdminForm(forms.ModelForm):
    entities = forms.ModelMultipleChoiceField(queryset=None,label=('Select Entities'),)

    def __init__(self, *args, **kwargs):
        super(ItemAdminForm,self).__init__(*args, **kwargs)
        # set the widget with wrapper
        self.fields['entities'].widget = RelatedFieldWidgetWrapper(
                                                FilteredSelectMultiple(('entities'),False,),
                                                Item._meta.get_field('entities').rel,
                                                self.admin_site)
        self.fields['entities'].queryset = Entity.objects.all()

    class Media:
        ## media for the FilteredSelectMultiple widget
        css = {
            'all':('/media/css/widgets.css',),
        }
        # jsi18n is required by the widget
        js = ('/admin/jsi18n/',)

    class Meta:
        model = Item

UPDATE:

I’ve published a follow up post that covers using this wrapper outside of the admin application. See More RelatedFieldWidgetWrapper – My Very Own Popup.