How do I require an inline in the Django Admin?

Question:

I have the following admin setup so that I can add/edit a user and their profile at the same time.

class ProfileInline(admin.StackedInline):
    """
    Allows profile to be added when creating user
    """
    model = Profile


class UserProfileAdmin(admin.ModelAdmin):
    """
    Options for the admin interface
    """
    inlines = [ProfileInline]
    list_display = ['edit_obj', 'name', 'username', 'email', 'is_active',
        'last_login', 'delete_obj']
    list_display_links = ['username']
    list_filter = ['is_active']
    fieldsets = (
        (None, {
            'fields': ('first_name', 'last_name', 'email', 'username',
                'is_active', 'is_superuser')}),
        )
    ordering = ['last_name', 'first_name']
    search_fields = ['first_name', 'last_name']

admin.site.register(User, UserProfileAdmin)

The problem is I need two of the fields in the Profile inline form to be required when adding the user. The inline form doesn’t validate unless input is entered. Is there anyway to make the inline required, so that it can’t be left blank?

Asked By: AJ.

||

Answers:

You can probably do this, but you’ll have to get your hands dirty in the formset/inline code.

First of all, I think you want there to be always one form in the formset in this case, and never more than one, so you’ll want to set max_num=1 and extra=1 in your ProfileInline.

Your core problem is that BaseFormSet._construct_form passes empty_permitted=True to each “extra” (i.e. empty) form in the formset. This parameter tells the form to bypass validation if its unchanged. You just need to find a way to set empty_permitted=False for the form.

You can use your own BaseInlineFormset subclass in your inline, so that might help. Noticing that _construct_form takes **kwargs and allows that to override the kwargs passed to the individual Form instances, you could override _construct_forms in your Formset subclass and have it pass empty_permitted=False in every call to _construct_form. The downside there is that you’re relying on internal APIs (and you’d have to rewrite _construct_forms).

Alternatively, you could try overriding the get_formset method on your ProfileInline, and after calling the parent’s get_formset, manually poke at the form inside the returned formset:

def get_formset(self, request, obj=None, **kwargs):
    formset = super(ProfileInline, self).get_formset(request, obj, **kwargs)
    formset.forms[0].empty_permitted = False
    return formset

Play around and see what you can make work!

Answered By: Carl Meyer

I took Carl’s advice and made a much better implementation then the hack-ish one I mentioned in my comment to his answer. Here is my solution:

From my forms.py:

from django.forms.models import BaseInlineFormSet


class RequiredInlineFormSet(BaseInlineFormSet):
    """
    Generates an inline formset that is required
    """

    def _construct_form(self, i, **kwargs):
        """
        Override the method to change the form attribute empty_permitted
        """
        form = super(RequiredInlineFormSet, self)._construct_form(i, **kwargs)
        form.empty_permitted = False
        return form

And the admin.py

class ProfileInline(admin.StackedInline):
    """
    Allows profile to be added when creating user
    """
    model = Profile
    extra = 1
    max_num = 1
    formset = RequiredInlineFormSet


class UserProfileAdmin(admin.ModelAdmin):
    """
    Options for the admin interface
    """
    inlines = [ProfileInline]
    list_display = ['edit_obj', 'name', 'username', 'email', 'is_active',
        'last_login', 'delete_obj']
    list_display_links = ['username']
    list_filter = ['is_active']
    fieldsets = (
        (None, {
            'fields': ('first_name', 'last_name', 'email', 'username',
                'is_active', 'is_superuser')}),
        (('Groups'), {'fields': ('groups', )}),
    )
    ordering = ['last_name', 'first_name']
    search_fields = ['first_name', 'last_name']


admin.site.register(User, UserProfileAdmin)

This does exactly what I want, it makes the Profile inline formset validate. So since there are required fields in the profile form it will validate and fail if the required information isn’t entered on the inline form.

Answered By: AJ.

The easiest and most natural way to do that is via fomset clean():

class RequireOneFormSet(forms.models.BaseInlineFormSet):
    def clean(self):
        super().clean()
        if not self.is_valid():
            return
        if not self.forms or not self.forms[0].cleaned_data:
            raise ValidationError('At least one {} required'
                                  .format(self.model._meta.verbose_name))

class ProfileInline(admin.StackedInline):
    model = Profile
    formset =  RequireOneFormSet

(Inspired by this Matthew Flanagan’s snippet and Mitar’s comment below, tested to work in Django 1.11 and 2.0).

Answered By: mrts

Now with Django 1.7 you can use parameter min_num. You do not need class RequiredInlineFormSet anymore.

See https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.min_num

class ProfileInline(admin.StackedInline):
    """
    Allows profile to be added when creating user
    """
    model = Profile
    extra = 1
    max_num = 1
    min_num = 1 # new in Django 1.7


class UserProfileAdmin(admin.ModelAdmin):
    """
    Options for the admin interface
    """
    inlines = [ProfileInline]
    ...


admin.site.register(User, UserProfileAdmin)
Answered By: quick

You need to set min_num in inline and validate_min in formset.

https://docs.djangoproject.com/en/1.8/topics/forms/formsets/#validate-min

class SomeInline(admin.TabularInline):
    ...
    min_num = 1

    def get_formset(self, request, obj=None, **kwargs):
        formset = super().get_formset(request, obj=None, **kwargs)
        formset.validate_min = True
        return formset
Answered By: un1t

As of Django 3+
this is as simple as you like:

class EmployeeAddressMap(admin.StackedInline):
    model = EmployeeAddress
    min_num = 1
    max_num = 1
    can_delete = False #specified that this cannnot be removed

Happy coding

Answered By: Mahbub Alam

Mar, 2022 Update:

"min_num" can make inline fields required. (Inline fields are unrequired by default and 3 unrequired inline fields are displayed by default)

So, if you want 2 required inline fields, set "min_num = 2" as shown below and in this case below, 2 required and 3 unrequired inline fields are displayed and you can add more unrequired inline fields but you cannot add required inline fields in the form:

from django.contrib import admin
from .models import Profile, UserProfile

class ProfileInline(admin.TabularInline):
    model = Profile
    min_num = 2  # 2 required inline fields displayed
                 # 3 unrequired inline fields displayed by default

@admin.register(UserProfile)
class UserProfileAdmin(admin.ModelAdmin):
    inlines = [ProfileInline]

In addition, you can use "extra" to set the number of unrequired fields displayed and you can use "max_num" to set the maximum number of the total inline fields including required and unrequired inline fields. So, in this case below, the maximum number of the total inline fields are 10 and 3 required and 2 unrequired inline fields are displayed and you can add 5 more unrequired inline fields as a maximum but you cannot add required inline fields in the form:

from django.contrib import admin
from .models import Profile, UserProfile

class ProfileInline(admin.TabularInline):
    model = Profile
    min_num = 3  # 3 required inline fields displayed
    extra = 2    # 2 unrequired inline fields displayed
    max_num = 10 # 10 inline fields as a maximum

@admin.register(UserProfile)
class UserProfileAdmin(admin.ModelAdmin):
    inlines = [ProfileInline]
Answered By: Kai – Kazuya Ito