Reputation: 16417
I'm writing a small web game in Django. Each game has two players. To manage the games and participants, I have something like this (very simplified):
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')
So I'm able to easily query for games where the current player is taking part of:
Games.objects.with_player(user)
But to show in the list of games view, I would like to also include the name of the other player.
I've tried a few things and seems like this is possible to do by adding an .extra()
and some raw SQL, but I'd like to try to avoid that as much as possible.
I've tried using annotate with something like this:
def add_other_player(self, user):
return self.annotate(other_player=PlayerInGame.objects.all(). \
filter(~Q(player=user)))
But without much luck. Also looked into fetch_related
and select_related
. What would be the right way to do this?
Upvotes: 2
Views: 1876
Reputation: 16417
OK, I think I found what I was looking for. Here is what I did (in a two step process):
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)
This way, I get a "field" in the result with the reverse foreign key relationship and I am able to filter on it too.
Not sure if there is a good way to avoid the two steps, but that works fine for me.
Upvotes: 0
Reputation: 25549
fetch_related
and select_related
are just optimization methods, the relationship is always there, these 2 methods just do the queries in one batch.
Since you are most likely going to loop through the queryset games, you could do:
games = Games.objects.with_player(user)
for game in games:
players = game.players.values_list('player__first_name',
'player__last_name')
The disadvantage of this is that you can specify as many fields as you want to display for a player
, but it doesn't give you a whole player object. I think it actually makes more sense to create a ManyToMany relationship between Game
and Player
, because sounds like it's what you are doing, with PlayerInGame
model as through
model:
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()
Then to get the players of a game you could do:
players = game.players.all()
Django doc about m2m with through.
Note: through
is mostly used when you have extra attributes for the m2m relationship. But if you don't have anything extra like in your code(maybe you were simplifying but just in case it's exactly like that), you could just use the ManyToManyField
and get rid of PlayerInGame
model all together, django would create the intermediate database table for you anyway.
Edit:
In template you do:
{% 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 %}
Upvotes: 1