XLSX Export vereinheitlicht.
Spieler Hanchanlisten können nun als XLSX exportiert werden. Anpassungen in den Einstellungen für die parametisierten Kyu/Dan Berechnung.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -62,6 +62,7 @@ docs/_build/
|
|||||||
target/
|
target/
|
||||||
|
|
||||||
#Django Development
|
#Django Development
|
||||||
|
backup/
|
||||||
/bower_components/
|
/bower_components/
|
||||||
/media/
|
/media/
|
||||||
/node_modules/
|
/node_modules/
|
||||||
|
|||||||
27
src/content/migrations/0007_auto_20171214_1215.py
Normal file
27
src/content/migrations/0007_auto_20171214_1215.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# -*- 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 = [
|
||||||
|
('content', '0006_auto_20171115_0653'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='author',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Autor'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='category',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='content.Category', verbose_name='Kategorie'),
|
||||||
|
),
|
||||||
|
]
|
||||||
32
src/events/migrations/0009_auto_20171214_1215.py
Normal file
32
src/events/migrations/0009_auto_20171214_1215.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# -*- 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 = [
|
||||||
|
('events', '0008_auto_20171115_0653'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='location',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='events.Location'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='photo',
|
||||||
|
name='event',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='events.Event'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='photo',
|
||||||
|
name='photographer',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -259,7 +259,9 @@ TOURNAMENT_WIN_BONUSPOINTS = 4
|
|||||||
TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS = 8
|
TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS = 8
|
||||||
|
|
||||||
# Old Dan System
|
# Old Dan System
|
||||||
|
DAN_3_WINS_IN_A_ROW = True
|
||||||
DAN_ALLOW_DROP_DOWN = True
|
DAN_ALLOW_DROP_DOWN = True
|
||||||
|
|
||||||
DAN_RANKS = (
|
DAN_RANKS = (
|
||||||
(80, 9),
|
(80, 9),
|
||||||
(70, 8),
|
(70, 8),
|
||||||
|
|||||||
4
src/kasu/templates/django/forms/widgets/date.html
Normal file
4
src/kasu/templates/django/forms/widgets/date.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{% with type="date" %}
|
||||||
|
{% include "django/forms/widgets/html5input.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
4
src/kasu/templates/django/forms/widgets/datetime.html
Normal file
4
src/kasu/templates/django/forms/widgets/datetime.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{% with type="datetime-local" %}
|
||||||
|
{% include "django/forms/widgets/html5input.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
1
src/kasu/templates/django/forms/widgets/html5input.html
Normal file
1
src/kasu/templates/django/forms/widgets/html5input.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<input type="{{ type|default:'text' }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} />
|
||||||
4
src/kasu/templates/django/forms/widgets/time.html
Normal file
4
src/kasu/templates/django/forms/widgets/time.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{% with type="time" %}
|
||||||
|
{% include "django/forms/widgets/html5input.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ urlpatterns = [ # Ignore PyLintBear (C0103)
|
|||||||
url(r'^add_page/(?P<path>[\+\.\-\d\w\/]+)/$',
|
url(r'^add_page/(?P<path>[\+\.\-\d\w\/]+)/$',
|
||||||
views.PageAddForm.as_view(), name='add-page'),
|
views.PageAddForm.as_view(), name='add-page'),
|
||||||
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', admin.site.urls),
|
||||||
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
|
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
|
||||||
url(r'^comments/', include('django_comments.urls')),
|
url(r'^comments/', include('django_comments.urls')),
|
||||||
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]+)/$',
|
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]+)/$',
|
||||||
|
|||||||
147
src/kasu/xlsx.py
Normal file
147
src/kasu/xlsx.py
Normal file
@@ -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)
|
||||||
@@ -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()
|
|
||||||
|
|
||||||
126
src/mahjong_ranking/management/commands/exportranking.py
Normal file
126
src/mahjong_ranking/management/commands/exportranking.py
Normal file
@@ -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()
|
||||||
@@ -4,30 +4,39 @@
|
|||||||
Recalculate Mahjong Rankings...
|
Recalculate Mahjong Rankings...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from datetime import date, datetime, time
|
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 import timezone
|
||||||
|
from django.utils.dateparse import parse_date
|
||||||
|
|
||||||
|
from mahjong_ranking import models
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
""" Recalculate all Kyu/Dan Rankings """
|
""" Recalculate all Kyu/Dan Rankings """
|
||||||
|
|
||||||
help = "Recalculate all Kyu/Dan Rankings"
|
help = "Recalculate the Kyu/Dan Rankings"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument('--since', nargs='?', type=parse_date)
|
parser.add_argument('-s', '--since', nargs='?', type=parse_date,
|
||||||
parser.add_argument('--until', nargs='?', type=parse_date)
|
metavar='YYYY-MM-DD',
|
||||||
parser.add_argument('--forcerecalc', action='store_true')
|
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):
|
def handle(self, *args, **options):
|
||||||
since = options.get('since', None)
|
since = options.get('since', None)
|
||||||
until = options.get('until', None)
|
until = options.get('until', None)
|
||||||
force_recalc = options.get('forcerecalc')
|
force_recalc = options.get('force')
|
||||||
if isinstance(since, date):
|
if isinstance(since, date):
|
||||||
since = datetime.combine(since, time(0, 0, 0))
|
since = datetime.combine(since, time(0, 0, 0))
|
||||||
since = timezone.make_aware(since)
|
since = timezone.make_aware(since)
|
||||||
if isinstance(until, date):
|
if isinstance(until, date):
|
||||||
until = datetime.combine(until, time(23, 59, 59))
|
until = datetime.combine(until, time(23, 59, 59))
|
||||||
until = timezone.make_aware(until)
|
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)
|
||||||
@@ -362,11 +362,22 @@ class KyuDanRanking(models.Model):
|
|||||||
verbose_name = _(u'Kyū/Dan Ranking')
|
verbose_name = _(u'Kyū/Dan Ranking')
|
||||||
verbose_name_plural = _(u'Kyū/Dan Rankings')
|
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):
|
def __str__(self):
|
||||||
if self.dan is not None:
|
if self.dan is not None:
|
||||||
return u"%s - %d. Dan" % (self.user.username, self.dan or 1)
|
return u"%s - %d. Dan" % (self.user.username, self.dan or 1)
|
||||||
else:
|
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):
|
def append_3_in_a_row_bonuspoints(self, hanchan):
|
||||||
u"""
|
u"""
|
||||||
@@ -382,7 +393,6 @@ class KyuDanRanking(models.Model):
|
|||||||
self.wins_in_a_row = 0
|
self.wins_in_a_row = 0
|
||||||
return
|
return
|
||||||
if self.wins_in_a_row >= 3 and self.dan < 9:
|
if self.wins_in_a_row >= 3 and self.dan < 9:
|
||||||
|
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
'adding bonuspoints for 3 wins in a row for %s', self.user)
|
'adding bonuspoints for 3 wins in a row for %s', self.user)
|
||||||
new_dan_rank = self.dan + 1
|
new_dan_rank = self.dan + 1
|
||||||
|
|||||||
@@ -50,6 +50,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{% if kyu_dan_ranking.legacy_date %}
|
||||||
|
<p><strong>Frühere Dan Punkte vom {{ kyu_dan_ranking.legacy_date|date }}:</strong> {{kyu_dan_ranking.legacy_dan_points }}</p>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block buttonbar %}
|
{% block buttonbar %}
|
||||||
|
|||||||
@@ -40,3 +40,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttonbar %}
|
||||||
|
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -48,3 +48,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttonbar %}
|
||||||
|
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -72,5 +72,8 @@
|
|||||||
</form>
|
</form>
|
||||||
</td></tr></tfoot>
|
</td></tr></tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttonbar %}
|
||||||
|
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -12,6 +12,7 @@ from django.utils.translation import ugettext as _
|
|||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
from events.mixins import EventDetailMixin
|
from events.mixins import EventDetailMixin
|
||||||
|
from kasu import xlsx
|
||||||
from . import forms, models
|
from . import forms, models
|
||||||
from .mixins import MahjongMixin
|
from .mixins import MahjongMixin
|
||||||
|
|
||||||
@@ -28,56 +29,6 @@ KYU_DAN_ORDER = { # map sort URL args to Django ORM order_by args
|
|||||||
'-username': ('-user__username',)
|
'-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,
|
class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin,
|
||||||
generic.DeleteView):
|
generic.DeleteView):
|
||||||
@@ -235,48 +186,67 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def get_xlsx(self, request, *args, **kwargs):
|
def get_xlsx(self, request, *args, **kwargs):
|
||||||
from management.commands.export_ranking import geneate_excel
|
|
||||||
self.object_list = self.get_queryset()
|
self.object_list = self.get_queryset()
|
||||||
allow_empty = self.get_allow_empty()
|
|
||||||
response = django.http.HttpResponse(
|
response = django.http.HttpResponse(
|
||||||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||||
response[
|
response['Content-Disposition'] = 'attachment; ' \
|
||||||
'Content-Disposition'] = 'attachment; filename="{xlsx_filename}"'.format(xlsx_filename=self.xlsx_filename)
|
'filename="{xlsx_filename}"'.format(
|
||||||
xlxs_workbook = geneate_excel()
|
xlsx_filename=self.xlsx_filename)
|
||||||
print(self.xlsx_columns)
|
xlxs_workbook = xlsx.Workbook()
|
||||||
generate_sheet(xlxs_workbook,
|
xlxs_workbook.generate_sheet(
|
||||||
title=self.xlsx_filename,
|
title=self.xlsx_filename.split('.')[0],
|
||||||
columns_settings=self.xlsx_columns,
|
columns_settings=self.xlsx_columns,
|
||||||
object_list=self.object_list
|
object_list=self.object_list
|
||||||
)
|
)
|
||||||
xlxs_workbook.create_sheet()
|
|
||||||
xlxs_workbook.save(response)
|
xlxs_workbook.save(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class PlayerDanScore(PlayerScore):
|
class PlayerDanScore(PlayerScore):
|
||||||
template_name = 'mahjong_ranking/player_dan_score.html'
|
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):
|
def get_queryset(self):
|
||||||
kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user)
|
self.kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user)
|
||||||
self.xlsx_filename = "{username}_dan_score.xlsx".format(
|
return models.Hanchan.objects.dan_hanchans(
|
||||||
username=self.user.username)
|
user=self.user,
|
||||||
return models.Hanchan.objects.dan_hanchans(user=self.user,
|
since=self.kyu_dan_ranking.legacy_date)
|
||||||
since=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):
|
class PlayerInvalidScore(PlayerScore):
|
||||||
@@ -292,9 +262,47 @@ class PlayerKyuScore(PlayerScore):
|
|||||||
template_name = 'mahjong_ranking/player_kyu_score.html'
|
template_name = 'mahjong_ranking/player_kyu_score.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.xlsx_filename = "{username}_kyu_score.xlsx".format(
|
self.kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user)
|
||||||
username=self.user.username)
|
return models.Hanchan.objects.kyu_hanchans(
|
||||||
return models.Hanchan.objects.kyu_hanchans(self.user)
|
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):
|
class PlayerLadderScore(PlayerScore):
|
||||||
@@ -311,10 +319,44 @@ class PlayerLadderScore(PlayerScore):
|
|||||||
|
|
||||||
def get_queryset(self, **kwargs):
|
def get_queryset(self, **kwargs):
|
||||||
self.season = int(self.request.GET.get('season', date.today().year))
|
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(
|
hanchan_list = models.Hanchan.objects.season_hanchans(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
season=self.season
|
season=self.season
|
||||||
)
|
)
|
||||||
return hanchan_list
|
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
|
||||||
|
)
|
||||||
|
|||||||
52
src/maistar_ranking/migrations/0007_auto_20171214_1215.py
Normal file
52
src/maistar_ranking/migrations/0007_auto_20171214_1215.py
Normal file
@@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -216,6 +216,10 @@ class Membership(AbstractUser):
|
|||||||
verbose_name = _('Membership')
|
verbose_name = _('Membership')
|
||||||
verbose_name_plural = _('Memberships')
|
verbose_name_plural = _('Memberships')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self):
|
||||||
|
return " ".join([self.last_name, self.first_name])
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,10 @@
|
|||||||
<h3>Mahjong</h3>
|
<h3>Mahjong</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{% if kyu_dan_ranking.dan %}
|
{% if kyu_dan_ranking.dan %}
|
||||||
<li><strong>{{kyu_dan_ranking.dan}}. Dan: </strong> {{ kyu_dan_ranking.dan_points }} {% trans 'Points' %}</li>
|
<li>
|
||||||
|
<strong>{{kyu_dan_ranking.dan}}. Dan: </strong> {{ kyu_dan_ranking.dan_points }} {% trans 'Points' %}
|
||||||
|
({% trans 'Maximum' %}: {{ kyu_dan_ranking.max_dan_points }})
|
||||||
|
</li>
|
||||||
{% elif kyu_dan_ranking.kyu%}
|
{% elif kyu_dan_ranking.kyu%}
|
||||||
<li><strong>{{kyu_dan_ranking.kyu}}. Kyu: </strong> {{ kyu_dan_ranking.kyu_points }} {% trans 'Points' %}</li>
|
<li><strong>{{kyu_dan_ranking.kyu}}. Kyu: </strong> {{ kyu_dan_ranking.kyu_points }} {% trans 'Points' %}</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user