You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 06:03:31