Zags
Zags

Reputation: 41180

Django Admin Page with Inline Model Loads Very Slowly

I have a Django admin page for a model with a single inline model. When the inline model has many items (like 75), the page loads very slowly (on the order of 30 seconds). This is true even if I exclude all of the fields on the inline model, having it just render the name. Removing the inline model causes the page to load very quickly (in seconds).

How can I have this page load faster?

Upvotes: 6

Views: 6950

Answers (2)

helpse
helpse

Reputation: 1548

Check out my solution here: https://stackoverflow.com/a/51391915/1496567

My admin classes look like follows:

class ItemBazarInlineAdmin(AdminInlineWithSelectRelated):
    model = ItemBazar

    list_select_related = (
        'alumno_item__alumno__estudiante__profile',
        'alumno_item__item__anio_lectivo',
        'comprobante__forma_pago',
    )


class ComprobanteAdmin(AdminWithSelectRelated):
    list_display = ('__str__', 'total', 'estado', 'fecha_generado', 'forma_pago', 'tipo', )
    list_filter = ('estado', 'forma_pago', )

    list_select_related = ('forma_pago', )
    inlines = (ItemServicioInlineAdmin, )

Upvotes: 0

Zags
Zags

Reputation: 41180

There are two things you can do to speed up the loading of this page.

  1. Set DEBUG = False.
  2. Cache the database queries used in the rendering of the inline model.

DEBUG = False

The reason that the page loads slowly is that the Django admin is rendering a subtemplate for every instance of the inline model. With 75 instances of the inline model, you are rendering 75 extra templates, even if there is minimal content in them. While rendering a template is normally very fast, if you have DEBUG = True, you incur extra overhead for every template you render because Django is injecting extra context for its debug-mode error page (this is about 0.4 seconds per template on my system). This extra overhead is normally not noticeable because you are normally only rendering a handful of templates for a single page. When you render 75 templates, however, this adds up to a noticeable delay (75 * 0.4 seconds = 30 seconds).


Caching database queries

At least as of Django 1.10, the inline model will make any necessary database queries to render it for each instance of the inline model. If your inline model has any foreign keys on the inline model, this can pretty inefficient if you have a slow database connection. The technique is to run database queries for the choices of these foreign keys when you initialize the instance of the inline class (using get_formsets_with_inlines), and then replace the choices of these fields on the inline with the cached values to prevent repeating this database query (using formfield_for_foreignkey).

class Manufacturer(models.Model):
    ...

class OperatingSystem(models.Model):
    ...

class Computer(models.Model):
    manufacturer = models.ForeignKey(Manufarturer)
    owner = models.ForeignKey(User)
    operating_system = models.ForeignKey(OperatingSystem)
    ...


class ComputerAdmin(admin.StackedInline):
    model = Computer

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        field = super(ComputerAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
        if db_field.name == "owner" and hasattr(self, "cached_owners"):
            field.choices = self.cached_owners
        elif db_field.name == "manufacturer" and hasattr(self, "cached_manufacturers"):
            field.choices = self.cached_manufacturers
        elif db_field.name == "operating_system" and hasattr(self, "cached_operating_systems"):
            field.choices = self.cached_operating_systems
        return field


class ManufacturerAdmin(admin.ModelAdmin):
    inlines = (ComputerAdmin,)

    def get_formsets_with_inlines(self, request, obj=None):
        for inline in self.get_inline_instances(request, obj):
            inline.cached_owners = [(i.pk, str(i)) for i in User.objects.all()]
            inline.cached_manufacturers = [(i.pk, str(i)) for i in Manufacturer.objects.all()]
            inline.cached_operating_systems = [(i.pk, str(i)) for i in OperatingSystem.objects.all()]
            yield inline.get_formset(request, obj), inline

Upvotes: 17

Related Questions