"""ORM Models for the Mai-Star ranking""" import logging from django.core.urlresolvers import reverse from django.db import models from django.db.models.signals import post_delete, post_save from django.utils.translation import ugettext as _ from django.dispatch import receiver from events.models import Event from . import settings, managers class Game(models.Model): """to record a complete game with 6 different players.""" _player_list = list() event = models.ForeignKey(Event, related_name='maistargame_set') comment = models.TextField(_('Comment'), blank=True) player1 = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("Player 1"), related_name='+' ) player1_score = models.SmallIntegerField(_("Score")) player1_placement = models.PositiveSmallIntegerField(editable=False) player2 = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("Player 2"), related_name='+' ) player2_score = models.SmallIntegerField(_("Score")) player2_placement = models.PositiveSmallIntegerField(editable=False) player3 = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("Player 3"), related_name='+' ) player3_score = models.SmallIntegerField(_("Score")) player3_placement = models.PositiveSmallIntegerField(editable=False) player4 = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("Player 4"), related_name='+' ) player4_score = models.SmallIntegerField(_("Score")) player4_placement = models.PositiveSmallIntegerField(editable=False) player5 = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("Player 5"), related_name='+' ) player5_score = models.SmallIntegerField(_("Score")) player5_placement = models.PositiveSmallIntegerField(editable=False) player6 = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("Player 6"), related_name='+' ) player6_score = models.SmallIntegerField(_("Score")) player6_placement = models.PositiveSmallIntegerField(editable=False) confirmed = models.BooleanField( _('Has been confirmed'), default=True, help_text=_('the game only counts whe it has been confirmed') ) player_names = models.CharField(max_length=255, editable=False) season = models.PositiveSmallIntegerField(_('Season'), editable=False, db_index=True) objects = managers.GameManager() class Meta(object): """Display rankings by placement, best players first.""" ordering = ('-event__start', '-id') def __str__(self): return _("Mai-Star Game with {0} from {1:%Y-%m-%d}").format( self.player_names, self.event.start ) def get_absolute_url(self): """URL to the event subpage that lists the Mai-Star games.""" url = reverse('maistar-game-list', kwargs={'event': self.event.pk}) return u'%(url)s#%(pk)d' % {'url': url, 'pk': self.pk} @property def player_list(self): """return a dict() with all 6 player names, scores and placements. It will be cached and reused for more perfomance.""" if self._player_list: return self._player_list for player_no in range(1, 7): self._player_list.append(dict( user=getattr(self, 'player%d' % player_no), score=getattr(self, 'player%d_score' % player_no), placement=getattr(self, 'player%d_placement' % player_no) )) return self._player_list def save(self, **kwargs): """recalculate the rankings before saving. Also self.player_names will be filled with an comma sperated list of all 6 player names.""" game_date = self.event.start.date() player_tuples = [ (self.player1.id, self.player1.username, self.player1_score), (self.player2.id, self.player2.username, self.player2_score), (self.player3.id, self.player3.username, self.player3_score), (self.player4.id, self.player4.username, self.player4_score), (self.player5.id, self.player5.username, self.player5_score), (self.player6.id, self.player6.username, self.player6_score), ] season_start = settings.MAISTAR_SEASON_START.replace( year=game_date.year) # sort player by Score: player_tuples = sorted(player_tuples, key=lambda player: player[2], reverse=True) logging.debug(player_tuples) other_player_ranking = 1 other_player_score = 0 player_names = [] ranking = 1 player_nr = 1 for player_id, player_name, player_score in player_tuples: if player_score == other_player_score: player_ranking = other_player_ranking else: player_ranking = ranking setattr(self, "player%d_id" % player_nr, player_id) setattr(self, "player%d_score" % player_nr, player_score) setattr(self, "player%d_placement" % player_nr, player_ranking) other_player_ranking = player_ranking other_player_score = player_score player_names.append(player_name) player_nr += 1 ranking += 1 self.player_names = ', '.join(player_names) if game_date >= season_start: self.season = season_start.year else: self.season = season_start.year - 1 super(Game, self).save(**kwargs) class Ranking(models.Model): """the player scores in the ladder for one season. """ user = models.ForeignKey(settings.AUTH_USER_MODEL) season = models.PositiveSmallIntegerField(_("Season")) placement = models.PositiveIntegerField(blank=True, null=True) avg_placement = models.PositiveSmallIntegerField(blank=True, null=True) avg_score = models.SmallIntegerField(blank=True, null=True) games_count = models.PositiveSmallIntegerField(default=0) games_good = models.PositiveSmallIntegerField(default=0) games_won = models.PositiveSmallIntegerField(default=0) objects = managers.LadderManager() class Meta(object): """Display rankings by placement, best players first.""" ordering = ('-season', 'placement', 'avg_placement', '-avg_score',) def __str__(self): return "Mai-Star Ranking: {user!s}, Season: {season!d}".format( user=self.user, season=int(self.season) ) def get_absolute_url(self): """URL to the subpage of the user profile, listing all Games.""" return reverse('maistar-player-games', kwargs={ 'username': self.user.username, 'season': self.season }) def recalculate(self): """Recalculate the Mai-Star Ranking for this Player in this Season.""" self.placement = None self.avg_placement = None self.avg_score = None self.games_count = 0 self.games_good = 0 self.games_won = 0 player_score = 0 player_placement = 0 for game in Game.objects.player_games(self.user, self.season): placement = 0 score = 0 for player in ('player1', 'player2', 'player3', 'player4', 'player5', 'player6'): if getattr(game, player) == self.user: placement = getattr(game, "%s_placement" % player) score = getattr(game, "%s_score" % player) player_placement += placement player_score += score self.games_count += 1 self.games_good += 1 if placement <= 3 else 0 self.games_won += 1 if placement == 1 else 0 if self.games_count > 0: self.avg_placement = round(player_placement / self.games_count) self.avg_score = round(player_score / self.games_count) self.save() @receiver([post_delete, post_save], sender=Game) def update_maistar_ranking(sender, **kwargs): """ A Django signal hook to trigger a recalculation of the rankings as soon as a Mai-Star game has been added, deleted, or modified. :param sender: The callback function which will be connected to this signal. See Receiver functions for more information. :param kwargs: """ instance = kwargs['instance'] for player in instance.player_list: ranking, created = Ranking.objects.get_or_create( user=player['user'], season=instance.season ) if created: logging.debug('Created ranking for %s in Season %d', player['user'].username, instance.season) else: logging.debug('Updating ranking for %s in Season %d', player['user'].username, instance.season) ranking.recalculate() Ranking.objects.calculate_rankings(instance.season)