Django admin inline: select_related

耗尽温柔 提交于 2019-11-30 04:11:54

The formset solution does work for me, but with a slightly different approach:

class MyInlineFormset(BaseInlineFormSet):
    def __init__(self, *args, **kwargs):
        super(MyInlineFormset, self).__init__(*args, **kwargs)
        self.queryset = self.queryset.prefetch_related('priced_product__product')

The BaseInlineFormSet class filters the queryset for you, and you need to take that filtered queryset and add the prefetch. With your formset implementation (the all() queryset) you get unrelated ComingProduct objects and it probably takes much too long to render. When it's the filtered queryset it renders very quickly.

You will find this approach very useful:

project/admin.py

from django.contrib import admin
from django.contrib.admin.options import BaseModelAdmin
from django.db.models.constants import LOOKUP_SEP


class AdminBaseWithSelectRelated(BaseModelAdmin):
    """
    Admin Base using list_select_related for get_queryset related fields
    """
    list_select_related = []

    def get_queryset(self, request):
        return super(AdminBaseWithSelectRelated, self).get_queryset(request).select_related(*self.list_select_related)

    def form_apply_select_related(self, form):
        for related_field in self.list_select_related:
            splitted = related_field.split(LOOKUP_SEP)

            if len(splitted) > 1:
                field = splitted[0]
                related = LOOKUP_SEP.join(splitted[1:])
                form.base_fields[field].queryset = form.base_fields[field].queryset.select_related(related)


class AdminInlineWithSelectRelated(admin.TabularInline, AdminBaseWithSelectRelated):
    """
    Admin Inline using list_select_related for get_queryset and get_formset related fields
    """

    def get_formset(self, request, obj=None, **kwargs):
        formset = super(AdminInlineWithSelectRelated, self).get_formset(request, obj, **kwargs)

        self.form_apply_select_related(formset.form)

        return formset


class AdminWithSelectRelated(admin.ModelAdmin, AdminBaseWithSelectRelated):
    """
    Admin using list_select_related for get_queryset and get_form related fields
    """

    def get_form(self, request, obj=None, **kwargs):
        form = super(AdminWithSelectRelated, self).get_form(request, obj, **kwargs)

        self.form_apply_select_related(form)

        return form


class FilterWithSelectRelated(admin.RelatedFieldListFilter):
    list_select_related = []

    def field_choices(self, field, request, model_admin):
        return [
            (getattr(x, field.remote_field.get_related_field().attname), str(x))
            for x in self.get_queryset(field)
        ]

    def get_queryset(self, field):
        return field.remote_field.model._default_manager.select_related(*self.list_select_related)

app/admin.py

from django.contrib import admin

from project.admin import AdminWithSelectRelated, AdminInlineWithSelectRelated, FilterWithSelectRelated
from .models import FormaPago, Comprobante, ItemServicio, ItemBazar


class ItemServicioInlineAdmin(AdminInlineWithSelectRelated):
    model = ItemServicio

    list_select_related = (
        'alumno_servicio__alumno__estudiante__profile',
        'alumno_servicio__servicio__grado',
        'comprobante__forma_pago',
    )


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, ItemBazarInlineAdmin, )


class AlumnoFilter(FilterWithSelectRelated):
    list_select_related = ('estudiante__profile', )


class ItemServicioAdmin(AdminWithSelectRelated):
    list_display = ('nombre', 'alumno', 'monto_pagado', 'comprobante', )
    list_filter = (
        'alumno_servicio__alumno__seccion__grado',
        ('alumno_servicio__alumno', AlumnoFilter),
    )

    list_select_related = (
        'comprobante__forma_pago',
        'alumno_servicio__alumno__estudiante__profile',
        'alumno_servicio__alumno__seccion__grado',
        'alumno_servicio__servicio__grado',
    )


class ItemBazarAdmin(AdminWithSelectRelated):
    list_display = ('nombre', 'alumno', 'monto_pagado', 'comprobante', )
    list_filter = (
        'alumno_item__alumno__seccion__grado',
        ('alumno_item__alumno', AlumnoFilter),
    )

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


admin.site.register(FormaPago)
admin.site.register(Comprobante, ComprobanteAdmin)
admin.site.register(ItemServicio, ItemServicioAdmin)
admin.site.register(ItemBazar, ItemBazarAdmin)

All I have to do is define the select_related fields, and the Custom AdminWithSelectRelated, AdminInlineWithSelectRelated, and FilterWithSelectRelated make use of them for Changelists, Changeforms, and even inline Formsets.

Works like a charm.

Danial Tz

I'm working currently on a similar problem. What I have found is documented at this thread: Translatable Manytomany fields in admin generate many queries

One important observation I did is that my solution works only for Django 1.7x and not for 1.8. Exactly same code, with d1.7 I have order of 10^1 queries, and with new installation of d1.8 I have 10^4.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!