Python for Power Systems

A blog for power systems engineers to learn Python.

Add a Foreign Key to forms.Form

How do you get that standard ForeignKey select box with the “+” add another icon next to it on your form? This post will take you through the steps that I took to get the ForeignKey working like it does in the Django admin.

How does the Django admin create a ForeignKey select field

Django creates a forms.ModelChoiceField with queryset and to_field_name arguments as the default form field for a foreignkey. To see for yourself, have a look at django.db.models.fields.related.py under the ForeignKey class and method formfield here.

Now we know to use the ModelChoiceField. But what about those arguments queryset and to_field_name, and how does the “+” add icon appear?

The queryset argument should be a queryset or manager for the model your ForeignKey is to. For the following example:

1
2
class SpaceMen(models.Model):
  company = models.ForeignKey('SpaceCompany')

We are trying to build a form.Form with a ForeignKey type field to SpaceCompany we might use:

1
queryset = SpaceCompany._default_manager

I don’t know what to_field_name does. So I’ve left it out of this discussion. Don’t worry though, to_field_name, will appear later on.

How Django admin adds that “+” add icon

Django actually replaces the default select widget on the ModelChoiceField with its own django.contrib.admin.widgets.RelatedFieldWidgetWrapper. This swap occurs in django.contrib.admin.options near the top of the file in formfield_for_dbfield method of the BaseModelAdmin class. This code is interesting for two reasons:

  • It checks if the current user has permission to add a new SpaceCompany
  • The RelatedFieldWidgetWrapper requires knowledge of the admin_site

But where can I make this widget swap occur? and how do I get an instance of admin_site?

This will largely depend on how and why you are using the forms.Form in the first place. I was using the forms.Form as one of the FormWizard’s forms. So in render_template I was able to create a hook that checked the form for any ModelChoice fields and did the switch, and checked for permissions. I had a copy of the admin_site on my FormWizard thanks to this article onFormWizard in the Django admin. If you were not using ModelChoiceField in the admin, then you probably don’t need that “+” add another icon, because it is created using the reverse of admin_site.name amongst other things (more details in django.contrib.admin.widgets.py class RelatedFieldWidgetWrapper)