Merge branch 'master' into css3_redesign
# Conflicts: # requirements/base.txt # src/content/locale/de/LC_MESSAGES/django.mo # src/content/locale/de/LC_MESSAGES/django.po # src/events/locale/de/LC_MESSAGES/django.mo # src/events/locale/de/LC_MESSAGES/django.po # src/kasu/locale/de/LC_MESSAGES/django.po # src/mahjong_ranking/locale/de/LC_MESSAGES/django.mo # src/mahjong_ranking/locale/de/LC_MESSAGES/django.po # src/maistar_ranking/locale/de/LC_MESSAGES/django.po # src/membership/locale/de/LC_MESSAGES/django.mo # src/membership/locale/de/LC_MESSAGES/django.po # src/utils/locale/de/LC_MESSAGES/django.po
This commit is contained in:
@@ -21,8 +21,6 @@ def recalculate(modeladmin, request, queryset): # Ignore PyLintBear (W0613)
|
||||
for ladder_ranking in queryset:
|
||||
set_dirty(user=ladder_ranking.user_id,
|
||||
season=ladder_ranking.season)
|
||||
|
||||
|
||||
recalculate.short_description = _("Recalculate")
|
||||
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@ Created on 04.10.2011
|
||||
|
||||
@author: christian
|
||||
"""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django import forms
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from events.models import Event
|
||||
from . import models
|
||||
|
||||
USER_MODEL = get_user_model()
|
||||
@@ -47,6 +48,17 @@ class HanchanForm(forms.ModelForm):
|
||||
self.fields[player_input_score].widget.attrs['type'] = 'number'
|
||||
self.fields[player].queryset = player_queryset
|
||||
|
||||
def is_valid(self):
|
||||
ret = forms.Form.is_valid(self)
|
||||
for field, errors in self.errors.items():
|
||||
message = ", ".join(set(errors))
|
||||
print(type(field), type(errors))
|
||||
self.fields[field].widget.attrs.update({
|
||||
'class': self.fields[field].widget.attrs.get('class', '') + ' error',
|
||||
'title': message
|
||||
})
|
||||
return ret
|
||||
|
||||
|
||||
class HanchanAdminForm(HanchanForm):
|
||||
""" Extends the HanchanForm for users with admin privileges.
|
||||
@@ -58,3 +70,9 @@ class HanchanAdminForm(HanchanForm):
|
||||
""" Extend the formfields to add the confirmed checkbox. """
|
||||
model = models.Hanchan
|
||||
fields = HanchanForm.Meta.fields + ('confirmed',)
|
||||
|
||||
|
||||
HanchanFormset = forms.inlineformset_factory(Event, models.Hanchan,
|
||||
form=HanchanForm,
|
||||
extra=1,
|
||||
can_delete=True)
|
||||
|
||||
Binary file not shown.
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: kasu.mahjong_ranking\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-04-27 09:49+0200\n"
|
||||
"PO-Revision-Date: 2018-04-27 09:54+0105\n"
|
||||
"POT-Creation-Date: 2018-01-11 22:50+0100\n"
|
||||
"PO-Revision-Date: 2018-01-12 15:23+0105\n"
|
||||
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
|
||||
"Language-Team: Kasu <verein@kasu.at>\n"
|
||||
"Language: de\n"
|
||||
@@ -19,369 +19,382 @@ msgstr ""
|
||||
"X-Generator: Poedit 1.8.9\n"
|
||||
"X-Translated-Using: django-rosetta 0.7.14\n"
|
||||
|
||||
#: admin.py:26
|
||||
#: src/mahjong_ranking/admin.py:24
|
||||
msgid "Recalculate"
|
||||
msgstr "Neuberechnen"
|
||||
|
||||
#: admin.py:36
|
||||
#: src/mahjong_ranking/admin.py:34
|
||||
msgid "Confirm"
|
||||
msgstr "Bestätigen"
|
||||
|
||||
#: admin.py:46
|
||||
#: src/mahjong_ranking/admin.py:44
|
||||
msgid "Set unconfirmed"
|
||||
msgstr "Als unbestätigt markieren"
|
||||
|
||||
#: forms.py:21
|
||||
#: src/mahjong_ranking/forms.py:22
|
||||
msgid "start"
|
||||
msgstr "Beginn"
|
||||
|
||||
#: models.py:91 templates/mahjong_ranking/player_dan_score.html:14
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:13
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:15
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:15
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:10
|
||||
#: src/mahjong_ranking/models.py:91
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:14
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:13
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:15
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:15
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:10
|
||||
msgid "Start"
|
||||
msgstr "Beginn"
|
||||
|
||||
#: models.py:92
|
||||
#: src/mahjong_ranking/models.py:92
|
||||
msgid "This is crucial to get the right Hanchans that scores"
|
||||
msgstr "Wichtig damit die richtigen Hanchans in die Wertung kommen."
|
||||
|
||||
#: models.py:99
|
||||
#: src/mahjong_ranking/models.py:99
|
||||
msgid "Player 1"
|
||||
msgstr "Spieler 1"
|
||||
|
||||
#: models.py:100 models.py:102 models.py:119 models.py:121 models.py:138
|
||||
#: models.py:140 models.py:157 models.py:159
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:19
|
||||
#: templates/mahjong_ranking/eventranking_list.html:21
|
||||
#: templates/mahjong_ranking/hanchan_confirm_delete.html:16
|
||||
#: templates/mahjong_ranking/hanchan_form.html:19
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:35
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:32
|
||||
#: src/mahjong_ranking/models.py:100 src/mahjong_ranking/models.py:102
|
||||
#: src/mahjong_ranking/models.py:119 src/mahjong_ranking/models.py:121
|
||||
#: src/mahjong_ranking/models.py:138 src/mahjong_ranking/models.py:140
|
||||
#: src/mahjong_ranking/models.py:157 src/mahjong_ranking/models.py:159
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:19
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:21
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:16
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:19
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:35
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:32
|
||||
msgid "Score"
|
||||
msgstr "Punkte"
|
||||
|
||||
#: models.py:112 models.py:131 models.py:150 models.py:169 models.py:171
|
||||
#: templates/mahjong_ranking/hanchan_form.html:20
|
||||
#: templates/mahjong_ranking/player_dan_score.html:18
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:17
|
||||
#: src/mahjong_ranking/models.py:112 src/mahjong_ranking/models.py:131
|
||||
#: src/mahjong_ranking/models.py:150 src/mahjong_ranking/models.py:169
|
||||
#: src/mahjong_ranking/models.py:171
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:20
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:18
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:17
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
#: models.py:118
|
||||
#: src/mahjong_ranking/models.py:118
|
||||
msgid "Player 2"
|
||||
msgstr "Spieler 2"
|
||||
|
||||
#: models.py:137
|
||||
#: src/mahjong_ranking/models.py:137
|
||||
msgid "Player 3"
|
||||
msgstr "Spieler 3"
|
||||
|
||||
#: models.py:156
|
||||
#: src/mahjong_ranking/models.py:156
|
||||
msgid "Player 4"
|
||||
msgstr "Spieler 4"
|
||||
|
||||
#: models.py:173
|
||||
#: src/mahjong_ranking/models.py:173
|
||||
msgid "Has been Confirmed"
|
||||
msgstr "Wurde bestätigt"
|
||||
|
||||
#: models.py:174
|
||||
#: src/mahjong_ranking/models.py:174
|
||||
msgid "Only valid and confirmed Hanchans will be counted in the rating."
|
||||
msgstr "Nur gültige und bestätigte Hanchans kommen in die Wertung."
|
||||
|
||||
#: models.py:179 models.py:603 templates/mahjong_ranking/ladder_redbox.html:29
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:63
|
||||
#: src/mahjong_ranking/models.py:179 src/mahjong_ranking/models.py:607
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:29
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:63
|
||||
msgid "Season"
|
||||
msgstr "Saison"
|
||||
|
||||
#: models.py:184
|
||||
#: src/mahjong_ranking/models.py:184
|
||||
msgid "Hanchan"
|
||||
msgstr "Hanchan"
|
||||
|
||||
#: models.py:185 templates/mahjong_ranking/eventranking_list.html:17
|
||||
#: src/mahjong_ranking/models.py:185
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:17
|
||||
msgid "Hanchans"
|
||||
msgstr "Hanchans"
|
||||
|
||||
#: models.py:188
|
||||
#: src/mahjong_ranking/models.py:188
|
||||
msgid "Hanchan from {0:%Y-%m-%d} at {0:%H:%M} with {1}"
|
||||
msgstr "Hanchan vom {0:%Y-%m-%d} um {0:%H:%M} mit {1}"
|
||||
|
||||
#: models.py:215
|
||||
#: src/mahjong_ranking/models.py:215
|
||||
#, python-format
|
||||
msgid "%s can't attend the same game multiple times"
|
||||
msgstr "%s kann an einem Spiel nicht mehrfach teilnehmen."
|
||||
|
||||
#: models.py:223
|
||||
#: src/mahjong_ranking/models.py:223
|
||||
msgid "Games in the future may not be added, Dr. Brown"
|
||||
msgstr "Spiele aus der Zukunft dürfen noch nicht erfasst werden. Dr. Brown."
|
||||
|
||||
#: models.py:225
|
||||
#: src/mahjong_ranking/models.py:225
|
||||
msgid "Only games during the event are allowed"
|
||||
msgstr "Nur Spiele während der Veranstaltung zählen."
|
||||
|
||||
#: models.py:228
|
||||
#: src/mahjong_ranking/models.py:228
|
||||
msgid "Gamescore is lower then 100.000 Pt."
|
||||
msgstr "Spielstand ist weniger als 100.000 Punkte"
|
||||
|
||||
#: models.py:230
|
||||
#: src/mahjong_ranking/models.py:230
|
||||
msgid "Gamescore is over 100.000 Pt."
|
||||
msgstr "Spielstand ist über 100.000 Punkte."
|
||||
|
||||
#: models.py:356
|
||||
#: src/mahjong_ranking/models.py:362
|
||||
msgid "Kyū/Dan Ranking"
|
||||
msgstr "Kyū/Dan Wertung"
|
||||
|
||||
#: models.py:357
|
||||
#: src/mahjong_ranking/models.py:363
|
||||
msgid "Kyū/Dan Rankings"
|
||||
msgstr "Kyū/Dan Wertungen"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:7
|
||||
msgid "Played Hanchans"
|
||||
msgstr "Gespielte Hanchans"
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:11
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:55
|
||||
#| msgid "Edit Hanchan"
|
||||
msgid "Edit Hanchans"
|
||||
msgstr "Hanchans bearbeiten"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:18
|
||||
#: templates/mahjong_ranking/hanchan_confirm_delete.html:15
|
||||
msgid "Place"
|
||||
msgstr "Platz"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:21
|
||||
#: templates/mahjong_ranking/hanchan_confirm_delete.html:18
|
||||
#: templates/mahjong_ranking/player_dan_score.html:17
|
||||
msgid "Dan Points"
|
||||
msgstr "Dan Punkte"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:23
|
||||
#: templates/mahjong_ranking/hanchan_confirm_delete.html:20
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:16
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:18
|
||||
msgid "Kyu Points"
|
||||
msgstr "Kyu Punkte"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:37
|
||||
#: templates/mahjong_ranking/hanchan_confirm_delete.html:4
|
||||
#: templates/mahjong_ranking/hanchan_confirm_delete.html:33
|
||||
#: templates/mahjong_ranking/player_dan_score.html:44
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:33
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:41
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:52
|
||||
msgid "Delete Hanchan"
|
||||
msgstr "Hanchan löschen"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:43
|
||||
#: templates/mahjong_ranking/hanchan_form.html:4
|
||||
#: templates/mahjong_ranking/hanchan_form.html:14
|
||||
#: templates/mahjong_ranking/player_dan_score.html:47
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:36
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:44
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:55
|
||||
msgid "Edit Hanchan"
|
||||
msgstr "Hanchan bearbeiten"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:48
|
||||
msgid "No Hanchan has been added to this event yet."
|
||||
msgstr "Für diese Veranstaltung wurde noch keine Hanchan eingetragen."
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:54
|
||||
#: templates/mahjong_ranking/eventranking_list.html:51
|
||||
msgid "Edit Event"
|
||||
msgstr "Veranstaltung bearbeiten"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:55
|
||||
#: templates/mahjong_ranking/eventranking_list.html:52
|
||||
#: templates/mahjong_ranking/hanchan_form.html:4
|
||||
#: templates/mahjong_ranking/hanchan_form.html:14
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:82
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:56
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:52
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:14
|
||||
msgid "Add Hanchan"
|
||||
msgstr "Hanchan hinzufügen"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:4
|
||||
#: templates/mahjong_ranking/eventranking_list.html:5
|
||||
msgid "Tournament Ranking"
|
||||
msgstr "Turnierwertung"
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:84
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:37
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:4
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:33
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:44
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:33
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:41
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:52
|
||||
msgid "Delete Hanchan"
|
||||
msgstr "Hanchan löschen"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:12
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:30
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:23
|
||||
msgid "Rank"
|
||||
msgstr "Rang"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:13
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:17
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:24
|
||||
msgid "Avatar"
|
||||
msgstr "Avatar"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:14
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:20
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:25
|
||||
msgid "Nickname"
|
||||
msgstr "Spitzname"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:15
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:26
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:16
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:27
|
||||
msgid "Average"
|
||||
msgstr "Durchschnitt"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:20
|
||||
#: templates/mahjong_ranking/player_dan_score.html:15
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:15
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:16
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:16
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:31
|
||||
msgid "Placement"
|
||||
msgstr "Platzierung"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:22
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:33
|
||||
msgid "count"
|
||||
msgstr "Anzahl"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:23
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:34
|
||||
msgid "good"
|
||||
msgstr "gut"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:24
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:35
|
||||
msgid "won"
|
||||
msgstr "gewonnen"
|
||||
|
||||
#: templates/mahjong_ranking/hanchan_confirm_delete.html:39
|
||||
msgid "Cancel"
|
||||
msgstr "Abbruch"
|
||||
|
||||
#: templates/mahjong_ranking/hanchan_confirm_delete.html:40
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: templates/mahjong_ranking/hanchan_form.html:18
|
||||
msgid "Player"
|
||||
msgstr "Spieler"
|
||||
|
||||
#: templates/mahjong_ranking/hanchan_form.html:58
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: templates/mahjong_ranking/hanchan_form.html:71
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:94
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:71
|
||||
msgid "back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: templates/mahjong_ranking/hanchan_form.html:72
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:95
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:72
|
||||
msgid "save"
|
||||
msgstr "Speichern"
|
||||
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:4
|
||||
#| msgid "Player List"
|
||||
msgid "Players list"
|
||||
msgstr "Spielerliste"
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:7
|
||||
msgid "Played Hanchans"
|
||||
msgstr "Gespielte Hanchans"
|
||||
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:9
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:18
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:15
|
||||
msgid "Place"
|
||||
msgstr "Platz"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:21
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:18
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:17
|
||||
msgid "Dan Points"
|
||||
msgstr "Dan Punkte"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:23
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:20
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:16
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:18
|
||||
msgid "Kyu Points"
|
||||
msgstr "Kyu Punkte"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:43
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:14
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:47
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:36
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:44
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:55
|
||||
msgid "Edit Hanchan"
|
||||
msgstr "Hanchan bearbeiten"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:48
|
||||
msgid "No Hanchan has been added to this event yet."
|
||||
msgstr "Für diese Veranstaltung wurde noch keine Hanchan eingetragen."
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:54
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:51
|
||||
msgid "Edit Event"
|
||||
msgstr "Veranstaltung bearbeiten"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:4
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:5
|
||||
msgid "Tournament Ranking"
|
||||
msgstr "Turnierwertung"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:12
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:30
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:23
|
||||
msgid "Rank"
|
||||
msgstr "Rang"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:13
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:17
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:24
|
||||
msgid "Avatar"
|
||||
msgstr "Avatar"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:14
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:20
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:25
|
||||
msgid "Nickname"
|
||||
msgstr "Spitzname"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:15
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:26
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:16
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:27
|
||||
msgid "Average"
|
||||
msgstr "Durchschnitt"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:20
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:15
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:15
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:16
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:16
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:31
|
||||
msgid "Placement"
|
||||
msgstr "Platzierung"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:22
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:33
|
||||
msgid "count"
|
||||
msgstr "Anzahl"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:23
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:34
|
||||
msgid "good"
|
||||
msgstr "gut"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:24
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:35
|
||||
msgid "won"
|
||||
msgstr "gewonnen"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:39
|
||||
msgid "Cancel"
|
||||
msgstr "Abbruch"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:40
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:18
|
||||
msgid "Player"
|
||||
msgstr "Spieler"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:58
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:4
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:9
|
||||
msgid "Player List"
|
||||
msgstr "Spieler Liste"
|
||||
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:25
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:25
|
||||
msgid "Full Name"
|
||||
msgstr "Voller Name"
|
||||
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:40
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:40
|
||||
msgid "Games Total"
|
||||
msgstr "Spiele total"
|
||||
|
||||
#: templates/mahjong_ranking/ladder_redbox.html:3
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:3
|
||||
msgid "Latest Hanchans"
|
||||
msgstr "Letzten Hanchans"
|
||||
|
||||
#: templates/mahjong_ranking/ladder_redbox.html:15
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:15
|
||||
msgid "Latest Events"
|
||||
msgstr "Letzte Veranstaltungen"
|
||||
|
||||
#: templates/mahjong_ranking/ladder_redbox.html:27
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:27
|
||||
msgid "Ladder Archive"
|
||||
msgstr "Ladder Archiv"
|
||||
|
||||
#: templates/mahjong_ranking/player_dan_score.html:4
|
||||
#: templates/mahjong_ranking/player_dan_score.html:5
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:4
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:5
|
||||
msgid "Dan Score for"
|
||||
msgstr "Dan Wertung für"
|
||||
|
||||
#: templates/mahjong_ranking/player_dan_score.html:8
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:8
|
||||
msgid "Hanchans that apply to the Dan Score"
|
||||
msgstr "Hanchans welche zur Dan Wertung zählen"
|
||||
|
||||
#: templates/mahjong_ranking/player_dan_score.html:12
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:13
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:13
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:12
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:13
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:13
|
||||
msgid "Date"
|
||||
msgstr "Datum"
|
||||
|
||||
#: templates/mahjong_ranking/player_dan_score.html:13
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:12
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:14
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:14
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:13
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:12
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:14
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:14
|
||||
msgid "Event"
|
||||
msgstr "Veranstaltung"
|
||||
|
||||
#: templates/mahjong_ranking/player_dan_score.html:16
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:14
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:17
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:17
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:16
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:14
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:17
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:17
|
||||
msgid "Players"
|
||||
msgstr "Spieler"
|
||||
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:4
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:6
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:4
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:6
|
||||
msgid "Unconfirmed Hanchans from"
|
||||
msgstr "Nicht bestätigte Hanchans von"
|
||||
|
||||
#: templates/mahjong_ranking/player_invalid_score.html:9
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:9
|
||||
msgid "Invalid hanchans with"
|
||||
msgstr "Ungültige Hanchans mit"
|
||||
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:4
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:6
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:4
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:6
|
||||
msgid "Kyu Score for"
|
||||
msgstr "Kyu Wertung für"
|
||||
|
||||
#: templates/mahjong_ranking/player_kyu_score.html:9
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:9
|
||||
msgid "Hanchans that apply to the Kyu Score"
|
||||
msgstr "Hanchans welche zur Kyu Wertung zählen"
|
||||
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:4
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:5
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:4
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:5
|
||||
msgid "Ladder Score for"
|
||||
msgstr "Ladder Wertung für"
|
||||
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:8
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:8
|
||||
msgid "Hanchans that apply to the Ladder Score"
|
||||
msgstr "Hanchans welche in der Ladder zählen"
|
||||
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:71
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:71
|
||||
msgid "Go"
|
||||
msgstr "Los"
|
||||
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:11
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:11
|
||||
msgid "End"
|
||||
msgstr "Ende"
|
||||
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:12
|
||||
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:12
|
||||
msgid "Participants"
|
||||
msgstr "Teilnehmer"
|
||||
|
||||
#: views.py:102
|
||||
#: src/mahjong_ranking/views.py:104
|
||||
#, python-format
|
||||
msgid "%s has been updated successfully."
|
||||
msgstr "%s wurde erfolgreich aktualisiert."
|
||||
|
||||
#: views.py:105
|
||||
#: src/mahjong_ranking/views.py:107
|
||||
#, python-format
|
||||
msgid "%s has been added successfully. You can now add a new one."
|
||||
msgstr "%s wurde erfolgreich hinzugefügt. Du kannst eine neue eintragen."
|
||||
|
||||
#: views.py:169
|
||||
#: src/mahjong_ranking/views.py:207
|
||||
msgid "No user found matching the name {}"
|
||||
msgstr "Kein Benutzer mit dem Namen %s gefunden"
|
||||
|
||||
|
||||
@@ -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, force_recalc=True)
|
||||
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()
|
||||
@@ -17,16 +17,22 @@ class Command(BaseCommand):
|
||||
parser.add_argument('reset_date', type=parse_date)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
reset_date = timezone.make_aware(datetime.combine(options.get('reset_date'), time(23, 59, 59)))
|
||||
# models.KyuDanRanking.objects.update(until=reset_date, force_recalc=True)
|
||||
dan_rankigns = models.KyuDanRanking.objects.filter(dan__isnull=False)
|
||||
for ranking in dan_rankigns:
|
||||
legacy_attrs = [ key for key in models.KyuDanRanking.__dict__.keys()
|
||||
if key.startswith('legacy') ]
|
||||
legacy_attrs.remove('legacy_date')
|
||||
reset_date = timezone.make_aware(datetime.combine(
|
||||
options.get('reset_date'), time(23, 59, 59)))
|
||||
models.KyuDanRanking.objects.update(until=reset_date, force_recalc=True)
|
||||
for ranking in models.KyuDanRanking.objects.filter(dan__gt=0):
|
||||
print(ranking)
|
||||
ranking.dan = 1
|
||||
ranking.dan_points = 0
|
||||
ranking.kyu = None
|
||||
ranking.kyu_points = 0
|
||||
ranking.wins_in_a_row = 0
|
||||
ranking.legacy_date = reset_date.date()
|
||||
ranking.legacy_hanchan_count = ranking.hanchan_count
|
||||
ranking.legacy_dan_points = ranking.dan_points
|
||||
ranking.legacy_kyu_points = ranking.kyu_points
|
||||
for legacy_attr in legacy_attrs:
|
||||
attr = legacy_attr.split("_", maxsplit=1)[1]
|
||||
print(ranking, legacy_attr, attr, getattr(ranking, attr))
|
||||
setattr(ranking, legacy_attr, getattr(ranking, attr))
|
||||
ranking.save()
|
||||
|
||||
|
||||
|
||||
@@ -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('forecerecalc', False)
|
||||
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)
|
||||
@@ -23,8 +23,8 @@ class HanchanManager(models.Manager):
|
||||
:return: QuerySet Object
|
||||
"""
|
||||
if user:
|
||||
return self.user_hanchans(
|
||||
user, confirmed=True, since=since, until=until, **filter_args)
|
||||
return self.user_hanchans(user, confirmed=True, until=until,
|
||||
**filter_args)
|
||||
hanchans = self.filter(confirmed=True, **filter_args)
|
||||
if since:
|
||||
hanchans = hanchans.filter(start__gt=since)
|
||||
@@ -101,7 +101,7 @@ class HanchanManager(models.Manager):
|
||||
)
|
||||
queryset = queryset.filter(**filter_args)
|
||||
if since:
|
||||
queryset = queryset.filter(start__gt=since)
|
||||
queryset = queryset.filter(start__gte=since)
|
||||
if until:
|
||||
queryset = queryset.filter(start__lte=until)
|
||||
queryset = queryset.select_related().order_by('-start')
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('events', '0005_auto_20150907_2021'),
|
||||
@@ -17,15 +16,19 @@ class Migration(migrations.Migration):
|
||||
name='EventRanking',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID',
|
||||
serialize=False, auto_created=True, primary_key=True)),
|
||||
('placement', models.PositiveIntegerField(null=True, blank=True)),
|
||||
serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('placement',
|
||||
models.PositiveIntegerField(null=True, blank=True)),
|
||||
('avg_placement', models.FloatField(default=4)),
|
||||
('avg_score', models.FloatField(default=0)),
|
||||
('hanchan_count', models.PositiveIntegerField(default=0)),
|
||||
('good_hanchans', models.PositiveIntegerField(default=0)),
|
||||
('won_hanchans', models.PositiveIntegerField(default=0)),
|
||||
('event', models.ForeignKey(to='events.Event')),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
||||
('event', models.ForeignKey(to='events.Event',
|
||||
on_delete=models.CASCADE)),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('placement', 'avg_placement', '-avg_score'),
|
||||
@@ -35,10 +38,13 @@ class Migration(migrations.Migration):
|
||||
name='Hanchan',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID',
|
||||
serialize=False, auto_created=True, primary_key=True)),
|
||||
serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('start', models.DateTimeField(
|
||||
help_text='Wichtig damit die richtigen Hanchans in die Wertung kommen.', verbose_name='Beginn')),
|
||||
('player1_input_score', models.IntegerField(verbose_name='Punkte')),
|
||||
help_text='Wichtig damit die richtigen Hanchans in die Wertung kommen.',
|
||||
verbose_name='Beginn')),
|
||||
('player1_input_score',
|
||||
models.IntegerField(verbose_name='Punkte')),
|
||||
('player1_game_score', models.PositiveIntegerField(
|
||||
default=0, verbose_name='Punkte', editable=False)),
|
||||
('player1_placement', models.PositiveSmallIntegerField(
|
||||
@@ -50,8 +56,11 @@ class Migration(migrations.Migration):
|
||||
('player1_bonus_points', models.SmallIntegerField(
|
||||
null=True, editable=False, blank=True)),
|
||||
('player1_comment', models.CharField(verbose_name='Anmerkung',
|
||||
max_length=255, editable=False, blank=True)),
|
||||
('player2_input_score', models.IntegerField(verbose_name='Punkte')),
|
||||
max_length=255,
|
||||
editable=False,
|
||||
blank=True)),
|
||||
('player2_input_score',
|
||||
models.IntegerField(verbose_name='Punkte')),
|
||||
('player2_game_score', models.PositiveIntegerField(
|
||||
default=0, verbose_name='Punkte', editable=False)),
|
||||
('player2_placement', models.PositiveSmallIntegerField(
|
||||
@@ -63,8 +72,11 @@ class Migration(migrations.Migration):
|
||||
('player2_bonus_points', models.SmallIntegerField(
|
||||
null=True, editable=False, blank=True)),
|
||||
('player2_comment', models.CharField(verbose_name='Anmerkung',
|
||||
max_length=255, editable=False, blank=True)),
|
||||
('player3_input_score', models.IntegerField(verbose_name='Punkte')),
|
||||
max_length=255,
|
||||
editable=False,
|
||||
blank=True)),
|
||||
('player3_input_score',
|
||||
models.IntegerField(verbose_name='Punkte')),
|
||||
('player3_game_score', models.PositiveIntegerField(
|
||||
default=0, verbose_name='Punkte', editable=False)),
|
||||
('player3_placement', models.PositiveSmallIntegerField(
|
||||
@@ -76,8 +88,11 @@ class Migration(migrations.Migration):
|
||||
('player3_bonus_points', models.SmallIntegerField(
|
||||
null=True, editable=False, blank=True)),
|
||||
('player3_comment', models.CharField(verbose_name='Anmerkung',
|
||||
max_length=255, editable=False, blank=True)),
|
||||
('player4_input_score', models.IntegerField(verbose_name='Punkte')),
|
||||
max_length=255,
|
||||
editable=False,
|
||||
blank=True)),
|
||||
('player4_input_score',
|
||||
models.IntegerField(verbose_name='Punkte')),
|
||||
('player4_game_score', models.PositiveIntegerField(
|
||||
default=0, verbose_name='Punkte', editable=False)),
|
||||
('player4_placement', models.PositiveSmallIntegerField(
|
||||
@@ -89,22 +104,37 @@ class Migration(migrations.Migration):
|
||||
('player4_bonus_points', models.SmallIntegerField(
|
||||
null=True, editable=False, blank=True)),
|
||||
('player4_comment', models.CharField(verbose_name='Anmerkung',
|
||||
max_length=255, editable=False, blank=True)),
|
||||
('comment', models.TextField(verbose_name='Anmerkung', blank=True)),
|
||||
max_length=255,
|
||||
editable=False,
|
||||
blank=True)),
|
||||
('comment',
|
||||
models.TextField(verbose_name='Anmerkung', blank=True)),
|
||||
('confirmed', models.BooleanField(
|
||||
default=True, help_text='Nur g\xfcltige und best\xe4tigte Hanchans kommen in die Wertung.', verbose_name='Wurde best\xe4tigt')),
|
||||
('player_names', models.CharField(max_length=255, editable=False)),
|
||||
default=True,
|
||||
help_text='Nur g\xfcltige und best\xe4tigte Hanchans kommen in die Wertung.',
|
||||
verbose_name='Wurde best\xe4tigt')),
|
||||
('player_names',
|
||||
models.CharField(max_length=255, editable=False)),
|
||||
('season', models.PositiveSmallIntegerField(
|
||||
verbose_name='Saison', editable=False, db_index=True)),
|
||||
('event', models.ForeignKey(to='events.Event')),
|
||||
('event', models.ForeignKey(to='events.Event',
|
||||
on_delete=models.CASCADE)),
|
||||
('player1', models.ForeignKey(related_name='user_hanchan+',
|
||||
verbose_name='Spieler 1', to=settings.AUTH_USER_MODEL)),
|
||||
verbose_name='Spieler 1',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)),
|
||||
('player2', models.ForeignKey(related_name='user_hanchan+',
|
||||
verbose_name='Spieler 2', to=settings.AUTH_USER_MODEL)),
|
||||
verbose_name='Spieler 2',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)),
|
||||
('player3', models.ForeignKey(related_name='user_hanchan+',
|
||||
verbose_name='Spieler 3', to=settings.AUTH_USER_MODEL)),
|
||||
verbose_name='Spieler 3',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)),
|
||||
('player4', models.ForeignKey(related_name='user_hanchan+',
|
||||
verbose_name='Spieler 4', to=settings.AUTH_USER_MODEL)),
|
||||
verbose_name='Spieler 4',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-start',),
|
||||
@@ -116,8 +146,11 @@ class Migration(migrations.Migration):
|
||||
name='KyuDanRanking',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID',
|
||||
serialize=False, auto_created=True, primary_key=True)),
|
||||
('dan', models.PositiveSmallIntegerField(null=True, blank=True)),
|
||||
serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
(
|
||||
'dan',
|
||||
models.PositiveSmallIntegerField(null=True, blank=True)),
|
||||
('dan_points', models.PositiveIntegerField(default=0)),
|
||||
('kyu', models.PositiveSmallIntegerField(
|
||||
default=10, null=True, blank=True)),
|
||||
@@ -128,7 +161,8 @@ class Migration(migrations.Migration):
|
||||
('legacy_date', models.DateField(null=True, blank=True)),
|
||||
('legacy_dan_points', models.PositiveIntegerField(default=0)),
|
||||
('legacy_kyu_points', models.PositiveIntegerField(default=0)),
|
||||
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
|
||||
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-dan', '-dan_points', '-kyu_points'),
|
||||
@@ -140,15 +174,19 @@ class Migration(migrations.Migration):
|
||||
name='SeasonRanking',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID',
|
||||
serialize=False, auto_created=True, primary_key=True)),
|
||||
('season', models.PositiveSmallIntegerField(verbose_name='Saison')),
|
||||
('placement', models.PositiveIntegerField(null=True, blank=True)),
|
||||
serialize=False, auto_created=True,
|
||||
primary_key=True)),
|
||||
('season',
|
||||
models.PositiveSmallIntegerField(verbose_name='Saison')),
|
||||
('placement',
|
||||
models.PositiveIntegerField(null=True, blank=True)),
|
||||
('avg_placement', models.FloatField(null=True, blank=True)),
|
||||
('avg_score', models.FloatField(null=True, blank=True)),
|
||||
('hanchan_count', models.PositiveIntegerField(default=0)),
|
||||
('good_hanchans', models.PositiveIntegerField(default=0)),
|
||||
('won_hanchans', models.PositiveIntegerField(default=0)),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('placement', 'avg_placement', '-avg_score'),
|
||||
|
||||
87
src/mahjong_ranking/migrations/0006_auto_20171214_1318.py
Normal file
87
src/mahjong_ranking/migrations/0006_auto_20171214_1318.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.8 on 2017-12-14 12:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mahjong_ranking', '0005_auto_20171115_0653'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_dan',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_good_hanchans',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_kyu',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_won_hanchans',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='max_dan_points',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventranking',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hanchan',
|
||||
name='player1',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 1'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hanchan',
|
||||
name='player2',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 2'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hanchan',
|
||||
name='player3',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 3'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hanchan',
|
||||
name='player4',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 4'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_dan_points',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_hanchan_count',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_kyu_points',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='seasonranking',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -9,8 +9,8 @@ from datetime import datetime, time
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
@@ -29,8 +29,8 @@ class EventRanking(models.Model):
|
||||
Sie beschränken sich aber auf einen Event und werden nur dann angestossen,
|
||||
wenn der Event als Turnier markiert wurde.
|
||||
"""
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||
event = models.ForeignKey(Event)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
placement = models.PositiveIntegerField(blank=True, null=True)
|
||||
avg_placement = models.FloatField(default=4)
|
||||
avg_score = models.FloatField(default=0)
|
||||
@@ -86,7 +86,7 @@ class Hanchan(models.Model):
|
||||
Es werden aber noch andere Tests durchgeführt, ob sie gültig ist.
|
||||
Außerdem gehört jede Hanchan zu einer Veranstaltung.
|
||||
"""
|
||||
event = models.ForeignKey(Event)
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
start = models.DateTimeField(
|
||||
_('Start'),
|
||||
help_text=_('This is crucial to get the right Hanchans that scores')
|
||||
@@ -94,7 +94,7 @@ class Hanchan(models.Model):
|
||||
|
||||
player1 = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='user_hanchan+',
|
||||
verbose_name=_('Player 1'))
|
||||
player1_input_score = models.IntegerField(_('Score'))
|
||||
@@ -113,7 +113,7 @@ class Hanchan(models.Model):
|
||||
|
||||
player2 = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='user_hanchan+',
|
||||
verbose_name=_('Player 2'))
|
||||
player2_input_score = models.IntegerField(_('Score'))
|
||||
@@ -132,7 +132,7 @@ class Hanchan(models.Model):
|
||||
|
||||
player3 = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='user_hanchan+',
|
||||
verbose_name=_('Player 3'))
|
||||
player3_input_score = models.IntegerField(_('Score'))
|
||||
@@ -151,7 +151,7 @@ class Hanchan(models.Model):
|
||||
|
||||
player4 = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='user_hanchan+',
|
||||
verbose_name=_('Player 4'))
|
||||
player4_input_score = models.IntegerField(_('Score'))
|
||||
@@ -335,18 +335,24 @@ class KyuDanRanking(models.Model):
|
||||
Im Gegensatz zum Ladder Ranking ist das nicht Saison gebunden.
|
||||
Deswegen läuft es getrennt.
|
||||
"""
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL)
|
||||
user = models.OneToOneField(settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE)
|
||||
dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||
dan_points = models.PositiveIntegerField(default=0)
|
||||
max_dan_points = models.PositiveIntegerField(default=0)
|
||||
kyu = models.PositiveSmallIntegerField(default=10, blank=True, null=True)
|
||||
kyu_points = models.PositiveIntegerField(default=0)
|
||||
won_hanchans = models.PositiveIntegerField(default=0)
|
||||
good_hanchans = models.PositiveIntegerField(default=0)
|
||||
hanchan_count = models.PositiveIntegerField(default=0)
|
||||
legacy_date = models.DateField(blank=True, null=True)
|
||||
legacy_hanchan_count = models.PositiveIntegerField(default=0)
|
||||
legacy_dan_points = models.PositiveIntegerField(default=0)
|
||||
legacy_kyu_points = models.PositiveIntegerField(default=0)
|
||||
legacy_dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||
legacy_dan_points = models.PositiveIntegerField(blank=True, null=True)
|
||||
legacy_kyu = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||
legacy_kyu_points = models.PositiveIntegerField(blank=True, null=True)
|
||||
legacy_hanchan_count = models.PositiveIntegerField(blank=True, null=True)
|
||||
legacy_good_hanchans = models.PositiveIntegerField(blank=True, null=True)
|
||||
legacy_won_hanchans = models.PositiveIntegerField(blank=True, null=True)
|
||||
wins_in_a_row = models.PositiveIntegerField(default=0)
|
||||
last_hanchan_date = models.DateTimeField(blank=True, null=True)
|
||||
objects = managers.KyuDanRankingManager()
|
||||
@@ -356,11 +362,22 @@ class KyuDanRanking(models.Model):
|
||||
verbose_name = _(u'Kyū/Dan Ranking')
|
||||
verbose_name_plural = _(u'Kyū/Dan Rankings')
|
||||
|
||||
def __unicode__(self):
|
||||
if self.dan_points is not None:
|
||||
@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"""
|
||||
@@ -368,12 +385,14 @@ class KyuDanRanking(models.Model):
|
||||
das er einen Dan Rang aufsteigt. Dies wird als Kommentar abgespeichert,
|
||||
um es besser nachvollziehen zu können.
|
||||
"""
|
||||
if self.dan and hanchan.placement == 1:
|
||||
if not self.dan or not settings.DAN_3_WINS_IN_A_ROW:
|
||||
return
|
||||
if hanchan.placement == 1:
|
||||
self.wins_in_a_row += 1
|
||||
else:
|
||||
self.wins_in_a_row = 0
|
||||
|
||||
if self.dan and self.wins_in_a_row >= 3 and self.dan < 9:
|
||||
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
|
||||
@@ -394,8 +413,8 @@ class KyuDanRanking(models.Model):
|
||||
bonus_points, new_dan_rank)
|
||||
self.dan_points += bonus_points
|
||||
self.wins_in_a_row = 0
|
||||
self.update_rank()
|
||||
|
||||
# TODO: Komplett Überabreiten!
|
||||
def append_tournament_bonuspoints(self, hanchan):
|
||||
"""
|
||||
Prüft ob es die letzte Hanchan in einem Turnier war. Wenn ja werden
|
||||
@@ -408,29 +427,29 @@ class KyuDanRanking(models.Model):
|
||||
user=self.user, event=hanchan.event
|
||||
).order_by('-start')
|
||||
last_hanchan_this_event = hanchans_this_event[0]
|
||||
if hanchan != last_hanchan_this_event:
|
||||
# Das braucht nur am Ende eines Turnieres gemacht werden.
|
||||
return False
|
||||
else:
|
||||
event_ranking = EventRanking.objects.get(
|
||||
user=self.user,
|
||||
event=hanchan.event
|
||||
)
|
||||
if event_ranking.placement == 1:
|
||||
bonus_points += 4
|
||||
hanchan.player_comment += u'+4 Punkte Turnier gewonnen. '
|
||||
if event_ranking.avg_placement == 1:
|
||||
bonus_points += 8
|
||||
hanchan.player_comment += u'+8 Pkt: alle Spiele des Turnieres gewonnen. '
|
||||
# Das braucht nur am Ende eines Turnieres gemacht werden.
|
||||
if hanchan != last_hanchan_this_event: return False
|
||||
event_ranking = EventRanking.objects.get(
|
||||
user=self.user,
|
||||
event=hanchan.event
|
||||
)
|
||||
if event_ranking.placement == 1:
|
||||
bonus_points += settings.TOURNAMENT_WIN_BONUSPOINTS
|
||||
hanchan.player_comment += u'+{0:d} Punkte Turnier gewonnen. '.format(
|
||||
settings.TOURNAMENT_WIN_BONUSPOINTS)
|
||||
if event_ranking.avg_placement == 1:
|
||||
bonus_points += settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS
|
||||
hanchan.player_comment += u'+{0:d} Pkt: alle Spiele des Turnieres gewonnen. '.format(
|
||||
settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS)
|
||||
|
||||
if bonus_points and self.dan:
|
||||
hanchan.dan_points += bonus_points
|
||||
self.dan_points += bonus_points
|
||||
elif bonus_points:
|
||||
hanchan.kyu_points += bonus_points
|
||||
self.kyu_points += bonus_points
|
||||
hanchan.bonus_points += bonus_points
|
||||
return True
|
||||
if bonus_points and self.dan:
|
||||
hanchan.dan_points += bonus_points
|
||||
self.dan_points += bonus_points
|
||||
elif bonus_points:
|
||||
hanchan.kyu_points += bonus_points
|
||||
self.kyu_points += bonus_points
|
||||
hanchan.bonus_points += bonus_points
|
||||
return True
|
||||
|
||||
def get_absolute_url(self):
|
||||
if self.dan or self.dan_points > 0:
|
||||
@@ -451,20 +470,19 @@ class KyuDanRanking(models.Model):
|
||||
force_recalc = True
|
||||
if force_recalc:
|
||||
# Setze alles auf die legacy Werte und berechne alles von neuem.
|
||||
self.dan = None
|
||||
self.dan = self.legacy_dan
|
||||
self.dan_points = self.legacy_dan_points or 0
|
||||
self.kyu = None
|
||||
self.max_dan_points = self.dan_points
|
||||
self.kyu = self.legacy_kyu
|
||||
self.kyu_points = self.legacy_kyu_points or 0
|
||||
self.hanchan_count = self.legacy_hanchan_count or 0
|
||||
self.good_hanchans = 0
|
||||
self.won_hanchans = 0
|
||||
self.update_rank()
|
||||
self.good_hanchans = self.legacy_good_hanchans or 0
|
||||
self.won_hanchans = self.legacy_won_hanchans or 0
|
||||
self.last_hanchan_date = None
|
||||
if self.legacy_date:
|
||||
since = timezone.make_aware(
|
||||
datetime.combine(self.legacy_date, time(0, 0, 0)))
|
||||
else:
|
||||
since = None
|
||||
self.update_rank()
|
||||
since = timezone.make_aware(datetime.combine(
|
||||
self.legacy_date,
|
||||
time(23, 59, 59))) if self.legacy_date else None
|
||||
elif self.last_hanchan_date:
|
||||
since = self.last_hanchan_date
|
||||
elif self.legacy_date:
|
||||
@@ -479,42 +497,29 @@ class KyuDanRanking(models.Model):
|
||||
valid_hanchans = valid_hanchans.filter(start__gt=since)
|
||||
if until:
|
||||
valid_hanchans = valid_hanchans.filter(start__lte=until)
|
||||
|
||||
self.hanchan_count += valid_hanchans.count()
|
||||
for hanchan in valid_hanchans:
|
||||
self.hanchan_count += 1
|
||||
hanchan.get_playerdata(self.user)
|
||||
if since and hanchan.start < since:
|
||||
print(hanchan, "<", since, "no recalc")
|
||||
LOGGER.debug(hanchan, "<", since, "no recalc")
|
||||
self.dan_points += hanchan.dan_points or 0
|
||||
self.kyu_points += hanchan.kyu_points or 0
|
||||
self.update_rank()
|
||||
else:
|
||||
hanchan.bonus_points = 0
|
||||
hanchan.player_comment = u""
|
||||
hanchan.player_comment = ""
|
||||
self.update_hanchan_points(hanchan)
|
||||
if hanchan.event.mahjong_tournament:
|
||||
self.append_tournament_bonuspoints(hanchan)
|
||||
self.update_rank()
|
||||
self.append_3_in_a_row_bonuspoints(hanchan)
|
||||
self.update_rank()
|
||||
hanchan.update_playerdata(self.user)
|
||||
hanchan.save(recalculate=False)
|
||||
self.won_hanchans += 1 if hanchan.placement == 1 else 0
|
||||
self.good_hanchans += 1 if hanchan.placement == 2 else 0
|
||||
self.last_hanchan_date = hanchan.start
|
||||
LOGGER.debug(
|
||||
'id: %(id)d, start: %(start)s, placement: %(placement)d, '
|
||||
'score: %(score)d, kyu points: %(kyu_points)d, dan points: '
|
||||
'%(dan_points)d, bonus points: %(bonus_points)d',
|
||||
{'id': hanchan.pk, 'start': hanchan.start,
|
||||
'placement': hanchan.placement, 'score': hanchan.game_score,
|
||||
'kyu_points': hanchan.kyu_points or 0,
|
||||
'dan_points': hanchan.dan_points or 0,
|
||||
'bonus_points': hanchan.bonus_points or 0}
|
||||
)
|
||||
self.won_hanchans += 1 if hanchan.placement == 1 else 0
|
||||
self.good_hanchans += 1 if hanchan.placement == 2 else 0
|
||||
self.last_hanchan_date = hanchan.start
|
||||
self.save(force_update=True)
|
||||
|
||||
|
||||
def update_hanchan_points(self, hanchan):
|
||||
"""
|
||||
Berechne die Kyu bzw. Dan Punkte für eine Hanchan neu.
|
||||
@@ -523,7 +528,7 @@ class KyuDanRanking(models.Model):
|
||||
"""
|
||||
hanchan.kyu_points = None
|
||||
hanchan.dan_points = None
|
||||
if hanchan.event.mahjong_tournament:
|
||||
if hanchan.event.mahjong_tournament and settings.TOURNAMENT_POINT_SYSTEM:
|
||||
"""Für Turniere gelten andere Regeln zur Punktevergabe:
|
||||
1. Platz 4 Punkte
|
||||
2. Platz 3 Punkte
|
||||
@@ -547,6 +552,7 @@ class KyuDanRanking(models.Model):
|
||||
hanchan.dan_points = -1
|
||||
elif hanchan.placement == 4:
|
||||
hanchan.dan_points = -2
|
||||
# otherwise player must be in the kyu ranking
|
||||
elif hanchan.game_score >= 60000:
|
||||
hanchan.kyu_points = 3
|
||||
elif hanchan.game_score >= 30000:
|
||||
@@ -560,46 +566,44 @@ class KyuDanRanking(models.Model):
|
||||
if self.dan:
|
||||
# Only substract so much points that player has 0 Points:
|
||||
if self.dan_points + hanchan.dan_points < 0:
|
||||
hanchan.player_comment = 'Spieler unterschreitet 0 Punkte.' \
|
||||
'(Original {} Punkte)'.format(hanchan.dan_points)
|
||||
hanchan.dan_points -= (self.dan_points + hanchan.dan_points)
|
||||
self.dan_points += hanchan.dan_points
|
||||
else:
|
||||
# Only substract so much points that player has 0 Points:
|
||||
if self.kyu_points + hanchan.kyu_points < 0:
|
||||
hanchan.player_comment = 'Spieler unterschreitet 0 Punkte.' \
|
||||
'(Original {} Punkte)'.format(hanchan.kyu_points)
|
||||
hanchan.kyu_points -= (self.kyu_points + hanchan.kyu_points)
|
||||
self.kyu_points += hanchan.kyu_points
|
||||
|
||||
|
||||
# TODO: Merkwürdige Methode die zwar funktioniert aber nicht sehr
|
||||
# aussagekräfig ist. Überarbeiten?
|
||||
def update_rank(self):
|
||||
if self.dan and self.dan_points < 0:
|
||||
self.dan_points = 0
|
||||
self.dan = 1
|
||||
elif self.dan or self.dan_points > 0:
|
||||
old_dan = self.dan
|
||||
for min_points, dan_rank in settings.DAN_RANKS:
|
||||
if self.dan_points > min_points:
|
||||
self.dan = dan_rank
|
||||
break
|
||||
if old_dan is None or self.dan > old_dan:
|
||||
self.wins_in_a_row = 0
|
||||
elif self.kyu_points < 1:
|
||||
self.kyu_points = 0
|
||||
self.kyu = 10
|
||||
# Update Dan ranking:
|
||||
if self.dan or self.dan_points > 0:
|
||||
if settings.DAN_ALLOW_DROP_DOWN:
|
||||
self.dan = max((dan for min_points, dan in settings.DAN_RANKS
|
||||
if self.dan_points > min_points))
|
||||
else:
|
||||
self.max_dan_points = max(self.max_dan_points, self.dan_points)
|
||||
self.dan = max((dan for min_points, dan in settings.DAN_RANKS
|
||||
if self.max_dan_points > min_points))
|
||||
|
||||
# jump from Kyu to Dan
|
||||
elif self.kyu_points > 50:
|
||||
self.dan = 1
|
||||
self.kyu = 0
|
||||
self.dan_points = 0
|
||||
self.kyu = None
|
||||
self.kyu_points = 0
|
||||
self.wins_in_a_row = 0
|
||||
# update Kyu ranking_
|
||||
else:
|
||||
for min_points, kyu_rank in settings.KYU_RANKS:
|
||||
if self.kyu_points > min_points:
|
||||
self.kyu = kyu_rank
|
||||
break
|
||||
self.kyu = min((kyu for min_points, kyu in settings.KYU_RANKS
|
||||
if self.kyu_points > min_points))
|
||||
|
||||
|
||||
class SeasonRanking(models.Model):
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||
season = models.PositiveSmallIntegerField(_('Season'))
|
||||
placement = models.PositiveIntegerField(blank=True, null=True)
|
||||
avg_placement = models.FloatField(blank=True, null=True)
|
||||
|
||||
120
src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html
Executable file
120
src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html
Executable file
@@ -0,0 +1,120 @@
|
||||
{% extends "events/event_detail.html" %}{% load i18n humanize thumbnail %}
|
||||
|
||||
{% block title %}Hanchans: {{ event.name }}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.formset.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
<h2 class="grid_12">{% trans 'Edit Hanchans' %}</h2>
|
||||
|
||||
<form method="post" action="" id="eventhanchan_form">
|
||||
{% csrf_token %}
|
||||
{{ formset.management_form }}
|
||||
|
||||
{% for form in formset %}
|
||||
<fieldset class="hanchan">
|
||||
{% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %}
|
||||
<p>
|
||||
<label for="id_{{ form.start.html_name }}_0" class="field_name {{ form.start.css_classes }}">{{ form.start.label }}:</label>
|
||||
{{ form.start }}
|
||||
{{ form.start.errors }}
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{form.player1.label}}</th>
|
||||
<th>{{form.player2.label}}</th>
|
||||
<th>{{form.player3.label}}</th>
|
||||
<th>{{form.player4.label}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>{{ form.player1 }}</td>
|
||||
<td>{{ form.player2 }}</td>
|
||||
<td>{{ form.player3 }}</td>
|
||||
<td>{{ form.player4 }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ form.player1_input_score }}</td>
|
||||
<td>{{ form.player2_input_score }}</td>
|
||||
<td>{{ form.player3_input_score }}</td>
|
||||
<td>{{ form.player4_input_score }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<label class="field_name {{ form.comment.css_classes }}">{% trans 'Total' %}:</label>
|
||||
<input type="number" value="0" name="total_score" disabled>
|
||||
<label>{% trans 'Difference' %}:</label>
|
||||
<span class="difference"></span>
|
||||
</p>
|
||||
|
||||
|
||||
<p><label for="id_{{ form.comment.html_name }}" class="field_name {{ form.comment.css_classes }}">{{ form.comment.label }}:</label>
|
||||
{{ form.comment }}
|
||||
{{ form.comment.errors }}
|
||||
</p>
|
||||
{% if form.instance.pk %}
|
||||
<p>
|
||||
<label for="id_{{ form.DELETE.html_name }}" class="field_name {{ form.DELETE.css_classes }}">{{ form.DELETE.label }}:</label>
|
||||
{{ form.DELETE }} {{form.DELETE.help_text}}
|
||||
{{ form.DELETE.errors }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if form.non_field_errors %}
|
||||
<p> {{ form.non_field_errors }}</p>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
{% endfor %}
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
function autofill(row) {
|
||||
row.find("input[id$='start_0']").val('{{ event.start|date:"SHORT_DATE_FORMAT"}}');
|
||||
row.find("input[id$='start_1']").val('{{ event.start|time:'TIME_FORMAT'}}');
|
||||
}
|
||||
|
||||
function recalculate_score(element) {
|
||||
var difference = 100000
|
||||
var total = 0;
|
||||
score_fields = $(element).closest('fieldset').find('input[name$="input_score"]')
|
||||
total_field = $(element).closest('fieldset').find('input[name$="total_score"]')
|
||||
difference_field = $(element).closest('fieldset').find('span[class="difference"]')
|
||||
score_fields.each(function() {total += Number($(this).val());});
|
||||
total_field.val(total)
|
||||
|
||||
difference = 100000 - total
|
||||
if (difference > 0) {
|
||||
differnence_text = difference + ' offen'
|
||||
} else if (difference < 0) {
|
||||
differnence_text = (0 - difference) + ' zu viel'
|
||||
} else {
|
||||
differnence_text = 'Ok'
|
||||
}
|
||||
difference_field.text(differnence_text)
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$('.hanchan').formset({
|
||||
prefix: '{{ formset.prefix }}',
|
||||
added: autofill,
|
||||
addText: '<span class="fa fa-plus-circle"></span> {% trans 'Add Hanchan' %}',
|
||||
addCssClass: 'button',
|
||||
deleteText:'<span class="fa fa-trash"></span> {% trans 'Delete Hanchan' %}',
|
||||
deleteCssClass: 'button'
|
||||
});
|
||||
})
|
||||
$('input[name$="_input_score"]').change(function() {recalculate_score(this);});
|
||||
$('input[name$="_input_score"]').keyup(function() {recalculate_score(this);});
|
||||
$('input[name$="total_score"]').each(function() {recalculate_score(this);});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block comments %}{% endblock %}
|
||||
|
||||
{% block buttonbar %}
|
||||
<a class="button" href="{% url 'event-hanchan-list' event.pk %}"><span class="fa fa-undo"></span> {% trans 'back' %}</a>
|
||||
<button type="submit" form="eventhanchan_form"><span class="fa fa-hdd-o"></span> {% trans 'save' %}</button>
|
||||
{% endblock %}
|
||||
@@ -52,6 +52,7 @@
|
||||
{% block buttonbar %}
|
||||
{% if perms.mahjong_ranking.add_hanchan %}
|
||||
<a class="button" href="{{event.get_edit_url}}"><span class="fa fa-pencil"></span> {% trans 'Edit Event' %}</a>
|
||||
<a class="button" href="{% url 'event-hanchan-form' event.id %}"><span class="fa fa-pencil"></span> {% trans 'Edit Hanchans' %}</a>
|
||||
<a class="button" href="{% url 'add-hanchan-form' event.id %}"><span class="fa fa-plus-circle"></span> {% trans 'Add Hanchan' %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -50,4 +50,12 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% 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 %}
|
||||
|
||||
{% block buttonbar %}
|
||||
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -35,8 +35,12 @@
|
||||
{% if perms.mahjong_ranking.change_hanchan %}
|
||||
<a href="{% url 'edit-hanchan' hanchan.pk %}"><span class="fa fa-pencil" title="{% trans 'Edit Hanchan' %}"></span></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block buttonbar %}
|
||||
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<th rowspan="2">{% trans 'Placement' %}</th>
|
||||
<th colspan="4">{% trans 'Players' %}</th>
|
||||
<th rowspan="2">{% trans 'Kyu Points' %}</th>
|
||||
<th rowspan="2"></th>
|
||||
<th rowspan="2"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>1.</th>
|
||||
@@ -45,6 +45,10 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</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>
|
||||
</td></tr></tfoot>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
{% block buttonbar %}
|
||||
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,14 +4,15 @@ from django.conf.urls import url
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [ # Ignore PyLintBear (C0103)
|
||||
url(r'^$',
|
||||
RedirectView.as_view(url='/ranking/mahjong-ladder/', permanent=True)),
|
||||
url(r'^event/(?P<event>[\d]+)/mahjong/$',
|
||||
views.EventHanchanList.as_view(), name="event-hanchan-list"),
|
||||
url(r'^event/(?P<event>[\d]+)/add-hanchan/$',
|
||||
views.HanchanForm.as_view(), name="add-hanchan-form"),
|
||||
url(r'^event/(?P<event>[\d]+)/edit/$',
|
||||
views.EventHanchanForm.as_view(), name="event-hanchan-form"),
|
||||
url(r'^event/(?P<event>[\d]+)/mahjong/$',
|
||||
views.EventHanchanList.as_view(), name="event-hanchan-list"),
|
||||
url(r'^event/(?P<event>[\d]+)/mahjong-ranking/$',
|
||||
views.EventRankingList.as_view(), name="event-ranking"),
|
||||
url(r'^hanchan/(?P<hanchan>[\d]+)/edit/$',
|
||||
@@ -32,6 +33,7 @@ urlpatterns = [ # Ignore PyLintBear (C0103)
|
||||
views.PlayerLadderScore.as_view(), name="player-ladder-score"),
|
||||
url(r'^mahjong/$', views.KyuDanRankingList.as_view(),
|
||||
name="kyudanranking-list"),
|
||||
url(r'^mahjong/(?P<order_by>[\+\-\w]+)/$',
|
||||
url(r'^mahjong/(?P<order_by>[\+\-][a-z_]+)/$',
|
||||
views.KyuDanRankingList.as_view(), name="kyudanranking-list"),
|
||||
]
|
||||
|
||||
|
||||
@@ -7,14 +7,16 @@ from django.contrib import auth
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, \
|
||||
PermissionRequiredMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
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
|
||||
|
||||
DEFAULT_KYU_DAN_ORDER = '-score'
|
||||
KYU_DAN_ORDER = { # map sort URL args to Django ORM order_by args
|
||||
'+full_name': ('user__last_name', 'user__first_name'),
|
||||
'-full_name': ('-user__last_name', '-user__first_name'),
|
||||
@@ -106,6 +108,47 @@ class HanchanForm(SuccessMessageMixin, EventDetailMixin,
|
||||
'one.') % self.object
|
||||
|
||||
|
||||
class EventHanchanForm(EventDetailMixin, PermissionRequiredMixin,
|
||||
generic.TemplateView):
|
||||
"""Display a Formset to add and Edit Hanchans of the specific Event."""
|
||||
permission_required = 'mahjong_ranking.add_hanchan'
|
||||
template_name = 'mahjong_ranking/eventhanchan_form.html'
|
||||
model=models.Hanchan
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
self.event = models.Event.objects.get(pk=self.kwargs['event'])
|
||||
context = super(EventHanchanForm, self).get_context_data()
|
||||
context['formset'] = self.formset
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.get_queryset()
|
||||
self.formset = forms.HanchanFormset(
|
||||
instance=self.event,
|
||||
initial=[{'start': self.event.start}]
|
||||
)
|
||||
context = self.get_context_data(**kwargs)
|
||||
return self.render_to_response(context)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
print("ICH WURDE GEPOSTET!!!!")
|
||||
self.get_queryset()
|
||||
self.formset = forms.HanchanFormset(
|
||||
self.request.POST,
|
||||
self.request.FILES,
|
||||
instance=self.event,
|
||||
initial=[{'start': self.event.start}]
|
||||
)
|
||||
if self.formset.is_valid():
|
||||
self.formset.save()
|
||||
return django.http.HttpResponseRedirect(
|
||||
reverse('event-hanchan-form', kwargs={'event': self.event.pk})
|
||||
)
|
||||
context = self.get_context_data(**kwargs)
|
||||
return self.render_to_response(context)
|
||||
|
||||
|
||||
|
||||
class EventHanchanList(EventDetailMixin, generic.ListView):
|
||||
"List all hanchans played on a given event."
|
||||
model = models.Hanchan
|
||||
@@ -117,23 +160,19 @@ class EventRankingList(EventDetailMixin, generic.ListView):
|
||||
model = models.EventRanking
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class KyuDanRankingList(MahjongMixin, generic.ListView):
|
||||
"""List all Players with an Kyu or Dan score. """
|
||||
default_order = '-score'
|
||||
order_by = ''
|
||||
order_by = None
|
||||
paginate_by = 25
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""Set the order_by settings, revert to default_order if necessary."""
|
||||
self.order_by = KYU_DAN_ORDER[
|
||||
kwargs.get('order_by', self.default_order)
|
||||
]
|
||||
if kwargs.get('order_by') in KYU_DAN_ORDER.keys():
|
||||
self.order_by = KYU_DAN_ORDER[kwargs.get('order_by')]
|
||||
else:
|
||||
self.order_by = KYU_DAN_ORDER[DEFAULT_KYU_DAN_ORDER]
|
||||
return super(KyuDanRankingList, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = models.KyuDanRanking.objects.filter(
|
||||
hanchan_count__gt=0).order_by(*self.order_by)
|
||||
@@ -160,7 +199,6 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
user_model = auth.get_user_model()
|
||||
username = kwargs.get('username')
|
||||
try:
|
||||
self.user = user_model.objects.get(
|
||||
username=self.kwargs.get('username'))
|
||||
@@ -168,6 +206,9 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
|
||||
raise django.http.Http404(
|
||||
_("No user found matching the name {}").format(
|
||||
self.kwargs.get('username')))
|
||||
print(request.GET)
|
||||
if request.GET.get('download') == 'xlsx':
|
||||
return self.get_xlsx(request, *args, **kwargs)
|
||||
return super(PlayerScore, self).get(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@@ -186,20 +227,76 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
|
||||
context['ladder_ranking'] = models.SeasonRanking(user=self.user)
|
||||
return context
|
||||
|
||||
def get_xlsx(self, request, *args, **kwargs):
|
||||
self.object_list = self.get_queryset()
|
||||
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 = 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'
|
||||
|
||||
def get_queryset(self):
|
||||
kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user)
|
||||
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': 'player_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):
|
||||
template_name = 'mahjong_ranking/player_invalid_score.html'
|
||||
|
||||
def get_queryset(self):
|
||||
self.xlsx_filename = "{username}_invalid_score.xlsx".format(
|
||||
username=self.user.username)
|
||||
return models.Hanchan.objects.unconfirmed(user=self.user)
|
||||
|
||||
|
||||
@@ -207,7 +304,47 @@ class PlayerKyuScore(PlayerScore):
|
||||
template_name = 'mahjong_ranking/player_kyu_score.html'
|
||||
|
||||
def get_queryset(self):
|
||||
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):
|
||||
@@ -229,3 +366,39 @@ class PlayerLadderScore(PlayerScore):
|
||||
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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user