6

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?

Zags
  • 37,389
  • 14
  • 105
  • 140

2 Answers2

17

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
Zags
  • 37,389
  • 14
  • 105
  • 140
  • I utilized this solution to speed up my inline a little, but now I am having the issue where the elements in the inline are not showing their actual values in the select. I have a Model: Serial_Roll that has many Labels. Each Label has a Weight, Flavor, and Status. (These are all foreign keys) Each roll has 1000+ labels. I want my inline to show the current value of each FK, but also have the other choices in the select list. Do I need to change this? – Andy Poquette Jun 25 '17 at 18:00
  • @AndyPoquette All this does is override the choices of the FKs of the inline. It should preserve selecting the current value. If I have a bug in the posted solution, please let me know – Zags Jun 29 '17 at 22:03
  • I'm sure it's not you, but me. I opted for a different solution to my issue, but thank you! – Andy Poquette Jun 30 '17 at 22:29
  • @AndyPoquette what was the other solution? – James Parker Jul 30 '21 at 14:23
0

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, )
helpse
  • 1,518
  • 1
  • 18
  • 29