Django admin inline: select_related

Usando Django 1.8 en Python 3.4.1 con modelos:

class Product(models.Model): name = models.CharField(max_length=255) # some more fields here def __str__(self): return self.name class PricedProduct(models.Model): product = models.ForeignKey(Product, related_name='prices') # some more fields here def __str__(self): return str(self.product) class Coming(models.Model): # some unimportant fields here class ComingProducts(models.Model): coming = models.ForeignKey(Coming) priced_product = models.ForeignKey(PricedProduct) # more unimportant fields 

y el siguiente admin.py:

 class ComingProductsInline(ForeignKeyCacheMixin, admin.TabularInline): model = ComingProducts class ComingAdmin(admin.ModelAdmin): inlines = [ComingProductsInline] 

Por supuesto, tengo un problema con multiplicar las consultas a la base de datos: tengo una consulta para cada elemento en la lista y una consulta para cada línea. Entonces, teniendo 100 artículos obtengo 100 ^ 2 consultas. Resolví el problema con las consultas de cada línea con las opciones de conjunto de consultas de Caching para ModelChoiceField o ModelMultipleChoiceField en un formulario Django. Pero todavía tengo problemas con el método str . He intentado lo siguiente:

1) agregando prefetch_related a ComingAdmin:

 def get_queryset(self, request): return super(ComingAdmin, self).get_queryset(request). \ prefetch_related('products__product') 

2) añadiendo select_related a ComingProductInline:

     def get_queryset(self, request): return super(ComingProductsInline, self).get_queryset(request). \ select_related('priced_product__product') 

    3) Definiendo un formulario personalizado para en línea y agregando select_related al campo queryset:

      class ComingProductsInline(ForeignKeyCacheMixin, admin.TabularInline): model = ComingProducts form = ComingProductsAdminForm class ComingProductsAdminForm(ModelForm): def __init__(self, *args, **kwargs): super(ComingProductsAdminForm, self).__init__(args, kwargs) self.fields['priced_product'].queryset = PricedProduct.objects.all(). \ select_related('product') class Meta: model = ComingProducts fields = '__all__' 

    4) Definiendo un formset personalizado:

      class ComingProductsInline(ForeignKeyCacheMixin, admin.TabularInline): model = ComingProducts formset = MyInlineFormset class MyInlineFormset(BaseInlineFormSet): def __init__(self, data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None, **kwargs): super(MyInlineFormset, self).__init__(data, files, instance, save_as_new, prefix, queryset, **kwargs) self.queryset = ComingProducts.objects.all(). \ prefetch_related('priced_product__product') 

    5) Diferentes combinaciones para los 4 métodos anteriores.

    Y nada ayuda: cada llamada de str para PricedProduct hace que Django realice una consulta para la tabla de productos. Todos estos métodos se mencionaron en stackoverflow, pero trataron ModelAdmin y no ayudan con Inline. ¿Qué extraño?

    La solución de formset funciona para mí, pero con un enfoque ligeramente diferente:

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

    La clase BaseInlineFormSet filtra el conjunto de consultas por usted, y usted necesita tomar ese conjunto de consultas filtrado y agregar el prefetch. Con su implementación de formset (el conjunto de consultas all ()) obtiene objetos ComingProduct no relacionados y probablemente el proceso demore demasiado. Cuando es el queryset filtrado, se procesa muy rápidamente.

    Estoy trabajando actualmente en un problema similar. Lo que he encontrado está documentado en este hilo: los campos traducibles de muchas personas en el administrador generan muchas consultas

    Una observación importante que hice es que mi solución solo funciona para Django 1.7x y no para 1.8. Exactamente el mismo código, con d1.7 tengo un orden de 10 ^ 1 consultas, y con la nueva instalación de d1.8 tengo 10 ^ 4.

    Usted encontrará este enfoque muy útil:

    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) 

    Todo lo que tengo que hacer es definir los campos relacionados con select_, y Custom AdminWithSelectRelated , AdminInlineWithSelectRelated y FilterWithSelectRelated utilizan para Changelists, Changeforms, e incluso en línea Formsets.

    Funciona de maravilla.