34. Django-Admin Integration

One of the goals of django-formset is to offer a flexible and easy to use library for manipulating formsets and to offer widgets with a way better usability than the builtin HTML form fields. During the development, the Django-Admin was an inspiration for many features of this library. Developers may recognize the similarities between the StackedInline- of the Django-Admin and the Form Collections of django-formset. The Dual Selector Widget is another such example, which the Django-Admin is referring to as filter horizontal.

34.1. Declaration over Configuration

The principal view class of the Django-Admin is the ModelAdmin. This class is responsible for converting a Django model into a form and optionally into extra fieldsets. It also provides a way to handle one level of related models, which are displayed as inlines. In a minimalistic configuration, a developer only needs to declare the model. However, this minimalism comes at a price because developers are bound by the constraints imposed by configuration options of that class.

django-formset provides an alternative class formset.admin.ModelAdmin to be used as a replacement for Django’s ModelAdmin. This class requires just one mandatory attribute, either form or collection_class. The form attribute must refer to an instance of a subclass of formset.forms.ModelForm. The collection_class attribute must refer to an instance of a subclass of formset.collection.CollectionForm. The attributes collection_class and form are mutually exclusive and must not be used together on the same instance of a ModelAdmin class.

The other attributes offered by Django’s ModelAdmin, have no effect if used with the implementation of django-formset. Instead of using various configuration directives, a declarative approach is used. This means that developers must create the structure of their forms using the components provided by django-formset. The big advantage of this approach is that such form-, fieldset- or collection declarations can also be used in normal Django views.

Here is a recipe on how to replace these configuration directives:

ModelAdmin.fields

The fields attribute of the Django-Admin is used to configure the fields to be displayed in the form. In django-formset this information is redundant, because the used form already declares the wanted fields.

ModelAdmin.fieldsets

In django-formset, fieldsets are declared using the formset.fieldset.Fieldset class. This allows developers to nest fieldsets and to reuse the same fieldset in multiple places. If rendered in the Django-Admin, a <fieldset> uses the <legend> tag to display the fieldset title. More on this can be found in section Fieldsets.

ModelAdmin.filter_horizontal

Adding a field name to the filter_horizontal attribute of the Django-Admin, renders a dual listbox for that field. This is used to create a widget for a many-to-many relationship between two models. django-formset provides a similar feature, with formset.widgets.DualSelector and alternatively with formset.widgets.SelectizeMultiple. These widgets have to be specified in the widget attribute of the form’s field classes. More on this can be found in section Dual Selector Widget.

ModelAdmin.filter_vertical

django-formset does not provide a vertical dual listbox. And sincerely, I never saw any implementation using it, nor do I see any need for such a widget.

ModelAdmin.form

By default, the Django-Admin creates a ModelForm dynamically for your model. With this directive developers can specify a custom form class and override this behaviour. In django-formset, this now is mandatory. So every instance of formset.admin.ModelAdmin must specify either a form class or a collection_class.

ModelAdmin.formfield_overrides

This attribute is used to override the default form field or widget of a model field. In django-formset, this is done by specifying the widget in the widget attribute of the form’s field classes or by overriding the field in the class itself.

ModelAdmin.inlines

The Django-Admin provides a way to edit related models in a formset. This is done by using the inlines attribute containing a list of subclasses of django.contrib.admin.TabularInline or django.contrib.admin.StackedInline. In django-formset, this is done by using one or more Form Collections. Since FormCollection-s can be nested deeply, they are rendered using a surrounding border each, so that for the user it is clear which subform and field belongs to which collection.

list_display and other related attributes

The list_display attribute of the Django-Admin is used by the list-view of a model. Its behaviour remains unchanged in django-formset. The same applies to the attributes list_display_links, list_editable, list_filter, list_max_show_all, list_per_page, list_select_related, ordering, paginator and preserve_filters, search_fields, search_help_text, show_full_result_count, sortable_by and view_on_site.

ModelAdmin.prepopulated_fields

The prepopulated_fields attribute of the Django-Admin is used to prepopulate a field with the value of another field. This usually is used to generate the value for a slug field. In django-formset, we can achieve the same effect by using the widget Slug Input Field on any given text input field.

ModelAdmin.radio_fields

The radio_fields attribute of the Django-Admin is used to render a radio button for a ChoiceField. In django-formset, this is done by using the widget django.forms.widgets.`RadioSelect on the corresponding field. There, the orientation of the radio buttons is determined by the attribute max_options_per_line used in the applied Form Renderer.

ModelAdmin.autocomplete_fields

The autocomplete_fields attribute of the Django-Admin is used to render a Select2 widget for a ForeignKey or ManyToManyField. In django-formset, this is done by using the widgets Selectize Widget, Selectize Multiple Widget or Dual Selector Widget when declaring the field.

ModelAdmin.raw_id_fields

The raw_id_fields attribute of the Django-Admin is used to render a ForeignKey or ManyToManyField as a text input field. In django-formset, there is no recommended equivalent for this widget, but it can be implemented using a TextInput or NumberInput widget when declaring the form field.

ModelAdmin.readonly_fields

The readonly_fields attribute of the Django-Admin is used to specify a list of fields as read-only. In django-formset, this is done by adding the given field names to the attribute disabled_fields in the Meta option when declaring the form. More on this can be found in section Extra Meta options.

ModelAdmin.save_as

The save_as attribute of the Django-Admin is used to display a button to save the current object as a new one. This behaviour remains unchanged in django-formset.

ModelAdmin.save_as_continue

The save_as_continue attribute of the Django-Admin is used in combination with save_as. If both are true, after saving a new object the user is redirected to the list view for that model.

34.2. Example using a ModelForm

Every class inheriting from formset.forms.ModelForm compatible with any django-formset aware view, can also be used in any class inheriting from formset.admin.ModelAdmin.

Say, we have a Django model to describe a person, with various fields for personal data including a FileField to upload a picture for an avatar. The ModelForm to edit this model may look like this:

form.py
     from django.forms.widgets import RadioSelect
     from formset.forms import ModelForm
     from formset.widgets import DatePicker, UploadedFileInput
     from .models import PersonModel

     class ModelPersonForm(ModelForm):
         class Meta:
             model = PersonModel
             fields = [
                 'full_name', 'avatar', 'birth_date', 'is_active',
                 'gender', 'continent', 'weight', 'height'
             ]
             widgets = {
                 'avatar': UploadedFileInput,
                 'gender': RadioSelect,
                 'birth_date': DatePicker,
             }

This ModelForm can be used directly in the Django Admin using a replacement for the Django ModelAdmin class.

admin.py
     from django.contrib import admin
     from formset.admin import ModelAdmin
     from .models import PersonModel
     from .forms import ModelPersonForm

     @admin.register(PersonModel)
     class PersonAdmin(ModelAdmin):
         form = ModelPersonForm

The editor rendered from this class will look like this:

Django Admin Person Model

34.3. Example using a FormCollection

Every class inheriting from formset.collection.FormCollection can also be used in any class inheriting from formset.admin.ModelAdmin.

Say, we have a Django model to describe a company. Each department has a foreign key to model Company and each team has a foreign key to model Department. A FormCollection to explain this setup can be found in One-to-Many Relations. In django-formset this collection of forms now can be used in the Django Admin:

admin.py
     from django.contrib import admin
     from formset.admin import ModelAdmin
     from .models import Company
     from .collections import CompanyCollection

     @admin.register(Company)
     class CompanyAdmin(ModelAdmin):
         collection_class = CompanyCollection
         save_as = True

The editor rendered from this class will look like this:

Django Company Form Collection

Some additional CSS has been added to this Django Admin to add borders around the given collections. This is to make the form collection more consistent with the logical structure of the model. Otherwise, the form collections would be rendered as a flat structure and this would make it hard to find out which teams belong to which department.

Note

The demo used to render these Django-Admin views is available when this testapp is started supporting the admin.