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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user