Django模板无法渲染ForeignKey关联模型数据问题
问题根因
渲染失败是多个配置错误叠加导致的,核心问题如下:
- 传给模板的
categories是DRF序列化输出的普通Python字典,不是Django ORM模型实例,字典不存在channel_set这类反向关联管理器方法,自然无法调用.all()遍历关联数据。 - 外键反向关联名配置错误:
Channel模型的category外键手动设置了related_name='channel',设置该参数后Django不会生成默认的channel_set反向管理器,且你设置的关联名为单数,不符合一个分类对应多个频道的一对多语义。 - 字段名不匹配:
Channel模型原本存储频道名称的字段是channel,模板里却写{{ channel.title }},就算拿到数据也读不到对应值。 - 序列化器写法错误:两个序列化器里多余写了
categories = Category()、channels = Channel(),直接实例化模型类作为序列化器字段,会导致序列化输出结构异常。 - 模型基础配置错误:ObjectId字段未设置为主键,外键错误指定
to_field关联到字符串类型的category字段而非主键,MongoDB环境下极易出现关联查询失效;创建时间字段错误使用auto_now=True,会导致创建时间每次修改记录都会被覆盖。 - 模板路径、HTML标签嵌套存在语法错误:模板路径开头多余加了斜杠,
<li>、<td>标签嵌套混乱,容易引发渲染异常。
分步修复方案
1. 修正models.py配置
调整外键关联、主键、时间字段配置,修正__str__返回逻辑:
class Category(models.Model): user = models.ForeignKey( 'auth.User', on_delete=models.DO_NOTHING, ) category = models.CharField( max_length=255, unique=True, ) # 明确设置ObjectId为主键,解决MongoDB关联查询异常 _id = models.ObjectIdField(auto_created=True, primary_key=True, serialize=False) # 创建时间用auto_now_add,避免修改记录时覆盖创建时间 event_creation = models.DateTimeField(auto_now_add=True) event_updated = models.DateTimeField(auto_now=True) class Meta: db_table = 'category' verbose_name = 'category' verbose_name_plural = 'categories' def __str__(self): return self.category class Channel(models.Model): user = models.ForeignKey( 'auth.User', on_delete=models.DO_NOTHING, ) _id = models.ObjectIdField(auto_created=True, primary_key=True, serialize=False) date_creation = models.DateTimeField(auto_now_add=True) date_updated = models.DateTimeField(auto_now=True) category = models.ForeignKey( Category, on_delete=models.PROTECT, # 设置正确的反向关联名,用复数匹配一对多语义 related_name='channels', # 去掉冗余的max_length、to_field、unique=False配置,默认关联主键即可 ) # 字段名和模板保持一致,用title存储频道名称 title = models.CharField( max_length=255, unique=True, ) def __str__(self): return self.title
修改完模型记得执行python manage.py makemigrations和python manage.py migrate同步数据库结构。
2. 修正serializers.py配置
删除冗余的错误字段,如果需要嵌套返回关联数据可以显式声明嵌套序列化器:
class ChannelSerializer(serializers.ModelSerializer): class Meta: model = Channel fields = '__all__' class CategorySerializer(serializers.ModelSerializer): # 嵌套关联频道数据,名称和外键related_name保持一致 channels = ChannelSerializer(many=True, read_only=True) class Meta: model = Category fields = '__all__'
3. 修正views.py逻辑
渲染HTML场景直接传预加载关联数据的QuerySet即可,不需要额外序列化(序列化是给JSON接口用的),同时加prefetch_related避免N+1查询问题:
class Gallery(viewsets.ModelViewSet): renderer_classes = [TemplateHTMLRenderer] # 去掉模板路径开头的斜杠 template_name = 'gallery.html' permission_classes = [permissions.IsAuthenticated] def get(self, request, **kwargs): # 预加载关联频道数据,优化查询性能 categories = Category.objects.prefetch_related('channels').all() return Response({ 'categories': categories }) def list(self, request): categories = Category.objects.prefetch_related('channels').all() return Response({'categories': categories})
4. 修正gallery.html模板
调整反向关联遍历写法,修正标签嵌套错误:
{% extends "model-page.html" %} {% load core_tags %} {% block content %} <h1> co. </h1> <h2> Last Archives </h2> <a href="/category-api/"> <button type="button"> New input </button> </a> <ul> {% for category in categories %} <li> {% underscoreTag category "_id" as category_id %} <div {% if category.get_pass_event %} style="color: red "{% endif %}> {{ category.category }} - {{ category.event_creation }} <ul> <li>{{ category }}</li> <li> <ul> {% for channel in category.channels.all %} <li>Teste {{ channel.title }}</li> {% empty %} <li>该分类下暂无频道</li> {% endfor %} </ul> </li> </ul> <a href="/category/?id={{category_id}}&{{category.category}}">Edit</a> / <a href="/category/delete/{{category_id}}">Delete</a> </div> </li> {% endfor %} </ul> {% endblock %}
额外说明
如果你坚持要传序列化后的数据到模板,必须使用嵌套版的CategorySerializer序列化数据,此时模板里遍历频道不需要加.all(),直接写{% for channel in category.channels %}即可——因为序列化后的数据是普通字典,channels对应的值已经是列表结构,没有ORM的管理器方法。
内容的提问来源于stack exchange,提问作者efraim.lima
相关产品推荐
相关产品推荐

