[\+\.\-\d\w\/]+)/$',
diff --git a/src/kasu/xlsx.py b/src/kasu/xlsx.py
new file mode 100644
index 0000000..4a26a88
--- /dev/null
+++ b/src/kasu/xlsx.py
@@ -0,0 +1,147 @@
+"""
+Helper to generate XLSX Spreadsheets in an uniform way.
+"""
+from datetime import date
+
+import openpyxl
+from openpyxl.styles import NamedStyle, Font, Border, Side
+
+DEFAULT_FONT = Font(name='Philosopher', size=10, bold=False, color='000000')
+THIN_BORDER = Border(
+ bottom=Side(style='thin', color="d3d7cf"),
+ top=Side(style='thin', color="d3d7cf"))
+XLSX_STYLES = dict()
+
+XLSX_STYLES['Content'] = NamedStyle(
+ name='Content',
+ font=DEFAULT_FONT,
+ border=THIN_BORDER
+)
+
+XLSX_STYLES['Headline'] = NamedStyle(
+ name="Headline",
+ font=openpyxl.styles.Font(name='Philosopher', size=11,
+ bold=True, color='ffffff'),
+ fill=openpyxl.styles.PatternFill(fill_type='solid',
+ start_color='a40000',
+ end_color='a40000')
+)
+
+XLSX_STYLES['Date'] = NamedStyle(
+ name='Date',
+ font=DEFAULT_FONT,
+ border=THIN_BORDER,
+ number_format='dd.mm.yyyy'
+)
+
+XLSX_STYLES['Date Time'] = NamedStyle(
+ name='Date Time',
+ font=DEFAULT_FONT,
+ border=THIN_BORDER,
+ number_format='dd.mm.yyyy hh:MM'
+)
+
+XLSX_STYLES['Float'] = NamedStyle(
+ name='Float',
+ font=DEFAULT_FONT,
+ border=THIN_BORDER,
+ number_format='#,##0.00'
+)
+
+XLSX_STYLES['Integer'] = NamedStyle(
+ name='Integer',
+ font=DEFAULT_FONT,
+ border=THIN_BORDER,
+ number_format='#,##0'
+)
+
+
+def getattr_recursive(obj, attr_string):
+ """A recusive version of gettattr. the attr_string is splitted on the ".".
+
+ :param obj: a python object.
+ :param attr_string: the desired attribute of the object.
+ :return: a getattr_recursice(obj, 'attr1.attr2') will return the value of attr2 of attr1 from obj
+ """
+ attr_list = attr_string.split('.')
+ return_value = None
+ for attr in attr_list:
+ return_value = getattr(obj, attr)
+ obj = return_value
+ return return_value
+
+
+class Workbook(object):
+ workbook = None
+
+ def __init__(self):
+ """Generate an XLSX Workbook in memory
+
+ :rtype: object
+ """
+ self.workbook = openpyxl.Workbook()
+ [self.workbook.add_named_style(style) for style in XLSX_STYLES.values()]
+ [self.workbook.remove(sheet) for sheet in self.workbook.worksheets]
+
+ def generate_sheet(self, title, columns_settings, object_list,
+ orientation='landscape'):
+ """
+
+ :param title: Title of the Sheet
+ :param columns_settings: a list of dicts for the settings of each column
+ :param object_list: List of objects that should be added to the sheet
+ :param orientation: Paper Orientation 'landscape' or 'portrait'
+ """
+ row = 1
+ ws = self.workbook.create_sheet()
+ ws.title = title
+ ws.syncHorizontal = True
+ ws.filterMode = True
+
+ # setup print orientation
+ ws.page_setup.fitToHeight = 0
+ ws.page_setup.fitToWidth = 1
+ if orientation == 'landscape':
+ ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE
+ else:
+ ws.page_setup.orientation = ws.ORIENTATION_PORTRAIT
+ ws.page_setup.paperSize = ws.PAPERSIZE_A4
+ 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 = 'Headline'
+ row += 1
+
+ # write the table content
+ for line in object_list:
+ 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']
+ row += 1
+
+ # write table footer
+ for column, settings in enumerate(columns_settings, 1):
+ cell = ws.cell(column=column, row=row, value=settings.get('footer'))
+ cell.style = settings['style']
+ row += 1
+
+ # set column widths
+ for settings in columns_settings:
+ ws.column_dimensions[settings['col']].width = settings['width']
+
+ def save(self, *args, **kwargs):
+ return self.workbook.save(*args, **kwargs)
diff --git a/src/mahjong_ranking/management/commands/export_ranking.py b/src/mahjong_ranking/management/commands/export_ranking.py
deleted file mode 100644
index 247f5a2..0000000
--- a/src/mahjong_ranking/management/commands/export_ranking.py
+++ /dev/null
@@ -1,214 +0,0 @@
-"""Export Mahjong Rankings as excel files."""
-
-import os
-from datetime import date, time, datetime
-
-import openpyxl
-from django.conf import settings
-from django.core.management.base import BaseCommand
-from django.utils import timezone
-from django.utils.dateparse import parse_date
-from django.core.mail import EmailMessage
-
-from mahjong_ranking.models import SeasonRanking, KyuDanRanking
-
-THIN_BORDER = openpyxl.styles.Side(style='thin', color="d3d7cf")
-
-HEADING_STYLE = openpyxl.styles.NamedStyle(name="heading")
-HEADING_STYLE.font = openpyxl.styles.Font(name='Philosopher', size=11,
- bold=True, color='ffffff')
-HEADING_STYLE.fill = openpyxl.styles.PatternFill(fill_type='solid',
- start_color='a40000',
- end_color='a40000')
-
-DEFAULT_STYLE = openpyxl.styles.NamedStyle(name='content')
-DEFAULT_STYLE.font = openpyxl.styles.Font(name='Philosopher', size=10,
- bold=False, color='000000')
-DEFAULT_STYLE.border = openpyxl.styles.Border(bottom=THIN_BORDER,
- top=THIN_BORDER)
-
-INT_STYLE = openpyxl.styles.NamedStyle(name='int')
-INT_STYLE.font = DEFAULT_STYLE.font
-INT_STYLE.border = DEFAULT_STYLE.border
-INT_STYLE.number_format = '#,##0'
-
-FLOAT_STYLE = openpyxl.styles.NamedStyle(name='float')
-FLOAT_STYLE.font = DEFAULT_STYLE.font
-FLOAT_STYLE.border = DEFAULT_STYLE.border
-FLOAT_STYLE.number_format = '#,##0.00'
-
-DATE_STYLE = openpyxl.styles.NamedStyle(name='date')
-DATE_STYLE.font = DEFAULT_STYLE.font
-DATE_STYLE.border = DEFAULT_STYLE.border
-DATE_STYLE.number_format = 'dd.mm.yyyy'
-
-MAIL_BODY = """
-Hallo! Ich bin's dein Server.
-
-Ich habe gerade die Mahjong Rankings als Excel exportiert und dachte mir das
-ich sie dir am besten gleich schicke.
-
-Bitte versuche nicht auf diese E-Mail zu antworten.
-Ich bin nur ein dummes Programm.
-
-mit lieben Grüßen
-
-Der Kasu Server
-"""
-def geneate_excel():
- """Generate an excel .xlsx spreadsheet from json data of the kyu/dan
- rankings.
-
- :param json_data: The ladder ranking as JSON export."""
- workbook = openpyxl.Workbook()
- workbook.add_named_style(HEADING_STYLE)
- workbook.add_named_style(DEFAULT_STYLE)
- workbook.add_named_style(INT_STYLE)
- workbook.add_named_style(FLOAT_STYLE)
- workbook.add_named_style(DATE_STYLE)
-
- for sheet in workbook.worksheets:
- workbook.remove(sheet)
- return workbook
-
-
-def generate_sheet(workbook, title, columns_settings, json_data):
- 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 json_data:
- row += 1
- for column, settings in enumerate(columns_settings, 1):
- cell = ws.cell(column=column, row=row, value=line[settings['attr']])
- cell.style = settings['style']
-
- # set column widths
- for settings in columns_settings:
- ws.column_dimensions[settings['col']].width = settings['width']
-
-
-def export_season_rankings(workbook, until):
- SeasonRanking.objects.update(until=until)
- json_data = SeasonRanking.objects.json_data()
- title = "Mahjong Ladder - {}".format(until.year)
- columns_settings = (
- {'col': 'A', 'title': 'Rang', 'attr': 'placement', 'style': 'int',
- 'width': 8},
- {'col': 'B', 'title': 'Spitzname', 'attr': 'username',
- 'style': 'content',
- 'width': 25},
- {'col': 'C', 'title': '⌀ Platz', 'attr': 'avg_placement',
- 'style': 'float', 'width': 8},
- {'col': 'D', 'title': '⌀ Punkte', 'attr': 'avg_score',
- 'style': 'float', 'width': 12},
- {'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
- 'style': 'int', 'width': 10},
- {'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
- 'style': 'int', 'width': 5},
- {'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
- 'style': 'int', 'width': 10},
- )
- generate_sheet(
- workbook=workbook,
- title=title,
- columns_settings=columns_settings,
- json_data=json_data)
-
-
-def export_kyu_dan_rankings(workbook, until):
- KyuDanRanking.objects.update(until=until)
- json_data = KyuDanRanking.objects.json_data()
- title = "Kyū & Dan Rankings"
- columns_settings = (
- {'col': 'A', 'title': 'Spitzname', 'attr': 'username',
- 'style': 'content', 'width': 14},
- {'col': 'B', 'title': 'Voller Name', 'attr': 'full_name',
- 'style': 'content', 'width': 20},
- {'col': 'C', 'title': 'Rang', 'attr': 'rank',
- 'style': 'content', 'width': 8},
- {'col': 'D', 'title': 'Punkte', 'attr': 'points',
- 'style': 'int', 'width': 8},
- {'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
- 'style': 'int', 'width': 10},
- {'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
- 'style': 'int', 'width': 5},
- {'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
- 'style': 'int', 'width': 8},
- {'col': 'H', 'title': 'letzte Hanchan', 'attr': 'last_hanchan_date',
- 'style': 'date', 'width': 16},
- )
- generate_sheet(
- workbook=workbook,
- title=title,
- columns_settings=columns_settings,
- json_data=json_data)
-
-
-class Command(BaseCommand):
- """Exports the SeasonRankings"""
- filename = str()
- until = datetime
-
- def add_arguments(self, parser):
- parser.add_argument(
- '--until', nargs='?', type=parse_date,
- default=date.today(), metavar='YYYY-MM-DD',
- help='Calculate and export rankings until the given date.')
- parser.add_argument(
- '--mail', nargs='*', type=str, metavar='user@example.com',
- help='Send the spreadsheet via eMail to the given recipient.')
-
- def handle(self, *args, **options):
- """Exports the current ladder ranking in a spreadsheet.
- This is useful as a backup in form of a hardcopy."""
- self.until = timezone.make_aware(datetime.combine(
- options['until'], time(23, 59, 59)
- ))
-
- self.filename = os.path.join(
- settings.RANKING_EXPORT_PATH,
- 'mahjong_rankings_{:%Y-%m-%d}.xlsx'.format(self.until)
- )
- workbook = geneate_excel()
- export_season_rankings(workbook, until=self.until)
- export_kyu_dan_rankings(workbook, until=self.until)
- os.makedirs(settings.RANKING_EXPORT_PATH, exist_ok=True)
- workbook.save(self.filename)
- if options['mail']:
- self.send_mail(options['mail'])
-
- def send_mail(self, recipients):
- mail = EmailMessage(
- subject='Mahjong Rankings vom {:%d.%m.%Y}'.format(self.until),
- body=MAIL_BODY,
- from_email=settings.DEFAULT_FROM_EMAIL,
- to=recipients)
- mail.attach_file(self.filename)
- mail.send()
-
diff --git a/src/mahjong_ranking/management/commands/exportranking.py b/src/mahjong_ranking/management/commands/exportranking.py
new file mode 100644
index 0000000..d7440a3
--- /dev/null
+++ b/src/mahjong_ranking/management/commands/exportranking.py
@@ -0,0 +1,126 @@
+"""Export Mahjong Rankings as excel files."""
+
+import os
+from datetime import date, time, datetime
+
+from django.conf import settings
+from django.core.mail import EmailMessage
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+from django.utils.dateparse import parse_date
+
+from kasu import xlsx
+from mahjong_ranking.models import SeasonRanking, KyuDanRanking
+
+MAIL_BODY = """
+Hallo! Ich bin's dein Server.
+
+Ich habe gerade die Mahjong Rankings als Excel exportiert und dachte mir das
+ich sie dir am besten gleich schicke.
+
+Bitte versuche nicht auf diese E-Mail zu antworten.
+Ich bin nur ein dummes Programm.
+
+mit lieben Grüßen
+
+Der Kasu Server
+"""
+
+
+def export_season_rankings(workbook, until):
+ SeasonRanking.objects.update(until=until)
+ season = until.year if until else date.today().year
+ object_list = SeasonRanking.objects.season_rankings()
+ title = "Mahjong Ladder - {}".format(season)
+ columns_settings = (
+ {'col': 'A', 'title': 'Rang', 'attr': 'placement', 'style': 'Integer',
+ 'width': 8},
+ {'col': 'B', 'title': 'Spitzname', 'attr': 'user.username',
+ 'style': 'Content',
+ 'width': 25},
+ {'col': 'C', 'title': '⌀ Platz', 'attr': 'avg_placement',
+ 'style': 'Float', 'width': 8},
+ {'col': 'D', 'title': '⌀ Punkte', 'attr': 'avg_score',
+ 'style': 'Float', 'width': 12},
+ {'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
+ 'style': 'Integer', 'width': 10},
+ {'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
+ 'style': 'Integer', 'width': 5},
+ {'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
+ 'style': 'Integer', 'width': 10},
+ )
+ workbook.generate_sheet(
+ title=title,
+ columns_settings=columns_settings,
+ object_list=object_list)
+
+
+def export_kyu_dan_rankings(workbook, until):
+ KyuDanRanking.objects.update(until=until)
+ object_list = KyuDanRanking.objects.all()
+ title = "Kyū & Dan Rankings"
+ columns_settings = (
+ {'col': 'A', 'title': 'Spitzname', 'attr': 'user.username',
+ 'style': 'Content', 'width': 14},
+ {'col': 'B', 'title': 'Voller Name', 'attr': 'user.full_name',
+ 'style': 'Content', 'width': 20},
+ {'col': 'C', 'title': 'Rang', 'attr': 'rank',
+ 'style': 'Content', 'width': 8},
+ {'col': 'D', 'title': 'Punkte', 'attr': 'points',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
+ 'style': 'Integer', 'width': 10},
+ {'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
+ 'style': 'Integer', 'width': 5},
+ {'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
+ 'style': 'Integer', 'width': 10},
+ {'col': 'H', 'title': 'letzte Hanchan', 'attr': 'last_hanchan_date',
+ 'style': 'Date', 'width': 16},
+ )
+ workbook.generate_sheet(
+ title=title,
+ columns_settings=columns_settings,
+ object_list=object_list)
+
+
+class Command(BaseCommand):
+ """Exports the SeasonRankings"""
+ filename = str()
+ until = datetime
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '--until', nargs='?', type=parse_date,
+ default=date.today(), metavar='YYYY-MM-DD',
+ help='Calculate and export rankings until the given date.')
+ parser.add_argument(
+ '--mail', nargs='*', type=str, metavar='user@example.com',
+ help='Send the spreadsheet via eMail to the given recipient.')
+
+ def handle(self, *args, **options):
+ """Exports the current ladder ranking in a spreadsheet.
+ This is useful as a backup in form of a hardcopy."""
+ self.until = timezone.make_aware(datetime.combine(
+ options['until'], time(23, 59, 59)
+ ))
+
+ self.filename = os.path.join(
+ settings.RANKING_EXPORT_PATH,
+ 'mahjong_rankings_{:%Y-%m-%d}.xlsx'.format(self.until)
+ )
+ workbook = xlsx.Workbook()
+ export_season_rankings(workbook, until=self.until)
+ export_kyu_dan_rankings(workbook, until=self.until)
+ os.makedirs(settings.RANKING_EXPORT_PATH, exist_ok=True)
+ workbook.save(self.filename)
+ if options['mail']:
+ self.send_mail(options['mail'])
+
+ def send_mail(self, recipients):
+ mail = EmailMessage(
+ subject='Mahjong Rankings vom {:%d.%m.%Y}'.format(self.until),
+ body=MAIL_BODY,
+ from_email=settings.DEFAULT_FROM_EMAIL,
+ to=recipients)
+ mail.attach_file(self.filename)
+ mail.send()
diff --git a/src/mahjong_ranking/management/commands/update_ranking.py b/src/mahjong_ranking/management/commands/updateranking.py
similarity index 54%
rename from src/mahjong_ranking/management/commands/update_ranking.py
rename to src/mahjong_ranking/management/commands/updateranking.py
index 4d6c1c5..546c78f 100644
--- a/src/mahjong_ranking/management/commands/update_ranking.py
+++ b/src/mahjong_ranking/management/commands/updateranking.py
@@ -4,30 +4,39 @@
Recalculate Mahjong Rankings...
"""
-from django.core.management.base import BaseCommand
from datetime import date, datetime, time
-from mahjong_ranking import models
-from django.utils.dateparse import parse_date
+
+from django.core.management.base import BaseCommand
from django.utils import timezone
+from django.utils.dateparse import parse_date
+
+from mahjong_ranking import models
+
class Command(BaseCommand):
""" Recalculate all Kyu/Dan Rankings """
- help = "Recalculate all Kyu/Dan Rankings"
+ help = "Recalculate the Kyu/Dan Rankings"
def add_arguments(self, parser):
- parser.add_argument('--since', nargs='?', type=parse_date)
- parser.add_argument('--until', nargs='?', type=parse_date)
- parser.add_argument('--forcerecalc', action='store_true')
+ parser.add_argument('-s', '--since', nargs='?', type=parse_date,
+ metavar='YYYY-MM-DD',
+ help='Use all Hanchans since the given date.')
+ parser.add_argument('-u', '--until', nargs='?', type=parse_date,
+ metavar='YYYY-MM-DD',
+ help='Only use Hanchans until the given date.')
+ parser.add_argument('-f', '--force', action='store_true',
+ help="Force the recalculation of all Hanchans.")
def handle(self, *args, **options):
since = options.get('since', None)
until = options.get('until', None)
- force_recalc = options.get('forcerecalc')
+ force_recalc = options.get('force')
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)
+ models.KyuDanRanking.objects.update(since=since, until=until,
+ force_recalc=force_recalc)
diff --git a/src/mahjong_ranking/models.py b/src/mahjong_ranking/models.py
index b5d12d6..aa44006 100644
--- a/src/mahjong_ranking/models.py
+++ b/src/mahjong_ranking/models.py
@@ -362,11 +362,22 @@ class KyuDanRanking(models.Model):
verbose_name = _(u'Kyū/Dan Ranking')
verbose_name_plural = _(u'Kyū/Dan Rankings')
+ @property
+ def rank(self):
+ if self.dan is not None:
+ return "{0:d}. Dan".format(self.dan)
+ else:
+ return "{0:d}. Kyū".format(self.kyu or 10)
+
+ @property
+ def points(self):
+ return self.dan_points if self.dan is not None else self.kyu_points
+
def __str__(self):
if self.dan is not None:
return u"%s - %d. Dan" % (self.user.username, self.dan or 1)
else:
- return u"%s - %d. Kyu" % (self.user.username, self.kyu or 10)
+ return u"%s - %d. Kyū" % (self.user.username, self.kyu or 10)
def append_3_in_a_row_bonuspoints(self, hanchan):
u"""
@@ -382,7 +393,6 @@ class KyuDanRanking(models.Model):
self.wins_in_a_row = 0
return
if 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
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 2ef6db7..82aba0f 100755
--- a/src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html
+++ b/src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html
@@ -50,6 +50,10 @@
{% endfor %}
+
+{% if kyu_dan_ranking.legacy_date %}
+Frühere Dan Punkte vom {{ kyu_dan_ranking.legacy_date|date }}: {{kyu_dan_ranking.legacy_dan_points }}
+{% endif %}
{% endblock %}
{% block buttonbar %}
diff --git a/src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html b/src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html
index 7da226c..8e31486 100755
--- a/src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html
+++ b/src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html
@@ -35,8 +35,12 @@
{% if perms.mahjong_ranking.change_hanchan %}
{% endif %}
-
+
-{% endfor %}
+{% endfor %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
+
+{% block buttonbar %}
+ Download
+{% endblock %}
diff --git a/src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html b/src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html
index 2444be7..a24677b 100755
--- a/src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html
+++ b/src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html
@@ -16,7 +16,7 @@
{% trans 'Placement' %} |
{% trans 'Players' %} |
{% trans 'Kyu Points' %} |
- |
+ |
| 1. |
@@ -45,6 +45,10 @@
{% endif %}
-{% endfor %}
+{% endfor %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
+
+{% block buttonbar %}
+ Download
+{% endblock %}
diff --git a/src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html b/src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html
index 6558fe0..e4aaa30 100755
--- a/src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html
+++ b/src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html
@@ -72,5 +72,8 @@
+{% endblock %}
-{% endblock %}
\ No newline at end of file
+{% block buttonbar %}
+ Download
+{% endblock %}
diff --git a/src/mahjong_ranking/views.py b/src/mahjong_ranking/views.py
index 3580dbb..275e99c 100644
--- a/src/mahjong_ranking/views.py
+++ b/src/mahjong_ranking/views.py
@@ -12,6 +12,7 @@ from django.utils.translation import ugettext as _
from django.views import generic
from events.mixins import EventDetailMixin
+from kasu import xlsx
from . import forms, models
from .mixins import MahjongMixin
@@ -28,56 +29,6 @@ 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):
@@ -235,48 +186,67 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
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()
+ response['Content-Disposition'] = 'attachment; ' \
+ 'filename="{xlsx_filename}"'.format(
+ xlsx_filename=self.xlsx_filename)
+ xlxs_workbook = xlsx.Workbook()
+ xlxs_workbook.generate_sheet(
+ title=self.xlsx_filename.split('.')[0],
+ columns_settings=self.xlsx_columns,
+ object_list=self.object_list
+ )
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)
+ self.kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user)
+ return models.Hanchan.objects.dan_hanchans(
+ user=self.user,
+ since=self.kyu_dan_ranking.legacy_date)
+
+ @property
+ def xlsx_columns(self):
+ return (
+ {'col': 'A', 'title': 'Beginn', 'attr': 'start',
+ 'style': 'Date Time',
+ 'width': 14, 'footer': self.kyu_dan_ranking.legacy_date},
+ {'col': 'B', 'title': 'Termin', 'attr': 'event.name',
+ 'style': 'Content', 'width': 16},
+ {'col': 'C', 'title': 'Platzierung', 'attr': 'placement',
+ 'style': 'Integer', 'width': 11},
+ {'col': 'D', 'title': 'Spieler 1', 'attr': 'player1.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'E', 'title': 'Punkte', 'attr': 'player1_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'F', 'title': 'Spieler 2', 'attr': 'player2.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'G', 'title': 'Punkte', 'attr': 'player2_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'H', 'title': 'Spieler 3', 'attr': 'player3.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'I', 'title': 'Punkte', 'attr': 'player3_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'J', 'title': 'Spieler 4', 'attr': 'player4.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'K', 'title': 'Punkte', 'attr': 'player4_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'L', 'title': 'Dan Punkte', 'attr': 'dan_points',
+ 'style': 'Integer', 'width': 12,
+ 'footer': self.kyu_dan_ranking.legacy_dan_points},
+ {'col': 'M', 'title': 'Anmerkung', 'attr': 'comment',
+ 'style': 'Content', 'width': 20, 'footer': 'Legacy Dan Punkte'},
+ )
+
+ @property
+ def xlsx_filename(self):
+ return "{username}_dan_score.xlsx".format(username=self.user.username)
class PlayerInvalidScore(PlayerScore):
@@ -292,9 +262,47 @@ 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)
+ self.kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user)
+ return models.Hanchan.objects.kyu_hanchans(
+ user=self.user,
+ since=self.kyu_dan_ranking.legacy_date)
+
+ @property
+ def xlsx_columns(self):
+ return (
+ {'col': 'A', 'title': 'Beginn', 'attr': 'start',
+ 'style': 'Date Time',
+ 'width': 14, 'footer': self.kyu_dan_ranking.legacy_date},
+ {'col': 'B', 'title': 'Termin', 'attr': 'event.name',
+ 'style': 'Content', 'width': 16},
+ {'col': 'C', 'title': 'Platzierung', 'attr': 'placement',
+ 'style': 'Integer', 'width': 11},
+ {'col': 'D', 'title': 'Spieler 1', 'attr': 'player1.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'E', 'title': 'Punkte', 'attr': 'player1_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'F', 'title': 'Spieler 2', 'attr': 'player2.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'G', 'title': 'Punkte', 'attr': 'player2_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'H', 'title': 'Spieler 3', 'attr': 'player3.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'I', 'title': 'Punkte', 'attr': 'player3_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'J', 'title': 'Spieler 4', 'attr': 'player4.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'K', 'title': 'Punkte', 'attr': 'player4_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'L', 'title': 'Kyū Punkte', 'attr': 'kyu_points',
+ 'style': 'Integer', 'width': 12,
+ 'footer': self.kyu_dan_ranking.legacy_kyu_points},
+ {'col': 'M', 'title': 'Anmerkung', 'attr': 'comment',
+ 'style': 'Content', 'width': 24, 'footer': 'Legacy Kyū Punkte'},
+ )
+
+ @property
+ def xlsx_filename(self):
+ return "{username}_kyu_score.xlsx".format(username=self.user.username)
class PlayerLadderScore(PlayerScore):
@@ -311,10 +319,44 @@ 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
)
return hanchan_list
+
+ @property
+ def xlsx_columns(self):
+ return (
+ {'col': 'A', 'title': 'Beginn', 'attr': 'start',
+ 'style': 'Date Time', 'width': 14},
+ {'col': 'B', 'title': 'Termin', 'attr': 'event.name',
+ 'style': 'Content', 'width': 16},
+ {'col': 'C', 'title': 'Platzierung', 'attr': 'placement',
+ 'style': 'Integer', 'width': 11},
+ {'col': 'D', 'title': 'Spieler 1', 'attr': 'player1.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'E', 'title': 'Punkte', 'attr': 'player1_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'F', 'title': 'Spieler 2', 'attr': 'player2.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'G', 'title': 'Punkte', 'attr': 'player2_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'H', 'title': 'Spieler 3', 'attr': 'player3.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'I', 'title': 'Punkte', 'attr': 'player3_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'J', 'title': 'Spieler 4', 'attr': 'player4.username',
+ 'style': 'Content', 'width': 16},
+ {'col': 'K', 'title': 'Punkte', 'attr': 'player4_game_score',
+ 'style': 'Integer', 'width': 8},
+ {'col': 'L', 'title': 'Punkte', 'attr': 'game_score',
+ 'style': 'Integer', 'width': 8},
+ )
+
+ @property
+ def xlsx_filename(self):
+ return "{username}_ladder_score_{season}.xlsx".format(
+ username=self.user.username,
+ season=self.season
+ )
diff --git a/src/maistar_ranking/migrations/0007_auto_20171214_1215.py b/src/maistar_ranking/migrations/0007_auto_20171214_1215.py
new file mode 100644
index 0000000..66feede
--- /dev/null
+++ b/src/maistar_ranking/migrations/0007_auto_20171214_1215.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2017-12-14 11:15
+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 = [
+ ('maistar_ranking', '0006_auto_20171115_0653'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='game',
+ name='player1',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 1'),
+ ),
+ migrations.AlterField(
+ model_name='game',
+ name='player2',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 2'),
+ ),
+ migrations.AlterField(
+ model_name='game',
+ name='player3',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 3'),
+ ),
+ migrations.AlterField(
+ model_name='game',
+ name='player4',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 4'),
+ ),
+ migrations.AlterField(
+ model_name='game',
+ name='player5',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 5'),
+ ),
+ migrations.AlterField(
+ model_name='game',
+ name='player6',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 6'),
+ ),
+ migrations.AlterField(
+ model_name='ranking',
+ name='user',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/src/membership/models.py b/src/membership/models.py
index 7157c5a..7b746d2 100644
--- a/src/membership/models.py
+++ b/src/membership/models.py
@@ -216,6 +216,10 @@ class Membership(AbstractUser):
verbose_name = _('Membership')
verbose_name_plural = _('Memberships')
+ @property
+ def full_name(self):
+ return " ".join([self.last_name, self.first_name])
+
def __str__(self):
return self.username
diff --git a/src/membership/templates/membership/membership_detail.html b/src/membership/templates/membership/membership_detail.html
index d04e6ba..d1af67c 100644
--- a/src/membership/templates/membership/membership_detail.html
+++ b/src/membership/templates/membership/membership_detail.html
@@ -35,7 +35,10 @@
Mahjong
{% if kyu_dan_ranking.dan %}
- - {{kyu_dan_ranking.dan}}. Dan: {{ kyu_dan_ranking.dan_points }} {% trans 'Points' %}
+ -
+ {{kyu_dan_ranking.dan}}. Dan: {{ kyu_dan_ranking.dan_points }} {% trans 'Points' %}
+ ({% trans 'Maximum' %}: {{ kyu_dan_ranking.max_dan_points }})
+
{% elif kyu_dan_ranking.kyu%}
- {{kyu_dan_ranking.kyu}}. Kyu: {{ kyu_dan_ranking.kyu_points }} {% trans 'Points' %}
{% endif %}