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

Django ListView获取最新关联对象 解决缓存时长不符预期问题

问题排查与解决方案

问题核心现象

  • 已定义Campaign与CampaignStatus模型,CampaignStatus通过外键关联Campaign,每次Campaign状态流转都会生成新的CampaignStatus记录
  • 基于Django类视图实现用户Campaign列表页,需要向模板传递每个Campaign的最新状态
  • 异常表现:自定义列表视图、Django Admin的Campaign列表页均存在状态缓存问题,新状态生成后不会即时更新;但Admin的CampaignStatus列表页正常,新记录可即时展示
  • 预期缓存时长为5秒,实际缓存生效时长约3分钟

当前视图代码如下:

@method_decorator(cache_page(5), name="dispatch") # single arg is seconds
class CampaignsListView(LoginRequiredMixin, ListView):

    model = Campaign
    paginate_by = 100

    template_name = "writing/user_campaigns.html"
    context_object_name = "user_campaigns"


    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        for i, _ in enumerate(context.get("user_campaigns")):
            campaign = context["user_campaigns"][i]
            campaign_status = CampaignStatus.objects.filter(campaign=campaign).latest("-status")
            context["user_campaigns"][i].status = campaign_status.get_status_display()

        return context

    def get_queryset(self):
        return Campaign.objects.filter(user=self.request.user).order_by("-modified")

    #... some other methods too

修复步骤

1. 修正缓存时长不符合预期的问题

cache_page装饰器传入的缓存时长会被全局缓存配置覆盖,优先检查settings.py中的两项配置:

  • 若开启了UpdateCacheMiddleware和FetchFromCacheMiddleware站点缓存中间件,全局配置项CACHE_MIDDLEWARE_SECONDS会覆盖视图上设置的5秒缓存,默认值为600秒,若之前手动设置为180秒就会出现3分钟缓存的现象。如果不需要全局站点缓存,直接移除这两个中间件,仅给需要缓存的视图单独加装饰器,可控性更高;如果需要保留全局缓存,将该值调整为5,或给目标视图单独设置缓存优先级。
  • 若使用Redis、Memcached等第三方缓存后端,检查后端配置中是否设置了全局默认TTL,覆盖Django传入的过期时间。

另外当前cache_page默认按URL生成缓存key,会导致不同登录用户访问同一路径时串数据,需要加用户维度的key前缀:

from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page

def get_user_cache_prefix(request, *args, **kwargs):
    return f"campaign_list_{request.user.id}"

@method_decorator(cache_page(5, key_prefix=get_user_cache_prefix), name="dispatch")
class CampaignsListView(LoginRequiredMixin, ListView):
    # 原有代码逻辑

2. 解决状态数据不刷新的问题

当前代码在get_context_data中循环查询每个Campaign的最新状态,既存在N+1查询性能问题,动态给模型实例绑定属性的写法也会在QuerySet缓存命中时跳过数据库查询,导致状态数据不更新。更合理的实现是在查询集阶段通过注解预加载最新状态,从根源避免该问题:

from django.db.models import OuterRef, Subquery

def get_queryset(self):
    # 子查询取每个Campaign关联的最新状态记录,按状态创建时间倒序取第一条
    latest_status = CampaignStatus.objects.filter(
        campaign=OuterRef("pk")
    ).order_by("-created_at").values("status")[:1]

    return Campaign.objects.filter(
        user=self.request.user
    ).annotate(
        current_status=Subquery(latest_status)
    ).order_by("-modified")

对应简化get_context_data逻辑,无需额外查库:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    # 直接读取模型定义的状态choices映射
    status_choices = dict(CampaignStatus._meta.get_field("status").choices)
    for campaign in context["user_campaigns"]:
        campaign.status = status_choices.get(campaign.current_status, "")
    return context

注意:原有代码中CampaignStatus.objects.filter(campaign=campaign).latest("-status")存在逻辑错误:latest()方法不需要在字段名前加负号,且取最新状态应该按状态记录的创建时间字段排序,而非status字段本身,否则无法拿到最新生成的流转记录。

3. 修复Admin页面缓存异常

Django Admin默认不会开启页面缓存,若Admin列表也出现3分钟缓存,说明全局缓存中间件对Admin路径生效。可以在缓存规则中配置排除/admin/路径,或者给Admin视图单独设置短缓存/关闭缓存,不建议对Admin后台做长缓存。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 13:15:35