From b7fab97715d298dcf84c02fa0920717f47d2db6f Mon Sep 17 00:00:00 2001 From: Xeniac Date: Fri, 22 Dec 2017 10:51:20 +0100 Subject: [PATCH] =?UTF-8?q?Diverse=20Umbauarbeiten=20f=C3=BCr=20das=20neue?= =?UTF-8?q?=20Ranking.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../management/commands/resetdanranking.py | 24 +++-- .../management/commands/update_ranking.py | 4 +- .../migrations/0006_auto_20171214_1318.py | 87 ++++++++++++++++ src/mahjong_ranking/models.py | 85 ++++++++-------- .../mahjong_ranking/player_dan_score.html | 6 +- src/mahjong_ranking/views.py | 99 ++++++++++++++++++- 6 files changed, 243 insertions(+), 62 deletions(-) create mode 100644 src/mahjong_ranking/migrations/0006_auto_20171214_1318.py diff --git a/src/mahjong_ranking/management/commands/resetdanranking.py b/src/mahjong_ranking/management/commands/resetdanranking.py index 2ead5e5..c01274e 100644 --- a/src/mahjong_ranking/management/commands/resetdanranking.py +++ b/src/mahjong_ranking/management/commands/resetdanranking.py @@ -17,16 +17,22 @@ class Command(BaseCommand): parser.add_argument('reset_date', type=parse_date) def handle(self, *args, **options): - reset_date = timezone.make_aware(datetime.combine(options.get('reset_date'), time(23, 59, 59))) - # models.KyuDanRanking.objects.update(until=reset_date, force_recalc=True) - dan_rankigns = models.KyuDanRanking.objects.filter(dan__isnull=False) - for ranking in dan_rankigns: + legacy_attrs = [ key for key in models.KyuDanRanking.__dict__.keys() + if key.startswith('legacy') ] + legacy_attrs.remove('legacy_date') + reset_date = timezone.make_aware(datetime.combine( + options.get('reset_date'), time(23, 59, 59))) + models.KyuDanRanking.objects.update(until=reset_date, force_recalc=True) + for ranking in models.KyuDanRanking.objects.filter(dan__gt=0): + print(ranking) ranking.dan = 1 ranking.dan_points = 0 + ranking.kyu = None + ranking.kyu_points = 0 + ranking.wins_in_a_row = 0 ranking.legacy_date = reset_date.date() - ranking.legacy_hanchan_count = ranking.hanchan_count - ranking.legacy_dan_points = ranking.dan_points - ranking.legacy_kyu_points = ranking.kyu_points + for legacy_attr in legacy_attrs: + attr = legacy_attr.split("_", maxsplit=1)[1] + print(ranking, legacy_attr, attr, getattr(ranking, attr)) + setattr(ranking, legacy_attr, getattr(ranking, attr)) ranking.save() - - diff --git a/src/mahjong_ranking/management/commands/update_ranking.py b/src/mahjong_ranking/management/commands/update_ranking.py index d7a8fd7..4d6c1c5 100644 --- a/src/mahjong_ranking/management/commands/update_ranking.py +++ b/src/mahjong_ranking/management/commands/update_ranking.py @@ -23,11 +23,11 @@ class Command(BaseCommand): def handle(self, *args, **options): since = options.get('since', None) until = options.get('until', None) - force_recalc = options.get('forecerecalc', False) + force_recalc = options.get('forcerecalc') if isinstance(since, date): since = datetime.combine(since, time(0, 0, 0)) since = timezone.make_aware(since) if isinstance(until, date): until = datetime.combine(until, time(23, 59, 59)) until = timezone.make_aware(until) - models.KyuDanRanking.objects.update(since=since, until=until, force_recalc=force_recalc) \ No newline at end of file + models.KyuDanRanking.objects.update(since=since, until=until, force_recalc=force_recalc) diff --git a/src/mahjong_ranking/migrations/0006_auto_20171214_1318.py b/src/mahjong_ranking/migrations/0006_auto_20171214_1318.py new file mode 100644 index 0000000..0c39fec --- /dev/null +++ b/src/mahjong_ranking/migrations/0006_auto_20171214_1318.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2017-12-14 12:18 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mahjong_ranking', '0005_auto_20171115_0653'), + ] + + operations = [ + migrations.AddField( + model_name='kyudanranking', + name='legacy_dan', + field=models.PositiveSmallIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='kyudanranking', + name='legacy_good_hanchans', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='kyudanranking', + name='legacy_kyu', + field=models.PositiveSmallIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='kyudanranking', + name='legacy_won_hanchans', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='kyudanranking', + name='max_dan_points', + field=models.PositiveIntegerField(default=0), + ), + migrations.AlterField( + model_name='eventranking', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='hanchan', + name='player1', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 1'), + ), + migrations.AlterField( + model_name='hanchan', + name='player2', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 2'), + ), + migrations.AlterField( + model_name='hanchan', + name='player3', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 3'), + ), + migrations.AlterField( + model_name='hanchan', + name='player4', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 4'), + ), + migrations.AlterField( + model_name='kyudanranking', + name='legacy_dan_points', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='kyudanranking', + name='legacy_hanchan_count', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='kyudanranking', + name='legacy_kyu_points', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='seasonranking', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/src/mahjong_ranking/models.py b/src/mahjong_ranking/models.py index dcaafcd..b5d12d6 100644 --- a/src/mahjong_ranking/models.py +++ b/src/mahjong_ranking/models.py @@ -347,9 +347,12 @@ class KyuDanRanking(models.Model): hanchan_count = models.PositiveIntegerField(default=0) legacy_date = models.DateField(blank=True, null=True) legacy_dan = models.PositiveSmallIntegerField(blank=True, null=True) - legacy_dan_points = models.PositiveIntegerField(default=0) - legacy_kyu_points = models.PositiveIntegerField(default=0) - legacy_hanchan_count = models.PositiveIntegerField(default=0) + legacy_dan_points = models.PositiveIntegerField(blank=True, null=True) + legacy_kyu = models.PositiveSmallIntegerField(blank=True, null=True) + legacy_kyu_points = models.PositiveIntegerField(blank=True, null=True) + legacy_hanchan_count = models.PositiveIntegerField(blank=True, null=True) + legacy_good_hanchans = models.PositiveIntegerField(blank=True, null=True) + legacy_won_hanchans = models.PositiveIntegerField(blank=True, null=True) wins_in_a_row = models.PositiveIntegerField(default=0) last_hanchan_date = models.DateTimeField(blank=True, null=True) objects = managers.KyuDanRankingManager() @@ -371,12 +374,15 @@ class KyuDanRanking(models.Model): 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: + if not self.dan or not settings.DAN_3_WINS_IN_A_ROW: + return + if hanchan.placement == 1: self.wins_in_a_row += 1 else: self.wins_in_a_row = 0 + return + if self.wins_in_a_row >= 3 and self.dan < 9: - if self.dan and self.wins_in_a_row >= 3 and self.dan < 9: LOGGER.info( 'adding bonuspoints for 3 wins in a row for %s', self.user) new_dan_rank = self.dan + 1 @@ -397,6 +403,7 @@ class KyuDanRanking(models.Model): bonus_points, new_dan_rank) self.dan_points += bonus_points self.wins_in_a_row = 0 + self.update_rank() def append_tournament_bonuspoints(self, hanchan): """ @@ -455,18 +462,17 @@ class KyuDanRanking(models.Model): # Setze alles auf die legacy Werte und berechne alles von neuem. self.dan = self.legacy_dan self.dan_points = self.legacy_dan_points or 0 - self.kyu = None + self.max_dan_points = self.dan_points + self.kyu = self.legacy_kyu self.kyu_points = self.legacy_kyu_points or 0 self.hanchan_count = self.legacy_hanchan_count or 0 - self.good_hanchans = 0 - self.won_hanchans = 0 - self.update_rank() + self.good_hanchans = self.legacy_good_hanchans or 0 + self.won_hanchans = self.legacy_won_hanchans or 0 self.last_hanchan_date = None - if self.legacy_date: - since = timezone.make_aware( - datetime.combine(self.legacy_date, time(0, 0, 0))) - else: - since = None + self.update_rank() + since = timezone.make_aware(datetime.combine( + self.legacy_date, + time(23, 59, 59))) if self.legacy_date else None elif self.last_hanchan_date: since = self.last_hanchan_date elif self.legacy_date: @@ -481,40 +487,27 @@ class KyuDanRanking(models.Model): valid_hanchans = valid_hanchans.filter(start__gt=since) if until: valid_hanchans = valid_hanchans.filter(start__lte=until) - - self.hanchan_count += valid_hanchans.count() for hanchan in valid_hanchans: + self.hanchan_count += 1 hanchan.get_playerdata(self.user) if since and hanchan.start < since: - print(hanchan, "<", since, "no recalc") + LOGGER.debug(hanchan, "<", since, "no recalc") self.dan_points += hanchan.dan_points or 0 self.kyu_points += hanchan.kyu_points or 0 self.update_rank() else: hanchan.bonus_points = 0 - hanchan.player_comment = u"" + 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(hanchan) - self.update_rank() hanchan.update_playerdata(self.user) hanchan.save(recalculate=False) - self.won_hanchans += 1 if hanchan.placement == 1 else 0 - self.good_hanchans += 1 if hanchan.placement == 2 else 0 - self.last_hanchan_date = hanchan.start - LOGGER.debug( - 'id: %(id)d, start: %(start)s, placement: %(placement)d, ' - 'score: %(score)d, kyu points: %(kyu_points)d, dan points: ' - '%(dan_points)d, bonus points: %(bonus_points)d', - {'id': hanchan.pk, 'start': hanchan.start, - 'placement': hanchan.placement, - 'score': hanchan.game_score, - 'kyu_points': hanchan.kyu_points or 0, - 'dan_points': hanchan.dan_points or 0, - 'bonus_points': hanchan.bonus_points or 0} - ) + self.won_hanchans += 1 if hanchan.placement == 1 else 0 + self.good_hanchans += 1 if hanchan.placement == 2 else 0 + self.last_hanchan_date = hanchan.start self.save(force_update=True) def update_hanchan_points(self, hanchan): @@ -572,25 +565,27 @@ class KyuDanRanking(models.Model): self.kyu_points += hanchan.kyu_points def update_rank(self): - print(self.user, self.dan, self.kyu) - old_dan = self.dan - if settings.DAN_ALLOW_DROP_DOWN and (self.dan or self.dan_points > 0): - self.dan = max((dan for min_points, dan in settings.DAN_RANKS if - self.dan_points > min_points)) - elif self.dan or self.dan_points > 0: - self.dan = max((dan for min_points, dan in settings.DAN_RANKS if - self.max_dan_points > min_points)) + # Update Dan ranking: + if self.dan or self.dan_points > 0: + if settings.DAN_ALLOW_DROP_DOWN: + self.dan = max((dan for min_points, dan in settings.DAN_RANKS + if self.dan_points > min_points)) + else: + self.max_dan_points = max(self.max_dan_points, self.dan_points) + self.dan = max((dan for min_points, dan in settings.DAN_RANKS + if self.max_dan_points > min_points)) + + # jump from Kyu to Dan elif self.kyu_points > 50: self.dan = 1 self.dan_points = 0 self.kyu = None self.kyu_points = 0 self.wins_in_a_row = 0 + # update Kyu ranking_ else: - print(self, self.kyu_points) - self.kyu = max((kyu for min_points, kyu in settings.KYU_RANKS if - self.kyu_points > min_points)) - self.wins_in_a_row = 0 if self.dan > old_dan else self.wins_in_a_row + self.kyu = min((kyu for min_points, kyu in settings.KYU_RANKS + if self.kyu_points > min_points)) class SeasonRanking(models.Model): diff --git a/src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html b/src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html index 5a0aa33..2ef6db7 100755 --- a/src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html +++ b/src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html @@ -50,4 +50,8 @@ {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block buttonbar %} + Download +{% endblock %} diff --git a/src/mahjong_ranking/views.py b/src/mahjong_ranking/views.py index 9855843..3580dbb 100644 --- a/src/mahjong_ranking/views.py +++ b/src/mahjong_ranking/views.py @@ -28,6 +28,56 @@ KYU_DAN_ORDER = { # map sort URL args to Django ORM order_by args '-username': ('-user__username',) } +def getattr_recursive(obj, attr_string): + attr_list = attr_string.split('.') + return_value=None + for attr in attr_list: + print("Obj:", obj,'Attr:', attr) + return_value = getattr(obj, attr) + obj = return_value + return return_value + +def generate_sheet(workbook, title, columns_settings, object_list): + row = 1 + ws = workbook.create_sheet() + ws.title = title + ws.syncHorizontal = True + ws.filterMode = True + + # setup print orientation + ws.page_setup.orientation = ws.ORIENTATION_PORTRAIT + ws.page_setup.paperSize = ws.PAPERSIZE_A4 + ws.page_setup.fitToWidth = True + ws.print_options.horizontalCentered = True + + # setup page header + ws.oddHeader.left.text = title + ws.oddHeader.left.size = 14 + ws.oddHeader.left.font = "Amerika Sans" + ws.oddHeader.left.color = "000000" + + ws.oddHeader.right.text = str(date.today()) + ws.oddHeader.right.size = 14 + ws.oddHeader.right.font = "Amerika Sans" + ws.oddHeader.right.color = "000000" + + # write table header + for column, data in enumerate(columns_settings, 1): + cell = ws.cell(column=column, row=row, value=data['title']) + cell.style = 'heading' + + # write the table content + for line in object_list: + row += 1 + for column, settings in enumerate(columns_settings, 1): + cell = ws.cell(column=column, row=row, value=getattr_recursive(line, settings['attr'])) + cell.style = settings['style'] + + # set column widths + for settings in columns_settings: + ws.column_dimensions[settings['col']].width = settings['width'] + + class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin, generic.DeleteView): @@ -117,9 +167,6 @@ class EventRankingList(EventDetailMixin, generic.ListView): model = models.EventRanking - - - class KyuDanRankingList(MahjongMixin, generic.ListView): """List all Players with an Kyu or Dan score. """ default_order = '-score' @@ -133,7 +180,6 @@ class KyuDanRankingList(MahjongMixin, generic.ListView): ] return super(KyuDanRankingList, self).dispatch(request, *args, **kwargs) - def get_queryset(self): queryset = models.KyuDanRanking.objects.filter( hanchan_count__gt=0).order_by(*self.order_by) @@ -160,7 +206,6 @@ class PlayerScore(LoginRequiredMixin, generic.ListView): def get(self, request, *args, **kwargs): user_model = auth.get_user_model() - username = kwargs.get('username') try: self.user = user_model.objects.get( username=self.kwargs.get('username')) @@ -168,6 +213,9 @@ class PlayerScore(LoginRequiredMixin, generic.ListView): raise django.http.Http404( _("No user found matching the name {}").format( self.kwargs.get('username'))) + print(request.GET) + if request.GET.get('download') == 'xlsx': + return self.get_xlsx(request, *args, **kwargs) return super(PlayerScore, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -186,12 +234,47 @@ class PlayerScore(LoginRequiredMixin, generic.ListView): context['ladder_ranking'] = models.SeasonRanking(user=self.user) return context + def get_xlsx(self, request, *args, **kwargs): + from management.commands.export_ranking import geneate_excel + self.object_list = self.get_queryset() + allow_empty = self.get_allow_empty() + response = django.http.HttpResponse( + content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') + response[ + 'Content-Disposition'] = 'attachment; filename="{xlsx_filename}"'.format(xlsx_filename=self.xlsx_filename) + xlxs_workbook = geneate_excel() + print(self.xlsx_columns) + generate_sheet(xlxs_workbook, + title=self.xlsx_filename, + columns_settings=self.xlsx_columns, + object_list=self.object_list + ) + xlxs_workbook.create_sheet() + xlxs_workbook.save(response) + return response + class PlayerDanScore(PlayerScore): template_name = 'mahjong_ranking/player_dan_score.html' + xlsx_columns = ( + {'col': 'A', 'title': 'Beginn', 'attr': 'start', 'style': 'date', 'width': 14}, + {'col': 'B', 'title': 'Platzierung', 'attr': 'placement', 'style': 'int', 'width': 8}, + {'col': 'C', 'title': 'Spieler 1', 'attr': 'player1.username', 'style': 'content', 'width': 8}, + {'col': 'D', 'title': 'Punkte', 'attr': 'player1_game_score', 'style': 'int', 'width': 8}, + {'col': 'E', 'title': 'Spieler 2', 'attr': 'player2.username', 'style': 'content', 'width': 8}, + {'col': 'F', 'title': 'Punkte', 'attr': 'player2_game_score', 'style': 'int', 'width': 8}, + {'col': 'G', 'title': 'Spieler 3', 'attr': 'player3.username', 'style': 'content', 'width': 8}, + {'col': 'H', 'title': 'Punkte', 'attr': 'player3_game_score', 'style': 'int', 'width': 8}, + {'col': 'I', 'title': 'Spieler 4', 'attr': 'player4.username', 'style': 'content', 'width': 8}, + {'col': 'J', 'title': 'Punkte', 'attr': 'player4_game_score', 'style': 'int', 'width': 8}, + {'col': 'K', 'title': 'Dan Punkte', 'attr': 'dan_points', 'style': 'int', 'width': 8}, + {'col': 'L', 'title': 'Anmerkung', 'attr': 'comment', 'style': 'content', 'width': 8}, + ) def get_queryset(self): kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user) + self.xlsx_filename = "{username}_dan_score.xlsx".format( + username=self.user.username) return models.Hanchan.objects.dan_hanchans(user=self.user, since=kyu_dan_ranking.legacy_date) @@ -200,6 +283,8 @@ class PlayerInvalidScore(PlayerScore): template_name = 'mahjong_ranking/player_invalid_score.html' def get_queryset(self): + self.xlsx_filename = "{username}_invalid_score.xlsx".format( + username=self.user.username) return models.Hanchan.objects.unconfirmed(user=self.user) @@ -207,6 +292,8 @@ class PlayerKyuScore(PlayerScore): template_name = 'mahjong_ranking/player_kyu_score.html' def get_queryset(self): + self.xlsx_filename = "{username}_kyu_score.xlsx".format( + username=self.user.username) return models.Hanchan.objects.kyu_hanchans(self.user) @@ -224,6 +311,8 @@ class PlayerLadderScore(PlayerScore): def get_queryset(self, **kwargs): self.season = int(self.request.GET.get('season', date.today().year)) + self.xlsx_filename = "{username}_ladder ({season}).xlsx".format( + username=self.user.username, season=self.season) hanchan_list = models.Hanchan.objects.season_hanchans( user=self.user, season=self.season