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

Django一对多关联查询:如何展示帖子及关联首张图片?

Django实现首页展示帖子及关联首张图片的解决方案

错误原因梳理

  • select_related('Media')报错是因为select_related仅支持正向的多对一/一对一关系查询,你的Post和Media是一对多的反向关联(Media关联Post),Post模型里并没有名为Media的外键字段,所以不能这么用。
  • 之前用Media.objects.all()会导致同一个帖子被重复展示(有几张图就显示几次),完全不符合“每个帖子只显示一次+首张图片”的需求。

方案一:用annotate直接获取每个帖子的首张图片(推荐,性能更优)

通过数据库层面的子查询,直接给每个Post对象添加first_photo字段,存储对应首张图片的信息,无需在模板中额外处理关联数据。

修改视图代码

from django.db.models import Subquery, OuterRef
from .models import Post, Media

def index(request):
    # 子查询:获取每个Post对应的第一个Media(按id排序)的photo字段
    first_media = Media.objects.filter(post=OuterRef('pk')).order_by('id')[:1]
    # 给Post对象添加first_photo注解
    posts = Post.objects.annotate(
        first_photo=Subquery(first_media.values('photo'))
    ).all()
    
    paginator = Paginator(posts, 2)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    return render(request, 'aggregator/index.html', {'page': page_obj})

修改模板代码

现在循环的是Post对象,直接调用注解的first_photo字段即可:

{% for post in page %}
<div class="col">
    <div>
        {% if post.first_photo %}
            <img src="{{ post.first_photo.url }}" alt="{{ post.title }}">
        {% else %}
            <p>无图片</p>
        {% endif %}
    </div>
    <div>
        <h4><a href="{% url 'show_post' post_id=post.id %}">{{ post.title|upper }}</a></h4>
    </div>
    <div>
        <h5>{{ post.price }}$</h5>
    </div>
    <div>
        <h6>{{ post.city }}</h6>
    </div>
</div>    
{% endfor %}

方案二:用prefetch_related预加载首张图片(适合需访问多个图片的场景)

如果后续需要展示帖子的多张图片,可通过Prefetch指定预加载每个Post的首个Media对象,减少数据库查询次数。

修改视图代码

from django.db.models import Prefetch
from .models import Post, Media

def index(request):
    # 预加载每个Post的首个Media(按id排序),并存储到first_media属性
    posts = Post.objects.prefetch_related(
        Prefetch(
            'media_set',  # Post反向关联Media的默认名称(模型名小写+_set)
            queryset=Media.objects.order_by('id')[:1],
            to_attr='first_media'
        )
    ).all()
    
    paginator = Paginator(posts, 2)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    return render(request, 'aggregator/index.html', {'page': page_obj})

修改模板代码

从预加载的first_media列表中取第一个元素:

{% for post in page %}
<div class="col">
    <div>
        {% if post.first_media %}
            <img src="{{ post.first_media.0.photo.url }}" alt="{{ post.title }}">
        {% else %}
            <p>无图片</p>
        {% endif %}
    </div>
    <div>
        <h4><a href="{% url 'show_post' post_id=post.id %}">{{ post.title|upper }}</a></h4>
    </div>
    <div>
        <h5>{{ post.price }}$</h5>
    </div>
    <div>
        <h6>{{ post.city }}</h6>
    </div>
</div>    
{% endfor %}

内容的提问来源于stack exchange,提问作者sergeusprecious

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 16:20:45