Django 管理员内联:select_related
Posted
技术标签:
【中文标题】Django 管理员内联:select_related【英文标题】:Django admin inline: 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 表的查询。所有这些方法都在 *** 上提到过,但是它们处理了 ModelAdmin,并且对 Inline 没有帮助。我错过了什么?
【问题讨论】:
【参考方案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 字段,自定义 AdminWithSelectRelated
、AdminInlineWithSelectRelated
和 FilterWithSelectRelated
将它们用于更改列表、更改表单,甚至内联表单集。
像魅力一样工作。
【讨论】:
似乎 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 个。
【讨论】:
以上是关于Django 管理员内联:select_related的主要内容,如果未能解决你的问题,请参考以下文章