Django,从反向外键查询添加数据(外键加入过滤器)

Posted

技术标签:

【中文标题】Django,从反向外键查询添加数据(外键加入过滤器)【英文标题】:Django, add data from reverse Foreign key query (foreign key join with filter) 【发布时间】:2016-12-09 09:00:06 【问题描述】:

我正在用 Django 编写一个小型网络游戏。每场比赛有两名球员。为了管理游戏和参与者,我有这样的东西(非常简化):

class GamesQuerySet(models.query.QuerySet):
    def with_player(self, user):
        """Games with |user| participating"""
        return self.filter(players__player=user)


class Game(models.Model):
    """All the games"""
    name = models.CharField(max_length=100, blank=True)

    objects = GamesQuerySet.as_manager()


class PlayerInGame(models.Model):
    """Players participating in games"""
    game = models.ForeignKey('Game', related_name='players')
    player = models.ForeignKey('auth.User')

    class Meta:
        unique_together = ('game', 'player')

所以我可以轻松查询当前玩家参与的游戏:

Games.objects.with_player(user)

但要显示在游戏列表视图中,我还想包括其他玩家的姓名。

我尝试了一些事情,似乎可以通过添加 .extra() 和一些原始 SQL 来做到这一点,但我想尽量避免这种情况。

我尝试过使用类似这样的注释:

    def add_other_player(self, user):
        return self.annotate(other_player=PlayerInGame.objects.all(). \
                filter(~Q(player=user)))

但运气不佳。还研究了fetch_relatedselect_related。这样做的正确方法是什么?

【问题讨论】:

我不确定我是否理解了这个问题,您正在寻找一种让游戏的所有玩家都参与其中的方法吗?或者您正在寻找一种方法来找到有两个特定玩家的游戏? 【参考方案1】:

好的,我想我找到了我要找的东西。这是我所做的(在两步过程中):

from django.db.models import F

game_ids = Games.objects.with_player(user).values_list('id', flat=True)
with_opponents = Games.objects.filter(id__in=game_ids) \
                    .annotate(opponent=F('players__player__username')) \
                    .filter(~Q(opponent=user.username)

这样,我在结果中得到一个具有反向外键关系的“字段”,我也可以对其进行过滤。

不确定是否有避免这两个步骤的好方法,但这对我来说很好。

【讨论】:

我很高兴你成功了。我只是想知道第一步是否必要。从 Game 模型中获取 id 然后再次使用 id 查询 Game 有什么意义? 因为否则它将查询的两个部分“混合”在一起,并且由于第一部分是过滤以仅获取该用户,因此在尝试查找对手时它不起作用【参考方案2】:

fetch_relatedselect_related只是优化方法,关系一直存在,这两种方法只是做一批查询。

由于您很可能会遍历查询集游戏,因此您可以这样做:

games = Games.objects.with_player(user)
for game in games:
    players = game.players.values_list('player__first_name',
                                       'player__last_name')

这样做的缺点是您可以为player 指定尽可能多的字段,但它不会为您提供完整的播放器对象。我认为在GamePlayer 之间创建ManyToMany 关系实际上更有意义,因为听起来就像你正在做的那样,PlayerInGame 模型作为through 模型:

class PlayerInGame(models.Model):
    """Players participating in games"""
    game = models.ForeignKey('Game', related_name='players')
    player = models.ForeignKey('auth.User')

    class Meta:
        unique_together = ('game', 'player')

class Game(models.Model):
    """All the games"""
    name = models.CharField(max_length=100, blank=True)
    players = models.ManyToManyField('auth.User', through='PlayerInGame')

    objects = GamesQuerySet.as_manager()

那么你可以做一个游戏的玩家:

players = game.players.all()

关于m2m with through的Django文档。

注意:through 主要用于 m2m 关系具有额外属性时。但是如果你的代码中没有任何额外的东西(也许你正在简化,但以防万一它完全一样),你可以使用ManyToManyField并一起摆脱PlayerInGame模型,django会创建反正你的中间数据库表。

编辑:

在你做的模板中:

% for game in games %
  Current game is:  game.name 
  The players in the game are:
  <ul>
  % for player in game.players.all %
    <li> player.player.first_name   player.player.last_name </li>
  % endfor %
  </ul>
% endfor %

【讨论】:

感谢您的帮助和详细解答!我想我的问题是,当它使用这些对手的表格呈现视图时,我需要从模板中访问一些玩家的详细信息(即使只是用户名或名字/姓氏也可以)......我知道你是什么建议会起作用,我只是不明白我将如何从模板/视图中做到这一点 完美!正是我需要的。 有没有好的过滤方法? (我只想显示另一个对手)。我可以做一个 % if ... % 来检查它是否与当前用户不同,只是不确定是否有更好的方法?

以上是关于Django,从反向外键查询添加数据(外键加入过滤器)的主要内容,如果未能解决你的问题,请参考以下文章

带有嵌套序列化程序的 Django 反向外键给出了多个结果

如何通过Django中的prefetch_related过滤具有多个条件的反向外键

如何以最少的数据库点击次数将多个实例添加到 Django 反向外键集?

Django 访问值对象中的反向外键数据

在main函数中使用django模型(附django正反向的外键关联查询)

如何通过django中的多级反向外键获取相关对象查询集?