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

Django多对多关联模型查询报错及分类未显示问题排查

带有多对多关系的模型属性数据未显示,错误出在哪里?

执行以下代码时:

genres = GamesGenres.objects.annotate(Count('games'))

出现报错:

Cannot resolve keyword 'games' into field. Choices are: games_genres, id, name, slug

需求是在侧边栏展示游戏分类列表,支持点击分类筛选对应游戏,但使用报错提示中的字段时,Games模型的genre属性无法在浏览器侧边栏按预期显示。


models.py

class Games(models.Model):
    name = models.CharField(max_length=255)
    slug = models.CharField(max_length=255, unique=True, db_index=True)
    content = models.TextField()
    photo = models.ImageField(upload_to="games/%Y/%m/%d/")
    release_date = models.DateField()
    developer = models.CharField(max_length=255)
    publisher = models.CharField(max_length=255)
    trailer = models.URLField()
    genre = models.ManyToManyField("GamesGenres", related_name="games_genres")

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("show_games", kwargs={"games_slug": self.slug})


class GamesGenres(models.Model):
    name = models.CharField(max_length=80)
    slug = models.CharField(max_length=255, unique=True, db_index=True)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("genres", kwargs={"genre_slug": self.slug})

views.py

class GamesList(GamesMixin, ListView):
    model = Games
    template_name = "main_app/games.html"
    context_object_name = "games"

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        c_def = self.get_user_context(title="Games")
        return dict(list(context.items()) + list(c_def.items()))


class ShowGamesGenres(GamesMixin, ListView):
    model = GamesGenres
    template_name = "main_app/games.html"
    context_object_name = "games"
    allow_empty = False

    def get_queryset(self):
        return Games.objects.filter(genre__slug=self.kwargs["genre_slug"]).select_related('genre')

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        c_def = self.get_user_context(title=str(context["games"][0].genre),
                                      genre_selected=context["games"][0].genre_id)
        return dict(list(context.items()) + list(c_def.items()))

utils.py

class GamesMixin:

    def get_user_context(self, **kwargs):
        context = kwargs
        genres = GamesGenres.objects.annotate(Count('games'))
        context["genres"] = genres
        if "genre_selected" not in context:
            context["genre_selected"] = 0
        return context

urls.py

...
    path('games/', GamesList.as_view(), name="games"),
    path('games/genres/<slug:genre_slug>/', ShowGamesGenres.as_view(), name="genres"),
...

HTML文件

{% extends "main_app/base.html" %}

{% block main %}
<tr>
  <td valign="top" class="left-chapters">
    <ul id="leftchapters">
      {% if genre_selected == 0 %}
        <li class="selected">All</li>
      {% else %}
        <li><a href="{% url 'games' %}">All</a></li>
      {% endif %}
      {% for g in genres %}
        {% if g.games__count > 0 %}
          {% if g.pk == genre_selected %}
            <li class="selected">{{ g.name }}</li>
          {% else %}
            <li><a href="{{ g.get_absolute_url }}">{{ g.name }}</a></li>
          {% endif %}
        {% endif %}
      {% endfor %}
    </ul>
  </td>

  <td valign="top" class="content">
    <div class="content-text col-md-10 offset-md-1">
      <h1><b>{{ title }}</b></h1>
      <ul class="list-articles">
        {% for g in games %}
          <div class="card">
            <div class="card-body">
              <li>
                {% if g.photo %}
                  <p><img class="img-article-left thumb" src="{{ g.photo.url }}"></p>
                {% endif %}
                <h2><a class="link-games" href="{{ g.get_absolute_url }}">{{ g.name }}</a></h2>
                <p>Platform: ...</p>
                <p>Genre: {{ g.genre }}</p>
                <p>Realese date: {{ g.release_date|date:"d-m-Y" }}</p>
              </li>
            </div>
          </div>
        {% endfor %}
      </ul>
    </div>
  </td>
</tr>
{% endblock %}

问题解决步骤

1. 修正反向关联字段名

报错核心原因是反向关联名称不匹配:

  • 在Games模型的genre字段中,你设置了related_name="games_genres",这意味着从GamesGenres反向查询关联的Games对象时,要使用games_genres作为字段名,而非代码里的games。

修改utils.py中的annotate代码:

# 原代码
genres = GamesGenres.objects.annotate(Count('games'))
# 修改后
genres = GamesGenres.objects.annotate(Count('games_genres'))

同时,HTML模板中判断分类下游戏数量的代码也要对应修改:

<!-- 原代码 -->
{% if g.games__count > 0 %}
<!-- 修改后 -->
{% if g.games_genres__count > 0 %}

2. 正确显示多对多的genre属性

HTML中直接写{{ g.genre }}会输出多对多关系的对象集合(类似GamesGenres object (1), GamesGenres object (2)),需要用格式化处理:

<!-- 原代码 -->
<p>Genre: {{ g.genre }}</p>
<!-- 修改后(用逗号分隔多个分类) -->
<p>Genre: {{ g.genre.all|join:", " }}</p>
<!-- 或者遍历显示每个分类 -->
<p>Genre: 
  {% for genre in g.genre.all %}
    {{ genre.name }}{% if not forloop.last %}, {% endif %}
  {% endfor %}
</p>

3. 修复分类筛选页面的标题获取逻辑

ShowGamesGenres中context["games"][0].genre是多对多关系,直接转字符串会得到集合对象,需要取具体的分类对象:

# 原代码
c_def = self.get_user_context(title=str(context["games"][0].genre),
                              genre_selected=context["games"][0].genre_id)
# 修改后(取第一个分类作为标题)
selected_genre = context["games"][0].genre.all().first()
c_def = self.get_user_context(title=str(selected_genre.name),
                              genre_selected=selected_genre.pk)

4. 优化多对多查询的性能

在ShowGamesGenres的get_queryset中,select_related不适用于多对多关系,应该用prefetch_related预加载关联数据,减少数据库查询:

# 原代码
return Games.objects.filter(genre__slug=self.kwargs["genre_slug"]).select_related('genre')
# 修改后
return Games.objects.filter(genre__slug=self.kwargs["genre_slug"]).prefetch_related('genre')

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 08:48:21