Fixing Django’s Admin Inlines

I’ve written before about using Django’s admin in production. It’s highly useful, especially once InlineModelAdmin configurations are mixed in. Let’s say you have a basic one-to-many relationship like the one defined below.

class Recipe (models.Model):
    """Handles holding together a bunch of Ingredients."""
    name = models.CharField(max_length=255)
    # ...

class Ingredient (models.Model):
    """Holds a quantity of items for use in a Recipe"""
    recipe = models.ForeignKey("Recipe")
    quantity = models.DecimalField(default=D("1.00"), decimal_places=4,
        max_digits=20)
    # ....

As of Django 1.7 the simple admin configuration for both of these models is to subclass ModelAdmin and plug-and-go. The nice extra step is to quickly configure an inline instance so that Ingredients can be managed directly under a Recipe.

Model Diagram

class IngredientInlineAdmin (admin.TabularInline):
    """Inline configuration for Django's admin on the Ingredient model."""
    model = Ingredient


class RecipeAdmin (admin.ModelAdmin):
    """Configuration for Django's admin on the Recipe model."""
    inlines = [ IngredientInlineAdmin ]

One feature of inlines is the ability to specify a number of extra forms that should appear using the extra property. This will cause the form to always have the determined number of “extra” blank forms to fill out. This is a helpful feature for just-added models and an unfortunate nuisance if you’re not adding a number of relations every time since the forms will trip validation errors unless deleted.

class IngredientInlineAdmin (admin.TabularInline):
    """Inline configuration for Django's admin on the Ingredient model."""
    model = Ingredient
    extra = 8

There is a little known hook for dynamically altering the number of extra forms in an InlineAdmin. This is exposed in the get_extra override.

class IngredientInlineAdmin (admin.TabularInline):
    """Inline configuration for Django's admin on the Ingredient model."""
    model = Ingredient
    extra = 8

    def get_extra (self, request, obj=None, **kwargs):
        """Dynamically sets the number of extra forms. 0 if the related object
        already exists or the extra configuration otherwise."""
        if obj:
            # Don't add any extra forms if the related object already exists.
            return 0
        return self.extra

Now for adding objects there will be 8 blank forms for new ingredients. Editing an existing object will not add those additional forms leaving us with a cleaner admin experience.

Comment on reddit’s /r/django/.

written October 19th, 2014

October 2014

Can’t find what you’re looking for? Try hitting the home page or viewing all archives.