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
相关产品推荐
相关产品推荐

