603 lines
24 KiB
Python
603 lines
24 KiB
Python
# -*- encoding: utf-8 -*-
|
|
|
|
from datetime import date, timedelta
|
|
from django.contrib.auth.models import User
|
|
from django.core.cache import cache
|
|
from django.core.exceptions import ValidationError
|
|
from django.core.urlresolvers import reverse
|
|
from django.db import models
|
|
from django.db.models.aggregates import Sum
|
|
from django.utils import timezone
|
|
from django.utils.translation import ugettext as _
|
|
from events.models import Event
|
|
|
|
from . import KYU_RANKS, DAN_RANKS, DAN_RANKS_DICT, MIN_HANCHANS_FOR_LADDER
|
|
from . import logger, set_dirty
|
|
|
|
kyu_dan_rankings = set()
|
|
ladder_rankings = set()
|
|
ladder_seasons = set()
|
|
|
|
|
|
class EventRanking(models.Model):
|
|
'''
|
|
Event Rankings funktionieren genauso wie Season Rankings.
|
|
Sie beschränken sich aber auf einen Event und werden nur dann angestossen,
|
|
wenn der Event als Turnier markiert wurde.
|
|
'''
|
|
user = models.ForeignKey(User)
|
|
event = models.ForeignKey(Event)
|
|
placement = models.PositiveIntegerField(blank=True, null=True)
|
|
avg_placement = models.FloatField(default=4)
|
|
avg_score = models.FloatField(default=0)
|
|
hanchan_count = models.PositiveIntegerField(default=0)
|
|
good_hanchans = models.PositiveIntegerField(default=0)
|
|
won_hanchans = models.PositiveIntegerField(default=0)
|
|
dirty = models.BooleanField(default=True, editable=False)
|
|
|
|
class Meta(object):
|
|
ordering = ('placement', 'avg_placement', '-avg_score',)
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('event-ranking', args=[self.tourney_id])
|
|
|
|
def recalculate(self):
|
|
'''
|
|
Berechnet die durschnittliche Platzierung und Punkte, u.v.m. neu.
|
|
|
|
Diese Daten werden benötigt um die Platzierung zu erstellen. Sie
|
|
können zwar sehr leicht errechnet werden, es macht trotzdem Sinn
|
|
sie zwischen zu speichern.
|
|
|
|
Das Eigenschaft dirty ist ein altes Überbleibsel, um das Objekt
|
|
zur neuberrechnung zu markieren. Mittlerweile wird ein lokaler
|
|
Cache dafür verwendet, das ist schneller.
|
|
'''
|
|
logger.info(u'Recalculate EventRanking for Player %s in %s', self.user, self.event.name) # @IgnorePep8
|
|
event_hanchans = Player.objects.valid_hanchans(user=self.user_id, event=self.event_id) # @IgnorePep8
|
|
aggregator = event_hanchans.aggregate(
|
|
models.Avg('placement'),
|
|
models.Avg('score'),
|
|
models.Count('pk'))
|
|
self.avg_placement = aggregator['placement__avg']
|
|
self.avg_score = aggregator['score__avg']
|
|
self.hanchan_count = aggregator['pk__count']
|
|
self.good_hanchans = event_hanchans.filter(placement__lt=3).count()
|
|
self.won_hanchans = event_hanchans.filter(placement=1).count()
|
|
self.dirty = False
|
|
if self.hanchan_count <= 0:
|
|
self.delete()
|
|
else:
|
|
self.save()
|
|
|
|
|
|
class Hanchan(models.Model):
|
|
'''
|
|
Ein komplette Runde Mahjong, die aus genau 4 Spielern bestehen muss.
|
|
Es werden aber noch andere Tests durchgeführt, ob sie gültig ist.
|
|
Außerdem gehört jede Hanchan zu einer Veranstaltung.
|
|
'''
|
|
comment = models.TextField(_('Comment'), blank=True)
|
|
confirmed = models.BooleanField(_('Has been Confirmed'), default=True, help_text=_('Only valid and confirmed Hanchans will be counted in the rating.')) # @IgnorePep8
|
|
event = models.ForeignKey(Event)
|
|
player_names = models.CharField(max_length=127, editable=False)
|
|
players = models.ManyToManyField(User, through='Player',verbose_name=_('Players')) # @IgnorePep8
|
|
season = models.ForeignKey('LadderSeason', blank=True, null=True, editable=False) # @IgnorePep8
|
|
start = models.DateTimeField(_('Start'), help_text=_('This is crucial to get the right Hanchans that scores')) # @IgnorePep8
|
|
valid = models.BooleanField(_('Is Valid'), default=False)
|
|
|
|
class Meta(object):
|
|
ordering = ('-start',)
|
|
verbose_name = _(u'Hanchan')
|
|
verbose_name_plural = _(u'Hanchans')
|
|
|
|
def __str__(self):
|
|
return "Hanchan am {0:%d.%m.%Y} um {0:%H:%M} ({1})".format(self.start, self.player_names)
|
|
|
|
def check_validity(self):
|
|
'''
|
|
Prüft ob die Hanchan gültig ist.
|
|
|
|
4 Spieler müssen genau 100.000 Punkte erreichen, mehr sind nur erlaubt
|
|
wenn midestens ein Spieler ins Minus (auf 0) geraten ist. Ansonsten
|
|
wird die Hanchan als ungültig markiert aber trotzdem abgespeichert,
|
|
außerdem wird die Begründung zurück gegeben, was nicht gestimmt hat.
|
|
'''
|
|
logger.debug("Hanchan wird geprüft ob er valide ist...")
|
|
if not self.pk:
|
|
self.valid = False
|
|
return
|
|
elif self.player_set.distinct().count() != 4:
|
|
self.valid = False
|
|
return _('For a Hanchan exactly 4 players are needed.')
|
|
|
|
score_sum = self.player_set.aggregate(Sum('score'))['score__sum']
|
|
if score_sum == 100000:
|
|
self.valid = True
|
|
return '4 Spieler, 100.000 Endpunktestand, die Hanchan ist \
|
|
korrekt!'
|
|
elif score_sum > 100000 and self.player_set.filter(score=0):
|
|
self.valid = True
|
|
return 'Endpunktestand über 100.000, aber jemand ist auf 0 \
|
|
gefallen. Die Hanchan stimmt.'
|
|
elif score_sum < 100000:
|
|
self.valid = False
|
|
return 'Endpunktestand weniger als 100.000 Punkte.'
|
|
elif score_sum > 100000 and not self.player_set.filter(score=0):
|
|
self.valid = False
|
|
return 'Endpunktestand über 100.000, aber niemand ist auf 0 \
|
|
gefallen.'
|
|
else:
|
|
self.valid = False
|
|
return 'Wir wissen nicht warum, aber das kann nicht passen...'
|
|
|
|
def clean(self):
|
|
'''
|
|
Prüft ob wichtige Vorrausetzungen gegeben sind und aktualisiert ein
|
|
paar Zwischenspeicher, bevor gespeichert wird.
|
|
|
|
Die Hanchan muss 4 (unterschiedliche) Spieler haben, muss in der
|
|
Veranstaltungzeit liegen, und darf nicht in der Zukunft liegen.
|
|
Ansonsten wird ein ValidationError ausgegeben und die Hanchan nicht
|
|
gespeichert.
|
|
|
|
Die Gültigkeit wird geprüft und die Sasion in der die Hanchan liegt
|
|
wird aktualisert.
|
|
'''
|
|
logger.debug("Hanchan clean() wurde getriggert!")
|
|
|
|
# if self.pk and self.player_set.distinct().count() != 4:
|
|
# raise ValidationError(
|
|
# _('For a Hanchan exactly 4 players are needed.'))
|
|
if self.start and self.start > timezone.now():
|
|
raise ValidationError(_("It's not allowed to enter future games."))
|
|
elif not (self.event.start <= self.start <= self.event.end):
|
|
raise ValidationError(_("Only games during the event are allowed"))
|
|
return self
|
|
|
|
def compute_player_placements(self):
|
|
u'''
|
|
Bestimmt die Platzierung eines der Spieler einer Hanchan und speichert
|
|
diese beim jeweiligen Spieler ab.
|
|
'''
|
|
logger.debug("Berechne die Platzierungen neu...")
|
|
attending_players = self.player_set.select_related('hanchan', 'user')
|
|
attending_players = attending_players.order_by('-score')
|
|
other_player_placement = 0
|
|
other_player_score = 0
|
|
placement = 1
|
|
player_list = []
|
|
logger.info("Compute player pacements for Hanchan Nr. %d", self.pk)
|
|
for player in attending_players:
|
|
player_list.append(player.user.username)
|
|
if player.score <= 0:
|
|
player.placement = 4
|
|
elif player.score == other_player_score:
|
|
player.placement = other_player_placement
|
|
else:
|
|
player.placement = placement
|
|
placement += 1
|
|
other_player_placement = player.placement
|
|
other_player_score = player.score
|
|
player.save(season_id=self.season_id, mark_dirty=True)
|
|
|
|
def get_absolute_url(self):
|
|
'''
|
|
URL zur Hanchanliste des Events wo diese Hanchan gelistet wurde.
|
|
'''
|
|
url = reverse('event-hanchan-list', kwargs={'event': self.event.pk})
|
|
return u'%(url)s#%(pk)d' % {'url': url, 'pk': self.pk}
|
|
|
|
def save(self, **kwargs):
|
|
logger.debug("Hanchan save() wurde getriggert!")
|
|
|
|
self.season = self.season or LadderSeason.objects.get_by_date(self.start)
|
|
if self.pk:
|
|
self.check_validity()
|
|
self.compute_player_placements()
|
|
self.player_names = ', '.join(self.player_set.values_list(
|
|
'user__username', flat=True).order_by('-score'))
|
|
return models.Model.save(self, **kwargs)
|
|
|
|
|
|
class KyuDanRanking(models.Model):
|
|
u'''
|
|
Die Einstufung des Spieles im Kyu bzw. Dan System.
|
|
Im Gegensatz zum Ladder Ranking ist das nicht Saison gebunden.
|
|
daher läuft es getrennt.
|
|
'''
|
|
user = models.OneToOneField(User)
|
|
dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
|
dan_points = models.PositiveIntegerField(default=0)
|
|
kyu = models.PositiveSmallIntegerField(default=10, blank=True, null=True)
|
|
kyu_points = models.PositiveIntegerField(default=0)
|
|
won_hanchans = models.PositiveIntegerField(default=0)
|
|
good_hanchans = models.PositiveIntegerField(default=0)
|
|
hanchan_count = models.PositiveIntegerField(default=0)
|
|
dirty = models.BooleanField(default=True, editable=False)
|
|
wins_in_a_row = 0
|
|
|
|
class Meta(object):
|
|
ordering = ('-dan_points', '-kyu_points',)
|
|
verbose_name = _(u'Kyū/Dan Ranking')
|
|
verbose_name_plural = _(u'Kyū/Dan Rankings')
|
|
|
|
def __unicode__(self):
|
|
if self.dan_points:
|
|
return u"%s - %d. Dan" % (self.user.username, self.dan)
|
|
else:
|
|
return u"%s - %d. Kyu" % (self.user.username, self.kyu)
|
|
|
|
def append_3_in_a_row_bonuspoints(self, hanchan):
|
|
u'''
|
|
Wenn der Spieler 3 Siege in folge hatte, bekommt er so viele Punkte
|
|
das er einen Dan Rang aufsteigt. Dies wird als Kommentar abgespeichert,
|
|
um es besser nachvollziehen zu können.
|
|
'''
|
|
|
|
if self.dan and hanchan.placement == 1:
|
|
self.wins_in_a_row += 1
|
|
else:
|
|
self.wins_in_a_row = 0
|
|
|
|
if self.dan and self.wins_in_a_row > 2:
|
|
logger.info('adding bonuspoints for 3 wins in a row for %s', self.user) # @IgnorePep8
|
|
new_dan_rank = self.dan + 1
|
|
new_dan_points = DAN_RANKS_DICT[new_dan_rank] + 1
|
|
bonus_points = new_dan_points - self.dan_points
|
|
|
|
logger.debug("Stats for %s:", self.user)
|
|
logger.debug("current dan_points: %d", self.dan_points)
|
|
logger.debug("current dan: %d", self.dan)
|
|
logger.debug("min required points for the next dan: %d", new_dan_points) # @IgnorePep8
|
|
logger.debug("bonus points to add: %d", bonus_points)
|
|
|
|
hanchan.dan_points += bonus_points
|
|
hanchan.bonus_points += bonus_points
|
|
hanchan.comment += '3 Siege in Folge: +%d Punkte auf den \
|
|
%d. Dan. ' % (bonus_points, new_dan_rank)
|
|
self.dan_points += bonus_points
|
|
|
|
def append_tournament_bonuspoints(self, player):
|
|
'''
|
|
Prüft ob es die letzte Hanchan in einem Turnier war. Wenn ja werden
|
|
bei Bedarf Bonuspunkte vergeben, falls der Spieler das Turnier gewonnen
|
|
hat.
|
|
:param player: Ein Player Objekt
|
|
'''
|
|
bonus_points = 0
|
|
current_event = player.hanchan.event
|
|
if not current_event.is_tournament:
|
|
return False # Kein Tunrier, den rest können wir uns sparen.
|
|
hanchans_this_event = Player.objects.filter(
|
|
user=self.user, hanchan__event=current_event
|
|
)
|
|
hanchans_this_event = hanchans_this_event.order_by('-hanchan__start')
|
|
last_hanchan_this_event = hanchans_this_event[0].hanchan
|
|
if player.hanchan != last_hanchan_this_event:
|
|
return # Das bruacht nur am Ende eines Turnieres gemacht werden.
|
|
event_ranking = EventRanking.objects.get(
|
|
user=self.user,
|
|
event=current_event)
|
|
if event_ranking.placement == 1:
|
|
bonus_points += 4
|
|
player.comment += '+4 Punkte für den Sieg des Turnieres. '
|
|
if event_ranking.avg_placement == 1:
|
|
bonus_points += 8
|
|
player.comment += '+8 Pkt: alle Spiele des Turnieres gewonnen. '
|
|
player.bonus_points += bonus_points
|
|
if bonus_points and self.dan:
|
|
player.dan_points += bonus_points
|
|
self.dan_points += bonus_points
|
|
elif bonus_points:
|
|
player.kyu_points += bonus_points
|
|
self.kyu_points += bonus_points
|
|
|
|
def get_absolute_url(self):
|
|
if self.dan:
|
|
return reverse('player-dan-score', args=[self.user.username])
|
|
else:
|
|
return reverse('player-kyu-score', args=[self.user.username])
|
|
|
|
def recalculate(self):
|
|
'''
|
|
Fetches all valid Hanchans from this Player and recalculates his
|
|
Kyu/Dan Ranking.
|
|
'''
|
|
logger.debug("recalculating Kyu/Dan punkte for %s...", self.user)
|
|
valid_hanchans = Player.objects.valid_hanchans(user=self.user_id)
|
|
valid_hanchans = valid_hanchans.order_by('hanchan__start')
|
|
self.kyu_points = 0
|
|
self.dan_points = 0
|
|
self.dan = None
|
|
self.kyu = 10
|
|
self.hanchan_count = valid_hanchans.count()
|
|
self.won_hanchans = valid_hanchans.filter(placement=1).count()
|
|
self.good_hanchans = valid_hanchans.filter(placement=2).count()
|
|
logger.info("Neuberechnung der Punkte von %s", self.user)
|
|
|
|
for hanchan in valid_hanchans:
|
|
self.update_points(hanchan)
|
|
self.append_tournament_bonuspoints(hanchan)
|
|
self.update_rank()
|
|
self.append_3_in_a_row_bonuspoints(hanchan)
|
|
self.update_rank()
|
|
hanchan.save(force_update=True, mark_dirty=False)
|
|
self.save(force_update=True)
|
|
|
|
def update_points(self, player):
|
|
'''
|
|
Berechne die Kyu bzw. Dan Punkte für ein Spiel neu.
|
|
:param player: Das Player Objekt das neuberechnet werden soll.
|
|
'''
|
|
player.bonus_points = 0
|
|
player.comment = ""
|
|
player.dan_points = None
|
|
player.kyu_points = None
|
|
|
|
if player.hanchan.event.is_tournament:
|
|
tourney_points = 4 - player.placement
|
|
if self.dan:
|
|
player.dan_points = tourney_points
|
|
else:
|
|
player.kyu_points = tourney_points
|
|
elif self.dan:
|
|
if player.score >= 60000:
|
|
player.dan_points = 3
|
|
elif player.score == 0:
|
|
player.dan_points = -3
|
|
elif player.placement == 1:
|
|
player.dan_points = 2
|
|
elif player.placement == 2:
|
|
player.dan_points = 1
|
|
elif player.placement == 3:
|
|
player.dan_points = -1
|
|
elif player.placement == 4:
|
|
player.dan_points = -2
|
|
elif player.score >= 60000:
|
|
player.kyu_points = 3
|
|
elif player.score >= 30000:
|
|
player.kyu_points = 1
|
|
elif player.score < 10000:
|
|
player.kyu_points = -1
|
|
else:
|
|
player.kyu_points = 0
|
|
|
|
# Add the hanchans points to the players score
|
|
if player.dan_points:
|
|
if self.dan_points + player.dan_points < 0:
|
|
# Only substract so much points that player has 0 Points:
|
|
player.dan_points -= (self.dan_points + player.dan_points)
|
|
self.dan_points += player.dan_points
|
|
elif player.kyu_points:
|
|
if self.kyu_points + player.kyu_points < 0:
|
|
# Only substract so much points that player has 0 Points:
|
|
player.kyu_points -= (self.kyu_points + player.kyu_points)
|
|
self.kyu_points += player.kyu_points
|
|
|
|
def update_rank(self):
|
|
if self.dan and self.dan_points < 0:
|
|
self.dan_points = 0
|
|
self.dan = 1
|
|
elif self.dan:
|
|
old_dan = self.dan
|
|
for min_points, dan_rank in DAN_RANKS:
|
|
if self.dan_points > min_points:
|
|
self.dan = dan_rank
|
|
break
|
|
if self.dan > old_dan:
|
|
self.wins_in_a_row = 0
|
|
elif self.kyu_points < 1:
|
|
self.kyu_points = 0
|
|
self.kyu = 10
|
|
elif self.kyu_points > 50:
|
|
self.dan = 1
|
|
self.kyu = None
|
|
self.dan_points = 0
|
|
self.kyu_points = 0
|
|
else:
|
|
for min_points, kyu_rank in KYU_RANKS:
|
|
if self.kyu_points > min_points:
|
|
self.kyu = kyu_rank
|
|
break
|
|
|
|
|
|
class LadderRanking(models.Model):
|
|
user = models.ForeignKey(User)
|
|
season = models.ForeignKey('LadderSeason')
|
|
placement = models.PositiveIntegerField(blank=True, null=True)
|
|
avg_placement = models.FloatField(blank=True, null=True)
|
|
avg_score = models.FloatField(blank=True, null=True)
|
|
hanchan_count = models.PositiveIntegerField(default=0)
|
|
good_hanchans = models.PositiveIntegerField(default=0)
|
|
won_hanchans = models.PositiveIntegerField(default=0)
|
|
dirty = models.BooleanField(default=True, editable=False)
|
|
|
|
class Meta(object):
|
|
ordering = ('placement', 'avg_placement', '-avg_score',)
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('player-ladder-score', args=[self.user.username])
|
|
|
|
def recalculate(self):
|
|
logger.info(u'Recalculate LadderRanking for Player %s in Season %s', self.user, self.season) # @IgnorePep8
|
|
ladder_hanchans = Player.objects.ladder_hanchans(
|
|
self.user_id, self.season_id)
|
|
aggregate = ladder_hanchans.aggregate(
|
|
models.Avg('placement'),
|
|
models.Avg('score'),
|
|
models.Count('pk')
|
|
)
|
|
self.dirty = False
|
|
self.placement = None
|
|
self.hanchan_count = aggregate['pk__count']
|
|
self.avg_placement = aggregate['placement__avg']
|
|
self.avg_score = aggregate['score__avg']
|
|
self.good_hanchans = ladder_hanchans.filter(placement=2).count()
|
|
self.won_hanchans = ladder_hanchans.filter(placement=1).count()
|
|
self.save(force_update=True)
|
|
|
|
|
|
class LadderSeasonManager(models.Manager):
|
|
|
|
def current(self):
|
|
'''
|
|
Returns the current season and caches the result for 12 hours
|
|
'''
|
|
current_season = cache.get('current_mahjong_season')
|
|
if not current_season:
|
|
try:
|
|
today = date.today()
|
|
current_season = self.filter(start__lte=today, end__gte=today)
|
|
current_season = current_season[0]
|
|
except IndexError:
|
|
current_season = None
|
|
cache.set('current_mahjong_season', current_season, 4320)
|
|
return current_season
|
|
|
|
def get_by_date(self, deadline):
|
|
'''
|
|
returns the season that where running on the given date.
|
|
:param deadline: the date you're intrested in
|
|
'''
|
|
try:
|
|
season = self.filter(start__lte=deadline, end__gte=deadline)
|
|
return season[0]
|
|
except IndexError:
|
|
return None
|
|
|
|
|
|
class LadderSeason(models.Model):
|
|
u'''
|
|
Eine Saison für das Kasu interne Ladder-Ranking.
|
|
'''
|
|
name = models.CharField(max_length=100)
|
|
start = models.DateField()
|
|
end = models.DateField()
|
|
objects = LadderSeasonManager()
|
|
dirty = models.BooleanField(default=True, editable=False)
|
|
|
|
class Meta(object):
|
|
ordering = ('start',)
|
|
verbose_name = _('Ladder Season')
|
|
verbose_name_plural = _('Ladder Seasons')
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
def recalculate(self):
|
|
logger.info(u'Recalculate LadderSeason %s', self.name)
|
|
self.ladderranking_set.update(placement=None)
|
|
placement = 1
|
|
ladder_rankings = self.ladderranking_set.filter(hanchan_count__gt=MIN_HANCHANS_FOR_LADDER) # @IgnorePep8
|
|
ladder_rankings = ladder_rankings.order_by('avg_placement', '-avg_score') # @IgnorePep8
|
|
for ranking in ladder_rankings:
|
|
ranking.placement = placement
|
|
ranking.save(force_update=True)
|
|
placement += 1
|
|
self.dirty = False
|
|
self.save(force_update=True)
|
|
|
|
|
|
class PlayerManager(models.Manager):
|
|
use_for_related_fields = True
|
|
|
|
def dan_hanchans(self, user=None):
|
|
dan_hanchans = self.valid_hanchans(user)
|
|
dan_hanchans = dan_hanchans.filter(dan_points__isnull=False)
|
|
return dan_hanchans.select_related()
|
|
|
|
def kyu_hanchans(self, user=None):
|
|
queryset = self.valid_hanchans(user).order_by('-hanchan__start')
|
|
queryset = queryset.filter(kyu_points__isnull=False).select_related()
|
|
return queryset
|
|
|
|
def ladder_hanchans(self, user=None, season=None, num_hanchans=None, max_age=None): # @IgnorePep8
|
|
queryset = self.valid_hanchans(user).order_by('-hanchan__start')
|
|
queryset = queryset.select_related()
|
|
season = season or LadderSeason.objects.current()
|
|
|
|
if season:
|
|
queryset = queryset.filter(hanchan__season_id=season)
|
|
if user:
|
|
queryset = queryset.filter(user=user)
|
|
if max_age:
|
|
expiration_date = date.today() - timedelta(days=max_age)
|
|
queryset = queryset.filter(start__gt=expiration_date)
|
|
if num_hanchans:
|
|
hanchan_list = queryset.values_list('id', flat=True)[:num_hanchans]
|
|
hanchan_list = set(hanchan_list)
|
|
queryset = self.valid_hanchans(user).filter(id__in=hanchan_list)
|
|
queryset = queryset.select_related().order_by('-hanchan__start')
|
|
return queryset
|
|
|
|
def hanchan_stats(self, queryset=None):
|
|
queryset = queryset or self.get_query_set()
|
|
self.num_hanchans = queryset.count()
|
|
self.won_hanchans = queryset.filter(placement=1).count()
|
|
self.good_hanchans = queryset.filter(placement__lt=3).count()
|
|
|
|
def non_counting_hanchans(self, user=None):
|
|
queryset = self.exclude(hanchan__valid=True, hanchan__confirmed=True)
|
|
if user:
|
|
queryset = queryset.filter(user=user)
|
|
queryset = queryset.select_related()
|
|
return queryset.order_by('-hanchan__start')
|
|
|
|
def valid_hanchans(self, user=None, event=None):
|
|
queryset = self.filter(hanchan__valid=True, hanchan__confirmed=True)
|
|
if user:
|
|
queryset = queryset.filter(user=user)
|
|
if event:
|
|
queryset = queryset.filter(hanchan__event_id=event)
|
|
queryset = queryset.select_related()
|
|
return queryset.order_by('-hanchan__start')
|
|
|
|
|
|
class Player(models.Model):
|
|
hanchan = models.ForeignKey(Hanchan)
|
|
user = models.ForeignKey(User)
|
|
score = models.PositiveIntegerField(default=0)
|
|
placement = models.PositiveSmallIntegerField(blank=True, null=True, default=None) # @IgnorePep8
|
|
kyu_points = models.PositiveSmallIntegerField(blank=True, null=True, default=None) # @IgnorePep8
|
|
dan_points = models.PositiveSmallIntegerField(blank=True, null=True, default=None) # @IgnorePep8
|
|
bonus_points = models.PositiveSmallIntegerField(blank=True, null=True, default=0) # @IgnorePep8
|
|
comment = models.TextField(_('Comment'), blank=True)
|
|
objects = PlayerManager()
|
|
|
|
class Meta(object):
|
|
unique_together = ('hanchan', 'user')
|
|
ordering = ['-score']
|
|
|
|
def __str__(self):
|
|
return "{0}'s Punkte vom {1: %d.%m.%Y um %H:%M}".format(self.user.username, self.hanchan.start)
|
|
|
|
def save(self, mark_dirty=True, season_id=None, *args, **kwargs):
|
|
season_id = season_id or self.hanchan.season_id
|
|
super(Player, self).save(*args, **kwargs)
|
|
|
|
# Set the correct Ladder Ranking to dirty, create it if necessary
|
|
if mark_dirty:
|
|
logger.debug("Marking %s's kyu/dan for recalculation", self.user)
|
|
set_dirty(user=self.user_id)
|
|
else:
|
|
return self
|
|
if season_id:
|
|
logger.debug("Marking %s's season no. %i ranking for recalculation.", self.user, season_id) # @IgnorePep8
|
|
set_dirty(season=season_id, user=self.user_id)
|
|
logger.debug("Marking season no %i for recalculation.", season_id)
|
|
set_dirty(season=season_id)
|
|
if self.hanchan.event.is_tournament:
|
|
logger.debug("Marking tournament %s for recalculation.", self.hanchan.event) # @IgnorePep8
|
|
set_dirty(event=self.hanchan.event_id, user=self.user_id)
|
|
return self
|
|
|
|
|
|
def update_ranking_delete(sender, instance, **kwargs): # @UnusedVariable
|
|
for player in instance.player_set.all():
|
|
set_dirty(user=player.user_id)
|
|
if instance.season_id:
|
|
set_dirty(season=instance.season_id, user=player.user_id)
|
|
|
|
models.signals.pre_delete.connect(update_ranking_delete, sender=Hanchan)
|