Django 管理内联:select_lated

问题描述 投票:0回答:5

在 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 choice for ModelChoiceField or ModelMultipleChoiceField in a Django form 解决了每行查询的问题 但我对 str 方法仍然有问题。我尝试过以下方法:

1) 添加 prefetch_lated 到 ComingAdmin:

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

2) 将 select_lated 添加到 ComingProductInline:

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

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

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

没有任何帮助:每次调用 PricedProduct 的 str 都会使 Django 对 Product 表执行查询。所有这些方法都在 stackoverflow 上提到过,但它们只针对 ModelAdmin,而对 Inline 没有帮助。我想念什么?

python django foreign-keys admin django-queryset
5个回答
13
投票

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

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_lated 字段,自定义

AdminWithSelectRelated
AdminInlineWithSelectRelated
FilterWithSelectRelated
将它们用于变更列表、变更表单,甚至内联表单集。

就像魅力一样。


11
投票

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

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 对象,并且可能需要很长时间才能呈现。当它是过滤后的查询集时,它渲染得非常快。


5
投票

受@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

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


1
投票

我目前正在解决类似的问题。我发现的内容记录在这个线程中:管理中的可翻译多位字段生成许多查询

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


0
投票

我和@juntatalor有同样的现象,

__str__()
正在一一进行查找。

我试图覆盖表单集的初始化,但是当添加表单模型的实例时,查询集是空的 - 它只会有 edit 视图的实例。

因此,根据@5parkp1ug的推荐,我使用了formfield_for_foreignkey(在Django 3.2中):

对于这种情况,我的代码相当于:

class ComingProductsInline(admin.TabularInline):
    model = ComingProducts

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name = "priced_product" and db_field.column == "product_id":
            kwargs["queryset"] = PricedProduct.objects.select_related("product")
        return super().formfield_for_foreignkey(db_field, request, **kwargs)
© www.soinside.com 2019 - 2024. All rights reserved.