【问题标题】:Django admin inline: select_relatedDjango 管理员内联:select_related
【发布时间】:2015-06-21 05:57:20
【问题描述】:

在带有模型的 Python 3.4.1 上使用 Django 1.8:

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

和下面的 admin.py:

class ComingProductsInline(ForeignKeyCacheMixin, admin.TabularInline):
    model = ComingProducts


class ComingAdmin(admin.ModelAdmin):
    inlines = [ComingProductsInline]

当然,我在对数据库进行多次查询时遇到了问题:我对列表中的每个项目都有一个查询,对每一行都有一个查询。所以,有 100 个项目我得到 100 ^ 2 个查询。 我用Caching queryset choices for ModelChoiceField or ModelMultipleChoiceField in a Django form 解决了每一行的查询问题 但我仍然对 str 方法有问题。我尝试了以下方法:

1) 将 prefetch_related 添加到 ComingAdmin:

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

2) 将 select_related 添加到 ComingProductInline:

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

3) 为内联定义自定义表单并将 select_related 添加到字段查询集:

 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) 定义自定义表单集:

 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) 前 4 种方法的不同组合

没有任何帮助:每次调用 str 对 PricedProduct 都会使 Django 执行对 Product 表的查询。所有这些方法都在 stackoverflow 上提到过,但是它们处理了 ModelAdmin,并且对 Inline 没有帮助。我错过了什么?

【问题讨论】:

    标签: python django foreign-keys admin django-queryset


    【解决方案1】:

    受@helpse 回答的启发,如果您只想覆盖单个管理员内联的查询集,还可以执行以下操作:

    class ComingProductsInline(admin.TabularInline):
        model = ComingProducts
    
        def get_formset(self, request, obj=None, **kwargs):
            formset = super(ComingProductsInline, self).get_formset(request, obj, **kwargs)
            queryset = formset.form.base_fields["priced_product"].queryset
            queryset = queryset.select_related("product")
            formset.form.base_fields["priced_product"].queryset = queryset
            return formset
    

    对于大多数情况来说,这可能就足够了。

    【讨论】:

      【解决方案2】:

      您会发现这种方法非常有用:

      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)
      

      我所要做的就是定义 select_related 字段,自定义 AdminWithSelectRelatedAdminInlineWithSelectRelatedFilterWithSelectRelated 将它们用于更改列表、更改表单,甚至内联表单集。

      像魅力一样工作。

      【讨论】:

      • 似乎 django 现在开箱即用地支持这一点,因此您可以将 list_select_related 添加到您的管理类中,而无需使用 AdminBaseWithSelectRelated 之类的东西。见docs.djangoproject.com/en/2.2/ref/contrib/admin/…
      • 嗨@MatthijsKooijman。 select_related 为 AdminClass 工作了很长时间。我的解决方案适用于内联和过滤器。
      • 啊。我在想您的解决方案也适用于普通管理员,因为您是从 BaseAdmin 派生的。我还假设内置的 selectRelated 支持实际上也适用于内联,因为我认为它也来自 BaseAdmin?不过还没有测试。
      【解决方案3】:

      formset 解决方案确实对我有用,但方法略有不同:

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

      BaseInlineFormSet 类为您过滤查询集,您需要获取过滤后的查询集并添加预取。使用您的表单集实现(all() 查询集),您将获得不相关的 ComingProduct 对象,并且渲染时间可能太长。当它是过滤后的查询集时,它会非常快速地呈现。

      【讨论】:

      • 哦。这是一个救生员。我有一个模型 A,它有许多模型 B 的内联。B 有三个 m2m 和两个 fk 关系。而且。使用了模型翻译。此外,Grappelli 的自动完成功能也提供了帮助。
      • 怎么样? Django的相关代码好像没变
      • @ramusus: 尝试过formfield_for_foreignkey 及其与 django 2.1 的作品
      • 适用于 Django 2.2
      【解决方案4】:

      我目前正在处理类似的问题。我发现的内容记录在此线程中:Translatable Manytomany fields in admin generate many queries

      我所做的一个重要观察是我的解决方案仅适用于 Django 1.7x 而不适用于 1.8。完全相同的代码,使用 d1.7 我有 10^1 个查询的顺序,而新安装 d1.8 我有 10^4 个。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-06-02
        • 2013-03-01
        • 2017-06-28
        • 1970-01-01
        • 1970-01-01
        • 2011-04-10
        • 1970-01-01
        相关资源
        最近更新 更多