Complex django annotation - django

I have the following models:-
class Group(models.Model):
name = models.CharField(max_length=200)
class Game(models.Model):
group = models.ForeignKey(Group, related_name='games')
date = models.DateField()
players = models.ManyToManyField(
Player, through='GameMembership', related_name='games'
)
class GameMembership(models.Model):
game = models.ForeignKey(Game, related_name='memberships')
player = models.ForeignKey(Player, related_name='memberships')
selected = models.BooleanField(default=False)
injured = models.BooleanField(default=False)
class Player(models.Model):
group = models.ForeignKey('groups.Group', related_name='players')
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='players')
I want to annotate all of the players in the group with a score which is calculated as follows:-
For the last 10 games for which a player wasn't injured, score 5 if they were selected.
I can do this using Sum/Case/When if I ignore the "wasn't injured" clause, by using a manager method on Player which looks something like this:-
def with_availability_scores(self, group):
for_games = group.games.reverse()[:10]
return self.annotate(
availability_score=Sum(Case(
When(
memberships__selected=True, memberships__game__in=for_games,
then=5)
default=0, output_field=IntegerField()))
)
But the addition of the "injured" clause means that I can't use that for_games variable like that to begin with.
I suspect it can be done using Subquery and OuterRef but I can't quite figure out the exact syntax I need.
Any ideas?

Related

Django search for child models via foreign key

I have a models.py file that more or less looks like this:
class Match(models.Model):
match_id = models.BigIntegerField()
class Team(models.Model):
team_id = models.CharField(max_length=4)
match = models.ForeignKey(
Match,
related_name='teams',
on_delete=models.CASCADE,
)
win = models.BooleanField()
class Player(models.Model):
player_id = models.IntegerField()
team = models.ForeignKey(
Team,
related_name='players'
on_delete=models.CASCADE,
)
For each match, I want to find the two teams that played, and for each team, I want to find the players that were on the team. This is what I tried so far:
match_id = 123456789
match_info = Match_model.objects.get(match_id=match_id)
red_info = Team_model.objects.get(match=match_info, team_id='Red')
blue_info = Team_model.objects.get(match=match_info, team_id='Blue')
red_players = Player_model.objects.filter(team=red_info)
blue_players = Player_model.objects.filter(team=blue_info)
but Django is giving me the error:
Team matching query does not exist.
How would I go about fixing this error? Any pointers on how to correct my queries would be greatly appreciated!
try:
red_info = Team_model.objects.get(match=match_info, team_id='Red')
blue_info = Team_model.objects.get(match=match_info, team_id='Blue')
except Team_model.DoesNotExist:
pass
else:
red_players = Player_model.objects.filter(team=red_info)
blue_players = Player_model.objects.filter(team=blue_info)

Count more than one occurrence in a Django query

I have two models: Players and Tournaments.
Players have also a type of game they play (dota, lol, magic, etc). They can participate in many tournaments at the same time (only once per Tournament). To manage the inscriptions, I use another model called TournamentMatch, that creates a new object for every inscription with the ID of the tournament and the player.
class Player(models.Model):
name = models.CharField(_('name'), max_length=150, null=True, blank=True)
email = models.EmailField(_('email address'), unique=True)
is_dota_player = models.BooleanField(default=False)
is_lol_player = models.BooleanField(default=False)
is_magic_player = models.BooleanField(default=False)
class Tournament(models.Model):
name = models.CharField(max_length=200)
date_start = models.DateField()
date_end = models.DateField()
class TournamentMatch(models.Model):
tournament = models.ForeignKey(Tournament)
player = models.ForeignKey(Player)
date_added = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
I want to count the number of Players that have more than two inscription to tournaments and are, for example, dota players.
I can easily achieve this with a for loop for every player, but for perfomance reasons, I want to achieve this using a Django query.
For example, if I want to count the dota players that have one or more inscription, I will do:
TournamentMatch.objects.filter(
player__is_dota_player=True
).distinct(
'player'
).count()
I'm sure is possible but I don't know how to count every ocurrence of distinct players on tournaments and only filter those that have more than one (and not only one).
I will appreciate any help or pointers!
# subquery for counting match per player (note OuterRef which will be linked to the outer query row)
player_match_count_subquery = TournamentMatch.objects.filter(player_id=OuterRef('pk'), ).\
annotate(match_count=Count('pk'), ).values('match_count', )[:1]
# the main (outer) query which uses subquery output for filtering
Players.objects.filter(is_dota_player=True, ).\
annotate(match_count=Subquery(player_match_count_subquery), ).\
filter(match_count__gt=2, )
https://docs.djangoproject.com/en/2.2/ref/models/expressions/#subquery-expressions
this code will produce logically this SQL query:
SELECT p.*, tm.match_count
FROM players p
CROSS APPLY (
SELECT COUNT(id) match_count
FROM tournamentmatch tm
WHERE tm.player_id = p.id
) tm
WHERE p.is_dota_player = $true
AND tm.match_count > 2

django max and groupby together

I have a model which looks like this
player_info, game, score, creation_date
I want to fetch the records with the highest score of each player for a particular game.
Any help is appreciated.
EDIT:
class Game(models.Model):
name = models.CharField(max_length=50)
description = models.TextField(max_length=1000)
logo = models.URLField()
resource_info = models.URLField()
cost = models.DecimalField(default=0.0, decimal_places=2, max_digits=10)
modified_date = models.DateTimeField(auto_now_add=True)
developer_info = models.ForeignKey(User, related_name='uploaded_games', on_delete=models.CASCADE)
class Score(models.Model):
game_info = models.ForeignKey(Game, related_name='game_info',on_delete=models.CASCADE)
player_info = models.ForeignKey(User, related_name='player_info', on_delete=models.CASCADE)
last_played = models.DateTimeField(auto_now=True)
score = models.BigIntegerField(default=0)
If I understand correctly:
You want to filter your Scores by a Game.
And then you want to GROUP BY User and get the MAX score
The GROUP BY part is a bit tricky, but you can achieve it using values + annotate.
Try something like this:
from django.db.models import Max
Score.objects.filter(game_info=...)
.values('player_info').annotate(max=Max('score'))
More info an examples: https://docs.djangoproject.com/en/1.10/topics/db/aggregation/

Filter by ForeignKey backwards and forwards

I have 3 model classes:
class Team(models.Model):
name = models.CharField(max_length=100, default="", blank=True, null=True)
number = models.IntegerField()
class Position(models.Model):
match = models.ForeignKey('Match')
color = models.CharField(max_length=5)
number = models.IntegerField()
team = models.ForeignKey('Team')
class Match(models.Model):
type = models.CharField(max_length=3)
number = models.IntegerField()
red_score = models.IntegerField(default=0)
blue_score = models.IntegerField(default=0)
match_events = models.TextField(max_length=1000)
Using the models as they are right now, how would I able to get a list of Matches a Team has won (i.e. if the Team belongs to a red Position, add it to the list if its Match has a red_score > blue_score)
Sorry if that's confusing. I can try to clarify if you have any specific questions.
Thanks!
Simplest way to do this:
Match.objects.filter(position__color='red', red_score__gt=F('blue_score'))
You may want to move Position model to the bottom, to remove apostrophes from foreign key related models names.
This is as well very good example to use ManyToMany relation:
class Team(models.Model):
name = models.CharField(max_length=100, default="", blank=True, null=True)
number = models.IntegerField()
class Match(models.Model):
type = models.CharField(max_length=3)
number = models.IntegerField()
red_score = models.IntegerField(default=0)
blue_score = models.IntegerField(default=0)
match_events = models.TextField(max_length=1000)
teams = models.ManyToManyField(Team, through='Position',
related_name='matches')
class Position(models.Model):
match = models.ForeignKey(Match)
color = models.CharField(max_length=5)
number = models.IntegerField()
team = models.ForeignKey(Team)
In this case, you will get few more options to simplify data access, e.g.:
if team is your actual team and match single match selected somewhere earlier in the code, this is valid:
team.matches.filter(red_score__gt=F('blue_score')) # all won matches of this team
match.teams.all() # teams involved in this match

Django getting field names from different models

Guys,
Is there an easy way to return different fields names from different models by chaining joins?
My model:
Class Item(models.Model):
item_code = models.CharField(max_length=10)
name = models.CharField(max_length=255)
...
Class Stock(models.Model):
item_code = models.ForeignKey( Item )
userid = models.ForeignKey( User )
qty = models.IntegerField()
...
I want to select " Item.Item_code, Item.name, Stock.qty where Stock.userid=2 and Item.item_code = Stock.Item_Code"
How do i do this in Django?
Gath
I want to select " Item.Item_code, Item.name, Stock.qty where Stock.userid=2 and Item.item_code = Stock.Item_Code"
You can pick these specific fields only using one SQL, provided you start from the Stock model. For instance
q = Stock.objects.select_related('userid', 'item_code').filter(
userid__id = 2).values('item_code__item_code', 'item_code__name', 'qty')
This will help if you want to limit the data and then number of queries. If you are not concerned with this then do:
q = Stock.objects.filter(userid__id = 2)
for stock in q:
print stock.item_code.item_code
print stock.item_code.name
print stock.qty
This will return a queryset with only those fields you have chose using values. You can then iterate through it.
PS: Re: your models.
Class Stock(models.Model):
item_code = models.ForeignKey( Item )
userid = models.ForeignKey( User )
qty = models.IntegerField()
...
It is a good idea to use the model name in lower case for FK relationships. For e.g. you ought to write:
Class Stock(models.Model):
item = models.ForeignKey( Item ) # Changed
user = models.ForeignKey( User ) # Changed
qty = models.IntegerField()
...
You can also use this:
Stock.objects.filter(user=2).values('item__item_code', 'item__name')
First of all change fileds names
Read this very carefuly http://docs.djangoproject.com/en/dev/ref/models/querysets/
Class Item(models.Model):
item_code = models.CharField(max_length=10)
name = models.CharField(max_length=255)
...
Class Stock(models.Model):
item = models.ForeignKey( Item )
user = models.ForeignKey( User )
qty = models.IntegerField()
...
#view
stocks = Stock.objects.filter(user=2)
for stock in stocks:
print stock.item.item_code, stock.item.name
#one query version