Milestone 08-14

* Mahjong Ranking wurde stark vereinfacht um Fehler besser vorzubeugen.
* Online WYSIWYG Editor auf CKEditor umgeändert, damit online bearbeiten für unbedarfte besser funktioniert.
* Viele kleine Optimierungen am CSS für bessere Performance.
* CSS wird jetzt aus LESS Code generiert
* Für dise Arbeit wird jetzt grunt und node package management lokal verwendet.
This commit is contained in:
Christian Berg
2015-08-23 16:37:39 +02:00
parent 8981d4b261
commit a7bfd2157d
279 changed files with 14708 additions and 2429 deletions

View File

@@ -2,19 +2,19 @@
# TODO: Rankings archiv Flag erstellen, womit sie nicht mehr neuberechnet werden dürfen.
from datetime import date, timedelta
from datetime import date
from django.conf import settings
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, logger, set_dirty
from . import KYU_RANKS, DAN_RANKS, DAN_RANKS_DICT, logger, set_dirty, MIN_HANCHANS_FOR_LADDER
from . import managers
kyu_dan_rankings = set()
ladder_rankings = set()
@@ -52,8 +52,8 @@ class EventRanking(models.Model):
"""
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
event_hanchans = Player.objects.confirmed_hanchans(user=self.user_id,
event=self.event_id) # @IgnorePep8
aggregator = event_hanchans.aggregate(
models.Avg('placement'),
models.Avg('game_score'),
@@ -75,18 +75,54 @@ class Hanchan(models.Model):
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)
start = models.DateTimeField(_('Start'),
help_text=_('This is crucial to get the right Hanchans that scores')
)
player1 = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user_hanchan+', verbose_name=_('Player 1'))
player1_input_score = models.IntegerField(_('Score'))
player1_game_score = models.PositiveIntegerField(_('Score'), default=0, editable=False)
player1_placement = models.PositiveSmallIntegerField(default=0, editable=False)
player1_kyu_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player1_dan_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player1_bonus_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player1_comment = models.CharField(_('Comment'), blank=True, max_length=255, editable=False)
player2 = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user_hanchan+', verbose_name=_('Player 2'))
player2_input_score = models.IntegerField(_('Score'))
player2_game_score = models.PositiveIntegerField(_('Score'), default=0, editable=False)
player2_placement = models.PositiveSmallIntegerField(default=0, editable=False)
player2_kyu_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player2_dan_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player2_bonus_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player2_comment = models.CharField(_('Comment'), blank=True, max_length=255, editable=False)
player3 = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user_hanchan+', verbose_name=_('Player 3'))
player3_input_score = models.IntegerField(_('Score'))
player3_game_score = models.PositiveIntegerField(_('Score'), default=0, editable=False)
player3_placement = models.PositiveSmallIntegerField(default=0, editable=False)
player3_kyu_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player3_dan_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player3_bonus_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player3_comment = models.CharField(_('Comment'), blank=True, max_length=255, editable=False)
player4 = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user_hanchan+', verbose_name=_('Player 4'))
player4_input_score = models.IntegerField(_('Score'))
player4_game_score = models.PositiveIntegerField(_('Score'), default=0, editable=False)
player4_placement = models.PositiveSmallIntegerField(default=0, editable=False)
player4_kyu_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player4_dan_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player4_bonus_points = models.SmallIntegerField(blank=True, null=True, editable=False)
player4_comment = models.CharField(_('Comment'), blank=True, max_length=255, editable=False)
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.')
)
player_names = models.CharField(max_length=255, editable=False)
players = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through='Player',
verbose_name=_('Players')
)
season = models.ForeignKey('LadderSeason', blank=True, null=True, editable=False)
start = models.DateTimeField(_('Start'), help_text=_('This is crucial to get the right Hanchans that scores'))
valid = models.BooleanField(_('Is Valid'), default=False)
season = models.PositiveSmallIntegerField(_('Season'), editable=False, db_index=True)
objects = managers.HanchanManager()
class Meta(object):
ordering = ('-start',)
@@ -94,77 +130,48 @@ class Hanchan(models.Model):
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('input_score'))['input_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:
self.valid = False
return 'Endpunktestand mehr 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...'
return _("Hanchan from {0:%Y-%m-%d} at {0:%H:%M} with {1}").format(
self.start, self.player_names
)
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.
Check if 4 different Players are attending the Game,
if the Game between the opening hours and the Hanchan has a total of 100.000 Points.
"""
super(Hanchan, self).clean()
errors = {}
score_sum = 0
player_set = set()
# if self.pk and self.player_set.distinct().count() != 4:
# raise ValidationError(
# _('For a Hanchan exactly 4 players are needed.'))
if not self.event_id:
raise ValidationError(_("Hanchan has no event"))
elif not self.start:
raise ValidationError(_("Hanchan has no start time set"))
elif self.start > timezone.now():
raise ValidationError(_("It's not allowed to enter future games."))
try:
self.clean_fields()
except ValidationError as e:
print e.update_error_dict(errors)
return
for i in xrange(1, 5):
player = getattr(self, 'player%d' % i)
input_score = getattr(self, 'player%d_input_score' % i)
score_sum += input_score
game_score = input_score if input_score > 0 else 0
if player in player_set:
errors['player%d' % i] = _("%s can't attend the same game multiple times") % player
else:
player_set.add(player)
setattr(self, 'player%d_game_score' % i, game_score)
# Check if the game was played during the event
if self.start > timezone.now():
errors['start'] = _("Games in the future may not be added, Dr. Brown")
elif not (self.event.start <= self.start <= self.event.end):
raise ValidationError(_("Only games during the event are allowed"))
return self
errors['start'] = _("Only games during the event are allowed")
elif score_sum < 100000:
errors['player4_input_score'] = _('Gamescore is lower then 100.000 Pt.')
elif score_sum > 100000:
errors['player4_input_score'] = _('Gamescore is over 100.000 Pt.')
if errors:
raise ValidationError(errors)
def compute_player_placements(self):
u"""
@@ -172,42 +179,85 @@ class Hanchan(models.Model):
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('-game_score')
player_names = []
other_player_placement = 0
other_player_score = 0
other_player_game_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.game_score <= 0:
player.placement = 4
elif player.game_score == other_player_score:
player.placement = other_player_placement
player_nr = 1
for player in self.player_list:
if player['game_score'] == other_player_game_score:
player['placement'] = other_player_placement
else:
player.placement = placement
player['placement'] = placement
setattr(self, "player%d" % player_nr, player['user'])
setattr(self, "player%d_input_score" % player_nr, player['input_score'])
setattr(self, "player%d_game_score" % player_nr, player['game_score'])
setattr(self, "player%d_placement" % player_nr, player['placement'])
setattr(self, "player%d_kyu_points" % player_nr, player['kyu_points'])
setattr(self, "player%d_dan_points" % player_nr, player['dan_points'])
setattr(self, "player%d_bonus_points" % player_nr, player['bonus_points'])
setattr(self, "player%d_comment" % player_nr, player['comment'])
other_player_placement = player['placement']
other_player_game_score = player['game_score']
player_names.append(player['user'].username)
player_nr += 1
placement += 1
other_player_placement = player.placement
other_player_score = player.game_score
player.save(season_id=self.season_id, mark_dirty=True)
self.player_names = ', '.join(player_names)
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}
return "{url:s}#{id:d}".format(
url=reverse('event-hanchan-list', kwargs={'event':self.event_id}),
id= self.pk
)
def get_playerdata(self, user):
"""i small workaround to access score, placement and points of a
specific user prominent from a in the user templates"""
for player in ('player1', 'player2', 'player3', 'player4'):
if getattr(self, player) == user:
self.user = user
self.input_score = getattr(self, '%s_input_score' % player)
self.game_score = getattr(self, '%s_game_score' % player)
self.placement = getattr(self, '%s_placement' % player)
self.kyu_points = getattr(self, '%s_kyu_points' % player)
self.dan_points = getattr(self, '%s_dan_points' % player)
self.bonus_points = getattr(self, '%s_bonus_points' % player)
self.player_comment = getattr(self, '%s_comment' % player)
def update_playerdata(self, user, **kwargs):
"""i small workaround to access score, placement of a specific user
prominent from a in the user templates"""
for player in ('player1', 'player2', 'player3', 'player4'):
if getattr(self, player) == user:
setattr(self, '%s_input_score' % player, self.input_score)
setattr(self, '%s_game_score' % player, self.game_score)
setattr(self, '%s_placement' % player, self.placement)
setattr(self, '%s_kyu_points' % player, self.kyu_points)
setattr(self, '%s_dan_points' % player, self.dan_points)
setattr(self, '%s_bonus_points' % player, self.bonus_points)
setattr(self, '%s_comment' % player, self.player_comment)
@property
def player_list(self):
player_list = []
for i in xrange(1, 5):
player_list.append({
'user': getattr(self, 'player%d' % i),
'input_score': getattr(self, 'player%d_input_score' % i),
'game_score': getattr(self, 'player%d_game_score' % i),
'placement': getattr(self, 'player%d_placement' % i),
'kyu_points': getattr(self, 'player%d_kyu_points' % i),
'dan_points': getattr(self, 'player%d_dan_points' % i),
'bonus_points': getattr(self, 'player%d_bonus_points' % i),
'comment': getattr(self, 'player%d_comment' % i),
})
# sort player by Score:
return sorted(player_list, key=lambda player: player['input_score'], reverse=True)
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('-input_score'))
self.season = self.event.mahjong_season or date.today().year
self.full_clean()
self.compute_player_placements()
return models.Model.save(self, **kwargs)
@@ -225,10 +275,10 @@ class KyuDanRanking(models.Model):
won_hanchans = models.PositiveIntegerField(default=0)
good_hanchans = models.PositiveIntegerField(default=0)
hanchan_count = models.PositiveIntegerField(default=0)
wins_in_a_row = 0
legacy_date = models.DateField(blank=True, null=True)
legacy_dan_points = models.PositiveIntegerField(default=0)
legacy_kyu_points = models.PositiveIntegerField(default=0)
wins_in_a_row = 0
class Meta(object):
ordering = ('-dan_points', '-kyu_points',)
@@ -243,19 +293,17 @@ class KyuDanRanking(models.Model):
def append_3_in_a_row_bonuspoints(self, hanchan):
u"""
Wenn der Spieler 3 Siege in folge hatte, bekommt er so viele Punkte
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
if self.dan and self.wins_in_a_row >= 3:
logger.info('adding bonuspoints for 3 wins in a row for %s', self.user)
new_dan_rank = self.dan + 1
new_dan_points = DAN_RANKS_DICT[new_dan_rank] + 1
bonus_points = new_dan_points - self.dan_points
@@ -263,50 +311,46 @@ class KyuDanRanking(models.Model):
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("min required points for the next dan: %d", new_dan_points)
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)
hanchan.player_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):
# TODO: Komplett Überabreiten!
def append_tournament_bonuspoints(self, hanchan):
"""
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
:param hanchan: 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.
hanchans_this_event = Hanchan.objects.user_hanchans(
user=self.user, event=hanchan.event
).order_by('-start')
last_hanchan_this_event = hanchans_this_event[0]
if hanchan != last_hanchan_this_event:
return # Das braucht nur am Ende eines Turnieres gemacht werden.
event_ranking = EventRanking.objects.get(
user=self.user,
event=current_event)
event=hanchan.event)
if event_ranking.placement == 1:
bonus_points += 4
player.comment += '+4 Punkte für den Sieg des Turnieres. '
hanchan.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
hanchan.comment += '+8 Pkt: alle Spiele des Turnieres gewonnen. '
if bonus_points and self.dan:
player.dan_points += bonus_points
hanchan.dan_points += bonus_points
self.dan_points += bonus_points
elif bonus_points:
player.kyu_points += bonus_points
hanchan.kyu_points += bonus_points
self.kyu_points += bonus_points
hanchan.bonus_points += bonus_points
def get_absolute_url(self):
if self.dan or self.dan_points > 0:
@@ -319,87 +363,91 @@ class KyuDanRanking(models.Model):
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)
if self.legacy_date:
valid_hanchans = valid_hanchans.filter(hanchan__start__gt=self.legacy_date)
valid_hanchans = valid_hanchans.order_by('hanchan__start')
self.kyu_points = self.legacy_kyu_points or 0
self.dan_points = self.legacy_dan_points or 0
self.dan = None
self.kyu = 10
self.update_rank()
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)
self.kyu = None
self.won_hanchans = 0
self.good_hanchans = 0
for played_hanchan in valid_hanchans:
self.update_hanchan_points(played_hanchan)
self.append_tournament_bonuspoints(played_hanchan)
logger.info("recalculating Kyu/Dan punkte for %s...", self.user)
self.update_rank()
valid_hanchans = Hanchan.objects.confirmed_hanchans(user=self.user)
valid_hanchans = valid_hanchans.order_by('start')
if self.legacy_date:
valid_hanchans = valid_hanchans.filter(start__gt=self.legacy_date)
self.hanchan_count = valid_hanchans.count()
for hanchan in valid_hanchans:
hanchan.get_playerdata(self.user)
hanchan.bonus_points=0
hanchan.player_comment=""
self.update_hanchan_points(hanchan)
if hanchan.event.mahjong_tournament:
self.append_tournament_bonuspoints(hanchan)
self.update_rank()
self.append_3_in_a_row_bonuspoints(played_hanchan)
self.append_3_in_a_row_bonuspoints(hanchan)
self.update_rank()
played_hanchan.save(force_update=True, mark_dirty=False)
self.won_hanchans += 1 if hanchan.placement == 1 else 0
self.good_hanchans += 1 if hanchan.placement == 2 else 0
hanchan.update_playerdata(self.user)
hanchan.save(force_update=True)
self.save(force_update=True)
def update_hanchan_points(self, played_hanchan):
def update_hanchan_points(self, hanchan):
"""
Berechne die Kyu bzw. Dan Punkte für eine Hanchan neu.
Für Turniere gelten andere Regeln zur Punktevergabe:
1. Platz 4 Punkte
2. Platz 3 Punkte
3. Platz 2 Punkte
4. Platz 1 Punkt
:param played_hanchan: Das Player Objekt das neuberechnet werden soll.
:type hanchan: Hanchan
:param hanchan: Das Player Objekt das neuberechnet werden soll.
"""
played_hanchan.bonus_points = 0
played_hanchan.comment = ""
played_hanchan.dan_points = None
played_hanchan.kyu_points = None
print self.user, played_hanchan.input_score, played_hanchan.game_score
if played_hanchan.hanchan.event.is_tournament:
tourney_points = 4 - played_hanchan.placement
hanchan.kyu_points = None
hanchan.dan_points = None
if hanchan.event.mahjong_tournament:
# Für Turniere gelten andere Regeln zur Punktevergabe:
# 1. Platz 4 Punkte
# 2. Platz 3 Punkte
# 3. Platz 2 Punkte
# 4. Platz 1 Punkt
tourney_points = 4 - hanchan.placement
if self.dan:
played_hanchan.dan_points = tourney_points
hanchan.dan_points = tourney_points
else:
played_hanchan.kyu_points = tourney_points
hanchan.kyu_points = tourney_points
elif self.dan:
if played_hanchan.game_score >= 60000:
played_hanchan.dan_points = 3
elif played_hanchan.game_score == 0:
played_hanchan.dan_points = -3
elif played_hanchan.placement == 1:
played_hanchan.dan_points = 2
elif played_hanchan.placement == 2:
played_hanchan.dan_points = 1
elif played_hanchan.placement == 3:
played_hanchan.dan_points = -1
elif played_hanchan.placement == 4:
played_hanchan.dan_points = -2
#Kein Turnier, Spieler nicht im Dan, dann muss er Sie Kyu Punkte bekommen
elif played_hanchan.game_score >= 60000:
played_hanchan.kyu_points = 3
elif played_hanchan.game_score >= 30000:
played_hanchan.kyu_points = 1
elif played_hanchan.game_score < 10000:
played_hanchan.kyu_points = -1
if hanchan.game_score >= 60000:
hanchan.dan_points = 3
elif hanchan.game_score == 0:
hanchan.dan_points = -3
elif hanchan.placement == 1:
hanchan.dan_points = 2
elif hanchan.placement == 2:
hanchan.dan_points = 1
elif hanchan.placement == 3:
hanchan.dan_points = -1
elif hanchan.placement == 4:
hanchan.dan_points = -2
elif hanchan.game_score >= 60000:
hanchan.kyu_points = 3
elif hanchan.game_score >= 30000:
hanchan.kyu_points = 1
elif hanchan.input_score < 10000:
hanchan.kyu_points = -1
else:
played_hanchan.kyu_points = 0
hanchan.kyu_points = 0
# Add the hanchans points to the players points
if self.dan:
if self.dan_points + played_hanchan.dan_points < 0:
# Only substract so much points that player has 0 Points:
played_hanchan.dan_points -= (self.dan_points + played_hanchan.dan_points)
self.dan_points += played_hanchan.dan_points
print "Danpunkte des Spielers: {}, Danpunkte der Hanchan {}".format(self.dan_points, played_hanchan.dan_points)
# Only substract so much points that player has 0 Points:
if self.dan_points + hanchan.dan_points < 0:
hanchan.dan_points -= (self.dan_points + hanchan.dan_points)
self.dan_points += hanchan.dan_points
else:
if self.kyu_points + played_hanchan.kyu_points < 0:
# Only substract so much points that player has 0 Points:
played_hanchan.kyu_points -= (self.kyu_points + played_hanchan.kyu_points)
self.kyu_points += played_hanchan.kyu_points
# Only substract so much points that player has 0 Points:
if self.kyu_points + hanchan.kyu_points < 0:
hanchan.kyu_points -= (self.kyu_points + hanchan.kyu_points)
self.kyu_points += hanchan.kyu_points
# TODO: Merkwürdige Methode die zwar funktioniert aber nicht sehr aussagekräfig ist. Überarbeiten?
def update_rank(self):
if self.dan and self.dan_points < 0:
self.dan_points = 0
@@ -427,15 +475,16 @@ class KyuDanRanking(models.Model):
break
class LadderRanking(models.Model):
class SeasonRanking(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
season = models.ForeignKey('LadderSeason')
season = models.PositiveSmallIntegerField(_('Season'))
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)
objects = managers.SeasonRankingManager()
class Meta(object):
ordering = ('placement', 'avg_placement', '-avg_score',)
@@ -444,217 +493,40 @@ class LadderRanking(models.Model):
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(
avg_placement = models.Avg('placement'),
avg_score = models.Avg('game_score'),
hanchan_count = models.Count('pk')
)
season_hanchans = Hanchan.objects.season_hanchans(user=self.user, season=self.season)
sum_placement = 0
sum_score = 0
self.placement = None
self.hanchan_count = aggregate['hanchan_count']
self.avg_placement = aggregate['avg_placement']
self.avg_score = aggregate['avg_score']
self.good_hanchans = ladder_hanchans.filter(placement=2).count()
self.won_hanchans = ladder_hanchans.filter(placement=1).count()
self.save(force_update=True)
self.hanchan_count = season_hanchans.count()
self.good_hanchans = 0
self.won_hanchans = 0
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:
current_year = date.today().year
current_season = self.get_or_create(name=current_year, defaults={
'name': current_year,
'start': date(year=current_year,month=1, day=1),
'end': date(year=current_year, month=12, day=31)
})[0]
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
"""
logger.info(u'Recalculate LadderRanking for Player %s in Season %s', self.user, self.season)
for hanchan in season_hanchans:
sum_placement += hanchan.placement
sum_score += hanchan.game_score
self.won_hanchans += 1 if hanchan.placement == 1 else 0
self.good_hanchans += 1 if hanchan.placement == 2 else 0
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.PositiveSmallIntegerField()
start = models.DateField()
end = models.DateField()
objects = LadderSeasonManager()
class Meta(object):
ordering = ('start',)
verbose_name = _('Ladder Season')
verbose_name_plural = _('Ladder Seasons')
def __unicode__(self):
return str(self.name)
def get_absolute_url(self):
"""
URL zur Hanchanliste des Events wo diese Hanchan gelistet wurde.
"""
return reverse('mahjong-ladder', kwargs={'season': self.pk})
@property
def participants(self):
return self.ladderranking_set.filter(placement__isnull=False).count()
def recalculate(self):
logger.info(u'Recalculate LadderSeason %s', self.name)
self.ladderranking_set.update(placement=None)
ladderrankings_for_placement = self.ladderranking_set.filter(
hanchan_count__gt=MIN_HANCHANS_FOR_LADDER
).order_by(
'avg_placement', '-avg_score'
)
placement = 1
for ranking in ladderrankings_for_placement:
ranking.placement = placement
ranking.save(force_update=True)
placement += 1
self.avg_placement = sum_placement / self.hanchan_count
self.avg_score = sum_score / self.hanchan_count
except ZeroDivisionError:
self.avg_placement = None
self.avg_score = None
self.save(force_update=True)
class PlayerManager(models.Manager):
use_for_related_fields = True
def update_ranking(sender, instance, **kwargs):
for user in (instance.player1, instance.player2, instance.player3, instance.player4):
logger.debug("Marking %s's kyu/dan for recalculation", user)
set_dirty(user=user.id)
if instance.season:
logger.debug("Marking %s's ladder %i season for recalculation.", user, instance.season)
set_dirty(user=user.id, season=instance.season)
if instance.event.mahjong_tournament:
logger.debug("Marking tournament %s for recalculation.", instance.event)
set_dirty(event=instance.event_id, user=user.id)
set_dirty(season=instance.season)
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):
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(settings.AUTH_USER_MODEL)
input_score = models.IntegerField(default=0)
game_score = models.PositiveIntegerField(default=0)
placement = models.PositiveSmallIntegerField(
blank=True,
null=True,
default=None
)
kyu_points = models.SmallIntegerField(
blank=True,
null=True,
default=None
)
dan_points = models.SmallIntegerField(
blank=True,
null=True,
default=None
)
bonus_points = models.PositiveSmallIntegerField(
blank=True,
null=True,
default=0
)
comment = models.TextField(_('Comment'), blank=True)
objects = PlayerManager()
class Meta(object):
unique_together = ('hanchan', 'user')
ordering = ['-input_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):
self.game_score = self.input_score if self.input_score > 0 else 0
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)
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)
models.signals.pre_delete.connect(update_ranking, sender=Hanchan)
models.signals.post_save.connect(update_ranking, sender=Hanchan)