如何在Django模板中基于外键关联访问关联对象?
Django模板中外键关联数据的层级遍历实现方法
我有一组通过外键关联的Django模型,精简后的models.py代码如下:
class Saga(models.Model): name = models.CharField(max_length=200) description = models.CharField(max_length=1000) startChapter = models.PositiveIntegerField() endChapter = models.PositiveIntegerField() class Arc(models.Model): name = models.CharField(max_length=200) description = models.CharField(max_length=1000) saga = models.ForeignKey(Saga,on_delete=models.SET_DEFAULT,default=Saga.get_default_pk) startChapter = models.PositiveIntegerField() endChapter = models.PositiveIntegerField() class Clip(models.Model): description = models.CharField(max_length=200) arc = models.ForeignKey(Arc,on_delete=models.SET_DEFAULT,default=Arc.get_default_pk) chapter = models.PositiveIntegerField() viewers = models.ManyToManyField(User, through='ClipViewer') author = models.ForeignKey(User,on_delete=models.SET_NULL,null=True, related_name='author_of')
这些模型的关联关系是:每个Saga对应多个Arc,每个Arc对应多个Clip。我希望在模板中按层级遍历展示,结构示例如下:
Saga 1 has arcs 1,2,3 Arc 1 has clips 1 & 2 Arc 2 has clip 3 Arc 3 has clips 4 & 5 Saga 2 has arc 4,5....
但Django模板功能有限,官方提示:
Django有意限制模板语言中的逻辑处理能力,因此无法在模板中调用带参数的方法。数据应在视图中计算完成后,再传递给模板展示。
以下是可行的解决方案:
方法一:利用自动生成的反向关联属性直接遍历
Django会为外键自动生成反向关联属性,默认名称为模型名小写_set,你可以直接在模板循环中使用这个属性获取关联数据:
模板代码示例:
{% for saga in sagas %} <p>{{ saga.name }} has arcs {% for arc in saga.arc_set.all %}{{ arc.name }}{% if not forloop.last %}, {% endif %}{% endfor %}</p> {% for arc in saga.arc_set.all %} <p> {{ arc.name }} has clips {% for clip in arc.clip_set.all %}{{ clip.description }}{% if not forloop.last %} & {% endif %}{% endfor %}</p> {% endfor %} {% endfor %}
注意:这种方式在数据量大时会触发N+1查询问题,每次遍历都会执行新的数据库查询,性能较差。
方法二:视图预加载关联数据(推荐)
为优化性能,在视图中使用prefetch_related预加载所有层级的关联数据,一次性完成数据库查询,再传递给模板:
视图代码示例:
from django.shortcuts import render from django.db.models import Prefetch from .models import Saga, Arc def saga_list(request): # 预加载Saga关联的所有Arc,以及每个Arc关联的Clip sagas = Saga.objects.prefetch_related( Prefetch('arc_set', queryset=Arc.objects.prefetch_related('clip_set')) ).all() return render(request, 'saga_list.html', {'sagas': sagas})
模板代码和方法一完全相同,但此时所有数据已在视图中一次性查询完成,性能显著提升。
可选:自定义反向关联名称
如果觉得默认的arc_set、clip_set不够直观,可以在定义外键时通过related_name指定自定义名称:
修改模型代码:
class Arc(models.Model): # ... 其他字段 saga = models.ForeignKey( Saga, on_delete=models.SET_DEFAULT, default=Saga.get_default_pk, related_name='arcs' # 自定义反向关联名称 ) class Clip(models.Model): # ... 其他字段 arc = models.ForeignKey( Arc, on_delete=models.SET_DEFAULT, default=Arc.get_default_pk, related_name='clips' # 自定义反向关联名称 )
之后模板中可以用更易读的名称遍历:
{% for saga in sagas %} <p>{{ saga.name }} has arcs {% for arc in saga.arcs.all %}{{ arc.name }}{% if not forloop.last %}, {% endif %}{% endfor %}</p> {% for arc in saga.arcs.all %} <p> {{ arc.name }} has clips {% for clip in arc.clips.all %}{{ clip.description }}{% if not forloop.last %} & {% endif %}{% endfor %}</p> {% endfor %} {% endfor %}
内容的提问来源于stack exchange,提问作者Makori SM
相关产品推荐
相关产品推荐

