Django,从反向外键查询添加数据(带过滤器的外键连接)

时间:2016-08-03 14:50:34

标签: django django-models foreign-keys django-queryset reverse

我正在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。什么是正确的方法呢?

2 个答案:

答案 0 :(得分:1)

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模型为{{1} } model:

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()

关于m2m with through的Django doc。

注意:当您有m2m关系的额外属性时,主要使用players = game.players.all() 。但是如果你没有在代码中有任何额外的东西(也许你正在简化,但以防万一它就是这样),你可以使用through并摆脱{{ 1}}模型一起,django无论如何都会为你创建中间数据库表。

修改

在模板中,您可以:

ManyToManyField

答案 1 :(得分:0)

好吧,我想我找到了我想要的东西。这是我做的(分两步):

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)

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

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