Django Admin列表展示自定义字段预取优化:解决查询过多问题
解决Django Admin自定义字段引发的大量查询问题
嘿,我太懂这种头疼的感觉了——自定义字段刚加上的时候看着挺好用,结果列表页一加载就蹦出几百条查询,数据库直接扛不住😅。你已经走对了第一步(用select/prefetch),只是没把预取逻辑覆盖到列表页的查询集上,我给你捋清楚怎么解决:
核心问题出在哪?
你那些get_xxx格式的自定义字段,每次在列表页渲染一行数据时都会被调用一次。如果方法里访问了关联对象(比如obj.subnet.subnet_type),而列表页的查询集没预取这些关联,就会触发可怕的N+1查询。之前你加的预取逻辑应该只作用于编辑表单,没管到列表页的查询。
关键解法:重写Admin的get_queryset方法
Django Admin列表页的所有数据,都是由get_queryset方法返回的查询集驱动的。你只需要在这里把自定义字段依赖的所有关联对象统一预取,就能一次性把所有需要的数据拉回来,避免重复查询。
第一步:先搞清楚自定义字段依赖哪些关联
先逐个看你的get_xxx方法,比如:
- 如果
get_subnet_type里用到了obj.subnet.subnet_type,那得预取subnet和subnet__subnet_type - 如果
get_device_hostname里用了obj.device.hostname,那要预取device
第二步:给你的DeviceCircuitSubnetsAdmin加get_queryset
直接上代码示例,你可以照着改:
class DeviceCircuitSubnetsAdmin(admin.ModelAdmin): search_fields = ['device__hostname','circuit__ref_no','subnet__subnet_type__subnet_type','subnet__subnet'] # 假设你的list_display里有这些自定义方法,按需调整 list_display = ['id', 'get_device_hostname', 'get_subnet_type', 'get_circuit_ref'] def get_queryset(self, request): # 先拿到父类默认的查询集 qs = super().get_queryset(request) # 用select_related处理一对一/外键关联(比如device、circuit、subnet这些都是外键) qs = qs.select_related( 'device', 'circuit', 'subnet__subnet_type' ) # 如果有需要预取的多对多或者反向关联,用prefetch_related,比如: # qs = qs.prefetch_related('some_many_to_many_field') return qs # 举个自定义方法的例子,你可以对应自己的方法改 def get_device_hostname(self, obj): return obj.device.hostname get_device_hostname.short_description = '设备主机名' def get_subnet_type(self, obj): return obj.subnet.subnet_type.subnet_type get_subnet_type.short_description = '子网类型'
为什么之前的预取没生效?
你之前加的select_related/prefetch_related大概率是在编辑表单的逻辑里(比如get_form或者formfield_for_foreignkey),这些只会影响编辑页面的查询,而列表页的查询集是独立由get_queryset控制的,所以必须在这里加才行。
额外小技巧
- 装个Django Debug Toolbar,优化完直接看查询数,确认是不是降到1-2条了,一目了然
- 如果自定义方法里用到了更复杂的关联(比如要过滤反向关联的对象),可以用
Prefetch对象定制预取逻辑,比如:from django.db.models import Prefetch qs = qs.prefetch_related( Prefetch('subnet__related_objects', queryset=RelatedObject.objects.filter(status='active')) )
内容的提问来源于stack exchange,提问作者AlexW
相关产品推荐
相关产品推荐

