commitbb5081a78bAuthor: Xeniac <xeniac@posteo.at> Date: Thu Nov 23 22:02:40 2017 +0100 Added a setting where the exported excel files should be stored. Added a option to send the exported excel as mail attachment. commit854fd38740Author: Xeniac <xeniac@posteo.at> Date: Thu Nov 23 22:01:38 2017 +0100 Fixed: enumerate the Seasonrankings starting with 1 Fixed: Logging error when a value changed from/to None commit6de1ecb102Author: Christian Berg <xeniac@posteo.at> Date: Thu Nov 23 14:15:36 2017 +0100 add a latest method to query the latest x events commitbf12060c3bAuthor: Christian Berg <xeniac@posteo.at> Date: Thu Nov 23 14:15:12 2017 +0100 add a latest method to query the latest x events commit5ad628f33aAuthor: Christian Berg <xeniac@posteo.at> Date: Mon Nov 20 07:47:47 2017 +0100 Changed PlayerDanScore to only list non-legacy hanchans commit36272c60d6Author: Christian Berg <xeniac@posteo.at> Date: Mon Nov 20 07:42:44 2017 +0100 fixed import of MIN_HANCHANS_FOR_LADDER that moved to settings commitc428f6ed1fAuthor: Christian Berg <xeniac@posteo.at> Date: Mon Nov 20 07:41:04 2017 +0100 Updated docstrings for new since and until kwargs commit9276e97c36Author: Christian Berg <xeniac@posteo.at> Date: Mon Nov 20 07:33:54 2017 +0100 added a since parameter to the hanchan queries to return only hanchans since the give date and time commitfd244f10e8Author: Christian Berg <xeniac@posteo.at> Date: Sun Nov 19 16:55:10 2017 +0100 new command: resetdanranking YYYY-MM-DD, sets every dan player to 1st dan with zero dan_points at the given date. commit0a45cf1fd8Author: Christian Berg <xeniac@posteo.at> Date: Sun Nov 19 16:14:59 2017 +0100 added new fields to KyuDanRanking that allow to pick up the calculation from the last state of the KyuDanRanking. last_hanchan_date: it contains the start of the latest hanchan content for this players ranking. wins_in_row: to save the currents wins in a row Added option to calcuclate rankings until a given datetime.
215 lines
7.5 KiB
Python
215 lines
7.5 KiB
Python
"""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()
|
|
|