如何在Django ModelAdmin中重写只读外键字段的默认表单字段?
Django Admin只读外键性能优化方案
问题场景
我在Django Admin的ModelAdmin中重写formfield_for_foreignkey方法,通过Car.objects.only("name")指定加载外键的必要字段,以此优化表单加载性能:
class MyModelAdmin(admin.ModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "car": kwargs["queryset"] = Car.objects.only("name") return super().formfield_for_foreignkey(db_field, request, **kwargs)
但当car外键被设为只读字段时,这个方法不会被触发,查询该外键时会加载Car模型的所有字段,其中包含大字段导致页面加载性能严重下降。尝试过官方文档提到的ModelForm.__init__()方案,但不适用当前场景。
可行解决方案
方案1:重写get_queryset预加载必要字段
在ModelAdmin中重写get_queryset,使用select_related关联外键并通过only明确指定要加载的字段,这样在获取主模型对象时就会预先加载外键的必要字段,避免后续的N+1查询和全字段加载:
class MyModelAdmin(admin.ModelAdmin): readonly_fields = ("car",) def get_queryset(self, request): return super().get_queryset(request).select_related("car").only( # 列出主模型需要显示的字段 "id", "your_main_model_field", # 外键字段格式:外键名__字段名 "car__name" )
这是性能最优的方案,从根源上减少了数据库查询开销。
方案2:自定义只读字段显示方法
如果需要更灵活的控制,可以为只读外键自定义显示方法,在方法中手动查询仅需字段:
class MyModelAdmin(admin.ModelAdmin): readonly_fields = ("display_car",) def display_car(self, obj): if obj.car_id: # 仅加载car的name字段 car = Car.objects.only("name").get(id=obj.car_id) return car.name return "-" display_car.short_description = "车辆" # 设置字段在Admin中的显示名称
这种方式适合外键可能为空,或者需要对显示内容做额外处理的场景。
方案3:重写render_change_form优化上下文对象
如果想保留Admin默认的只读外键显示样式(比如带跳转链接的对象名称),可以重写render_change_form方法,在渲染页面前替换上下文里的原始对象为预加载了必要字段的版本:
class MyModelAdmin(admin.ModelAdmin): readonly_fields = ("car",) def render_change_form(self, request, context, add=False, change=False, form_url="", obj=None): if obj and change: # 重新获取对象,预加载car的必要字段 obj = self.get_queryset(request).select_related("car").only( "id", "your_main_model_field", "car__name" ).get(id=obj.id) context["original"] = obj return super().render_change_form(request, context, add, change, form_url, obj)
内容的提问来源于stack exchange,提问作者CurlyError
相关产品推荐
相关产品推荐

