Another Step in the Quest to clean up the code base.
This commit is contained in:
@@ -1,18 +1,13 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
""" Adds management of the mahong ranking system to the admin interface. """
|
||||
|
||||
"""
|
||||
Created on 19.09.2011
|
||||
|
||||
@author: christian
|
||||
"""
|
||||
# import stuff we need from django
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from . import models, set_dirty
|
||||
|
||||
|
||||
def recalculate(modeladmin, request, queryset):
|
||||
def recalculate(modeladmin, request, queryset): # Ignore PyLintBear (W0613)
|
||||
""" An admin action to force recalculation of the selected items. """
|
||||
if isinstance(modeladmin, HanchanAdmin):
|
||||
for hanchan in queryset:
|
||||
hanchan.save()
|
||||
@@ -31,7 +26,8 @@ def recalculate(modeladmin, request, queryset):
|
||||
recalculate.short_description = _("Recalculate")
|
||||
|
||||
|
||||
def confirm(modeladmin, request, queryset):
|
||||
def confirm(modeladmin, request, queryset): # Ignore PyLintBear (W0613)
|
||||
"""An admin action to quickly set selected hanchans to confirmed. """
|
||||
for hanchan in queryset:
|
||||
hanchan.confirmed = True
|
||||
hanchan.save()
|
||||
@@ -40,7 +36,8 @@ def confirm(modeladmin, request, queryset):
|
||||
confirm.short_description = _("Confirm")
|
||||
|
||||
|
||||
def unconfirm(modeladmin, request, queryset):
|
||||
def unconfirm(modeladmin, request, queryset): # Ignore PyLintBear (W0613)
|
||||
"""An admin action to quickly set selected hanchans to unconfirmed. """
|
||||
for hanchan in queryset:
|
||||
hanchan.confirmed = False
|
||||
hanchan.save()
|
||||
@@ -50,6 +47,7 @@ unconfirm.short_description = _('Set unconfirmed')
|
||||
|
||||
|
||||
class EventRankingAdmin(admin.ModelAdmin):
|
||||
""" Lists the Event Rankings and allows to recalculate them. """
|
||||
list_filter = ['event']
|
||||
list_display = ('placement', 'user', 'event', 'avg_placement', 'avg_score',
|
||||
'hanchan_count', 'good_hanchans', 'won_hanchans')
|
||||
@@ -58,6 +56,7 @@ class EventRankingAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
class HanchanAdmin(admin.ModelAdmin):
|
||||
""" To administrate the stored Hanchans. """
|
||||
actions = [recalculate, confirm, unconfirm]
|
||||
date_hierarchy = 'start'
|
||||
list_filter = ('season', 'event', 'confirmed')
|
||||
@@ -82,6 +81,7 @@ class HanchanAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
class KyuDanAdmin(admin.ModelAdmin):
|
||||
""" Lists the Kyu/dan Rankings and allows to recalculate them. """
|
||||
actions = [recalculate]
|
||||
list_display = ('user', 'kyu', 'kyu_points', 'dan', 'dan_points',
|
||||
'hanchan_count')
|
||||
@@ -92,13 +92,15 @@ class KyuDanAdmin(admin.ModelAdmin):
|
||||
'classes': ('grp-collapse grp-open',),
|
||||
}),
|
||||
('Frühere Aufzeichnungen', {
|
||||
'fields': ('legacy_date', 'legacy_hanchan_count', ('legacy_kyu_points', 'legacy_dan_points')),
|
||||
'fields': ('legacy_date', 'legacy_hanchan_count',
|
||||
('legacy_kyu_points', 'legacy_dan_points')),
|
||||
'classes': ('grp-collapse grp-closed',),
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
class SeasonRankingAdmin(admin.ModelAdmin):
|
||||
""" Lists the Season Rankings and allows to recalculate them. """
|
||||
actions = [recalculate]
|
||||
list_display = ('placement', 'season', 'user', 'avg_placement',
|
||||
'avg_score', 'hanchan_count', 'good_hanchans',
|
||||
@@ -108,6 +110,7 @@ class SeasonRankingAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
class LadderSeasonAdmin(admin.ModelAdmin):
|
||||
""" Seasons will be generated automaticly, but you can examine them here."""
|
||||
actions = [recalculate]
|
||||
list_display = ('name', 'start', 'end')
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@ USER_MODEL = get_user_model()
|
||||
|
||||
|
||||
class HanchanForm(forms.ModelForm):
|
||||
""" Form to add/edit mahjong hanchans, grouped with formsets. """
|
||||
error_css_class = 'error'
|
||||
required_css_class = 'required'
|
||||
start = forms.SplitDateTimeField(label=_('start'), required=True)
|
||||
|
||||
class Meta(object):
|
||||
""" Medadata for better usability of the HanchanForm """
|
||||
model = models.Hanchan
|
||||
fields = ('start',
|
||||
'player1', 'player1_input_score',
|
||||
@@ -30,12 +32,14 @@ class HanchanForm(forms.ModelForm):
|
||||
widgets = {
|
||||
'event': forms.HiddenInput(),
|
||||
'comment': forms.widgets.Textarea(attrs={'rows': 4, 'cols': 40})
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
""" Overwrites some standard widgets for better usability. """
|
||||
super(HanchanForm, self).__init__(*args, **kwargs)
|
||||
player_queryset = USER_MODEL.objects.filter(is_active=True,
|
||||
membership=True)
|
||||
player_queryset = USER_MODEL.objects.filter(
|
||||
is_active=True,
|
||||
membership=True)
|
||||
for i in range(1, 4):
|
||||
player = 'player%d' % i
|
||||
player_input_score = 'player%d_input_score' % i
|
||||
@@ -45,18 +49,12 @@ class HanchanForm(forms.ModelForm):
|
||||
|
||||
|
||||
class HanchanAdminForm(HanchanForm):
|
||||
""" Extends the HanchanForm for users with admin privileges.
|
||||
|
||||
They are allowed to confirm/unconfirm Hanchans, this could be userful if
|
||||
one games smells fishy and needs the opinion of an referee."""
|
||||
|
||||
class Meta(object):
|
||||
""" Extend the formfields to add the confirmed checkbox. """
|
||||
model = models.Hanchan
|
||||
fields = HanchanForm.Meta.fields + ('confirmed',)
|
||||
|
||||
|
||||
class SeasonSelectForm(forms.Form):
|
||||
season = forms.ChoiceField(label='', choices=('a', 'b', 'c'))
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super(SeasonSelectForm, self).__init__(args, kwargs)
|
||||
season_list = models.LadderRanking.objects.filter(user=user)
|
||||
season_list = season_list.select_related('user')
|
||||
season_list = season_list.values_list('season__id', 'season__name')
|
||||
self.fields['season'] = forms.ChoiceField(choices=season_list)
|
||||
|
||||
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: kasu.mahjong_ranking\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-05-10 23:16+0200\n"
|
||||
"POT-Creation-Date: 2017-06-19 22:46+0200\n"
|
||||
"PO-Revision-Date: 2016-09-28 00:24+0200\n"
|
||||
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
|
||||
"Language-Team: Kasu <verein@kasu.at>\n"
|
||||
@@ -19,7 +19,7 @@ msgstr ""
|
||||
"X-Generator: Poedit 1.8.9\n"
|
||||
"X-Translated-Using: django-rosetta 0.7.6\n"
|
||||
|
||||
#: mahjong_ranking/admin.py:29
|
||||
#: mahjong_ranking/admin.py:26
|
||||
msgid "Recalculate"
|
||||
msgstr "Neuberechnen"
|
||||
|
||||
@@ -27,15 +27,15 @@ msgstr "Neuberechnen"
|
||||
msgid "Confirm"
|
||||
msgstr "Bestätigen"
|
||||
|
||||
#: mahjong_ranking/admin.py:43
|
||||
#: mahjong_ranking/admin.py:46
|
||||
msgid "Set unconfirmed"
|
||||
msgstr "Als unbestätigt markieren"
|
||||
|
||||
#: mahjong_ranking/forms.py:22
|
||||
#: mahjong_ranking/forms.py:21
|
||||
msgid "start"
|
||||
msgstr "Beginn"
|
||||
|
||||
#: mahjong_ranking/models.py:90
|
||||
#: mahjong_ranking/models.py:89
|
||||
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:14
|
||||
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:13
|
||||
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:15
|
||||
@@ -44,18 +44,18 @@ msgstr "Beginn"
|
||||
msgid "Start"
|
||||
msgstr "Beginn"
|
||||
|
||||
#: mahjong_ranking/models.py:91
|
||||
#: mahjong_ranking/models.py:90
|
||||
msgid "This is crucial to get the right Hanchans that scores"
|
||||
msgstr "Wichtig damit die richtigen Hanchans in die Wertung kommen."
|
||||
|
||||
#: mahjong_ranking/models.py:98
|
||||
#: mahjong_ranking/models.py:97
|
||||
msgid "Player 1"
|
||||
msgstr "Spieler 1"
|
||||
|
||||
#: mahjong_ranking/models.py:99 mahjong_ranking/models.py:101
|
||||
#: mahjong_ranking/models.py:118 mahjong_ranking/models.py:120
|
||||
#: mahjong_ranking/models.py:137 mahjong_ranking/models.py:139
|
||||
#: mahjong_ranking/models.py:156 mahjong_ranking/models.py:158
|
||||
#: mahjong_ranking/models.py:98 mahjong_ranking/models.py:100
|
||||
#: mahjong_ranking/models.py:117 mahjong_ranking/models.py:119
|
||||
#: mahjong_ranking/models.py:136 mahjong_ranking/models.py:138
|
||||
#: mahjong_ranking/models.py:155 mahjong_ranking/models.py:157
|
||||
#: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:19
|
||||
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:21
|
||||
#: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:16
|
||||
@@ -65,80 +65,80 @@ msgstr "Spieler 1"
|
||||
msgid "Score"
|
||||
msgstr "Punkte"
|
||||
|
||||
#: mahjong_ranking/models.py:111 mahjong_ranking/models.py:130
|
||||
#: mahjong_ranking/models.py:149 mahjong_ranking/models.py:168
|
||||
#: mahjong_ranking/models.py:170
|
||||
#: mahjong_ranking/models.py:110 mahjong_ranking/models.py:129
|
||||
#: mahjong_ranking/models.py:148 mahjong_ranking/models.py:167
|
||||
#: mahjong_ranking/models.py:169
|
||||
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:20
|
||||
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:18
|
||||
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:17
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
#: mahjong_ranking/models.py:117
|
||||
#: mahjong_ranking/models.py:116
|
||||
msgid "Player 2"
|
||||
msgstr "Spieler 2"
|
||||
|
||||
#: mahjong_ranking/models.py:136
|
||||
#: mahjong_ranking/models.py:135
|
||||
msgid "Player 3"
|
||||
msgstr "Spieler 3"
|
||||
|
||||
#: mahjong_ranking/models.py:155
|
||||
#: mahjong_ranking/models.py:154
|
||||
msgid "Player 4"
|
||||
msgstr "Spieler 4"
|
||||
|
||||
#: mahjong_ranking/models.py:171
|
||||
#: mahjong_ranking/models.py:170
|
||||
msgid "Has been Confirmed"
|
||||
msgstr "Wurde bestätigt"
|
||||
|
||||
#: mahjong_ranking/models.py:173
|
||||
#: mahjong_ranking/models.py:172
|
||||
msgid "Only valid and confirmed Hanchans will be counted in the rating."
|
||||
msgstr "Nur gültige und bestätigte Hanchans kommen in die Wertung."
|
||||
|
||||
#: mahjong_ranking/models.py:178 mahjong_ranking/models.py:565
|
||||
#: mahjong_ranking/models.py:177 mahjong_ranking/models.py:576
|
||||
#: mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:29
|
||||
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:63
|
||||
msgid "Season"
|
||||
msgstr "Saison"
|
||||
|
||||
#: mahjong_ranking/models.py:183
|
||||
#: mahjong_ranking/models.py:182
|
||||
msgid "Hanchan"
|
||||
msgstr "Hanchan"
|
||||
|
||||
#: mahjong_ranking/models.py:184
|
||||
#: mahjong_ranking/models.py:183
|
||||
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:17
|
||||
msgid "Hanchans"
|
||||
msgstr "Hanchans"
|
||||
|
||||
#: mahjong_ranking/models.py:187
|
||||
#: mahjong_ranking/models.py:186
|
||||
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}"
|
||||
|
||||
#: mahjong_ranking/models.py:214
|
||||
#: mahjong_ranking/models.py:213
|
||||
#, python-format
|
||||
msgid "%s can't attend the same game multiple times"
|
||||
msgstr "%s kann an einem Spiel nicht mehrfach teilnehmen."
|
||||
|
||||
#: mahjong_ranking/models.py:222
|
||||
#: mahjong_ranking/models.py:221
|
||||
msgid "Games in the future may not be added, Dr. Brown"
|
||||
msgstr "Spiele aus der Zukunft dürfen noch nicht erfasst werden. Dr. Brown."
|
||||
|
||||
#: mahjong_ranking/models.py:224
|
||||
#: mahjong_ranking/models.py:223
|
||||
msgid "Only games during the event are allowed"
|
||||
msgstr "Nur Spiele während der Veranstaltung zählen."
|
||||
|
||||
#: mahjong_ranking/models.py:227
|
||||
#: mahjong_ranking/models.py:226
|
||||
msgid "Gamescore is lower then 100.000 Pt."
|
||||
msgstr "Spielstand ist weniger als 100.000 Punkte"
|
||||
|
||||
#: mahjong_ranking/models.py:229
|
||||
#: mahjong_ranking/models.py:228
|
||||
msgid "Gamescore is over 100.000 Pt."
|
||||
msgstr "Spielstand ist über 100.000 Punkte."
|
||||
|
||||
#: mahjong_ranking/models.py:353
|
||||
#: mahjong_ranking/models.py:352
|
||||
msgid "Kyū/Dan Ranking"
|
||||
msgstr "Kyū/Dan Wertung"
|
||||
|
||||
#: mahjong_ranking/models.py:354
|
||||
#: mahjong_ranking/models.py:353
|
||||
msgid "Kyū/Dan Rankings"
|
||||
msgstr "Kyū/Dan Wertungen"
|
||||
|
||||
@@ -362,7 +362,7 @@ msgstr "Ladder Wertung für"
|
||||
msgid "Hanchans that apply to the Ladder Score"
|
||||
msgstr "Hanchans welche in der Ladder zählen"
|
||||
|
||||
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:69
|
||||
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:71
|
||||
msgid "Go"
|
||||
msgstr "Los"
|
||||
|
||||
@@ -374,17 +374,17 @@ msgstr "Ende"
|
||||
msgid "Participants"
|
||||
msgstr "Teilnehmer"
|
||||
|
||||
#: mahjong_ranking/views.py:97
|
||||
#: mahjong_ranking/views.py:98
|
||||
#, python-format
|
||||
msgid "%s has been updated successfully."
|
||||
msgstr "%s wurde erfolgreich aktualisiert."
|
||||
|
||||
#: mahjong_ranking/views.py:100
|
||||
#: mahjong_ranking/views.py:101
|
||||
#, 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."
|
||||
|
||||
#: mahjong_ranking/views.py:118 mahjong_ranking/views.py:134
|
||||
#: mahjong_ranking/views.py:119 mahjong_ranking/views.py:135
|
||||
msgid "Event does not exist"
|
||||
msgstr "Veranstaltung existiert nicht"
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Export Mahjong Rankings...
|
||||
"""
|
||||
"""Export Mahjong Rankings as excel files."""
|
||||
|
||||
from operator import itemgetter
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from mahjong_ranking.models import SeasonRanking
|
||||
from openpyxl import Workbook
|
||||
|
||||
from mahjong_ranking.models import SeasonRanking
|
||||
|
||||
|
||||
def geneate_seasonexcel(json_data):
|
||||
wb = Workbook()
|
||||
worksheet = wb.active
|
||||
"""Generate an excel .xlsx spreadsheet from json data of the kyu/dan
|
||||
rankings.
|
||||
|
||||
:param json_data: The ladder ranking as JSON export."""
|
||||
workbook = Workbook()
|
||||
worksheet = workbook.active
|
||||
|
||||
worksheet.append([
|
||||
'Rang', 'Spitzname',
|
||||
@@ -20,7 +22,6 @@ def geneate_seasonexcel(json_data):
|
||||
'Hanchans', 'Gut', 'Gewonnen'
|
||||
])
|
||||
|
||||
json_data = json_data.values()
|
||||
json_data = sorted(json_data, key=itemgetter('placement'))
|
||||
for row in json_data:
|
||||
worksheet.append([
|
||||
@@ -29,13 +30,13 @@ def geneate_seasonexcel(json_data):
|
||||
row['hanchan_count'],
|
||||
row['good_hanchans'], row['won_hanchans']
|
||||
])
|
||||
wb.save("sample.xlsx")
|
||||
workbook.save("sample.xlsx")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Exports the SeasonRankings"
|
||||
|
||||
"""Exports the SeasonRankings"""
|
||||
|
||||
def handle(self, *args, **options):
|
||||
season_json = SeasonRanking.objects.json_data()
|
||||
geneate_seasonexcel(season_json)
|
||||
"""Exports the current ladder ranking in a spreadsheet.
|
||||
This is useful as a backup in form of a hardcopy."""
|
||||
geneate_seasonexcel(SeasonRanking.objects.json_data())
|
||||
@@ -9,7 +9,9 @@ from mahjong_ranking import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Recalculate all Rankings"
|
||||
""" Recalculate all Kyu/Dan Rankings """
|
||||
|
||||
help = "Recalculate all Kyu/Dan Rankings"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
for ranking in models.KyuDanRanking.objects.all():
|
||||
@@ -1,100 +1,150 @@
|
||||
__author__ = 'christian'
|
||||
|
||||
"""ObjectManagers for the Django Models used in the Mahjong-Ranking."""
|
||||
from datetime import date
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class HanchanManager(models.Manager):
|
||||
|
||||
"""
|
||||
The ObjectManager for models.Hanchan QuerySets.
|
||||
|
||||
It adds many specific filters that makes many queries much easier.
|
||||
"""
|
||||
use_for_related_fields = True
|
||||
|
||||
def confirmed_hanchans(self, user=None, **kwargs):
|
||||
def confirmed_hanchans(self, user=None, **filter_args):
|
||||
""" Return all valid and confirmed Hanchans.
|
||||
|
||||
:param user: Only return Hanchans where this user participated.
|
||||
:param filter_args: To add specific arguments to the Django filter.
|
||||
:return: QuerySet Object
|
||||
"""
|
||||
if user:
|
||||
return self.user_hanchans(user, confirmed=True, **kwargs)
|
||||
return self.user_hanchans(user, confirmed=True, **filter_args)
|
||||
else:
|
||||
return self.filter(confirmed=True, **kwargs)
|
||||
return self.filter(confirmed=True, **filter_args)
|
||||
|
||||
def dan_hanchans(self, user, **kwargs):
|
||||
def dan_hanchans(self, user, **filter_args):
|
||||
""" Return all Hanchans where a specific user has participated and had
|
||||
gain dan points and make his gamestats availabale.
|
||||
|
||||
:param user: Only return Hanchans where this user participated.
|
||||
:param filter_args: To add specific arguments to the Django filter.
|
||||
:return: QuerySet Object
|
||||
"""
|
||||
queryset = self.filter(
|
||||
models.Q(player1=user, player1_dan_points__isnull=False) |
|
||||
models.Q(player2=user, player2_dan_points__isnull=False) |
|
||||
models.Q(player3=user, player3_dan_points__isnull=False) |
|
||||
models.Q(player4=user, player4_dan_points__isnull=False)
|
||||
).filter(confirmed=True, **kwargs)
|
||||
).filter(confirmed=True, **filter_args)
|
||||
queryset = queryset.select_related().order_by('-start')
|
||||
for hanchan in queryset:
|
||||
hanchan.get_playerdata(user)
|
||||
[ hanchan.get_playerdata(user) for hanchan in queryset ]
|
||||
return queryset
|
||||
|
||||
def kyu_hanchans(self, user, **kwargs):
|
||||
def kyu_hanchans(self, user, **filter_args):
|
||||
""" Return all Hanchans where a specific user has participated and had
|
||||
gain kyū points and make his gamestats availabale.
|
||||
|
||||
:param user: Only return Hanchans where this user participated.
|
||||
:param filter_args: To add specific arguments to the Django filter.
|
||||
:return: QuerySet Object
|
||||
"""
|
||||
queryset = self.filter(
|
||||
models.Q(player1=user, player1_kyu_points__isnull=False) |
|
||||
models.Q(player2=user, player2_kyu_points__isnull=False) |
|
||||
models.Q(player3=user, player3_kyu_points__isnull=False) |
|
||||
models.Q(player4=user, player4_kyu_points__isnull=False)
|
||||
).filter(confirmed=True, **kwargs)
|
||||
).filter(confirmed=True, **filter_args)
|
||||
queryset = queryset.select_related().order_by('-start')
|
||||
for hanchan in queryset:
|
||||
hanchan.get_playerdata(user)
|
||||
[ hanchan.get_playerdata(user) for hanchan in queryset ]
|
||||
return queryset
|
||||
|
||||
def season_hanchans(self, user=None, season=None):
|
||||
"""Return all Hanchans that belong to a given or the current season.
|
||||
|
||||
:param user: Only return Hanchans where this user participated.
|
||||
:param season: the year of the wanted season, current year if None.
|
||||
:return: QuerySet Object
|
||||
"""
|
||||
season = season or date.today().year
|
||||
return self.confirmed_hanchans(user=user, season=season)
|
||||
|
||||
def user_hanchans(self, user, since=None, **kwargs):
|
||||
"""
|
||||
|
||||
:param user: User Object, or an player_id as integer
|
||||
def user_hanchans(self, user, since=None, **filter_args):
|
||||
"""Return all Hanchans where a specific user has participated.
|
||||
|
||||
:param user: Return Hanchans where this user participated.
|
||||
:param since: optional a date value since when you want to hanchans
|
||||
:return:
|
||||
:param filter_args: To add specific arguments to the Django filter.
|
||||
:return: a QuerySet Object
|
||||
"""
|
||||
queryset = self.filter(
|
||||
models.Q(player1=user) | models.Q(player2=user) |
|
||||
models.Q(player3=user) | models.Q(player4=user)
|
||||
)
|
||||
if since:
|
||||
queryset = queryset.filter(start__gte=since)
|
||||
if kwargs:
|
||||
queryset = queryset.filter(**kwargs)
|
||||
queryset = queryset.filter(start__gte=since, **filter_args)
|
||||
else:
|
||||
queryset = queryset.filter(**filter_args)
|
||||
queryset = queryset.select_related().order_by('-start')
|
||||
for hanchan in queryset:
|
||||
hanchan.get_playerdata(user)
|
||||
[ hanchan.get_playerdata(user) for hanchan in queryset ]
|
||||
return queryset
|
||||
|
||||
def hanchan_stats(self, queryset=None):
|
||||
queryset = queryset or self.get_query_set()
|
||||
self.num_hanchans = queryset.count()
|
||||
self.won_hanchans = queryset.filter(placement=1).count()
|
||||
self.good_hanchans = queryset.filter(placement__lt=3).count()
|
||||
def unconfirmed_hanchans(self, user=None, **filter_args):
|
||||
""" Return all Hanchans that have been set to unconfirmed.
|
||||
|
||||
def unconfirmed_hanchans(self, user=None, **kwargs):
|
||||
:param user: Only return Hanchans where this user participated.
|
||||
:param filter_args: To add specific arguments to the Django filter.
|
||||
:return: QuerySet Object
|
||||
"""
|
||||
if user:
|
||||
return self.user_hanchans(user, confirmed=False, **kwargs)
|
||||
return self.user_hanchans(user, confirmed=False, **filter_args)
|
||||
else:
|
||||
return self.filter(confirmed=False, **kwargs)
|
||||
return self.filter(confirmed=False, **filter_args)
|
||||
|
||||
|
||||
class SeasonRankingManager(models.Manager):
|
||||
"""
|
||||
The ObjectManager for models.SeasonRanking QuerySets to handle the
|
||||
Ladderrankings.
|
||||
|
||||
It adds many specific filters that makes many queries much easier.
|
||||
"""
|
||||
|
||||
def current_rankings(self):
|
||||
""" Returns the Rankings for the current year/season.
|
||||
|
||||
:return: models.QuerySet
|
||||
"""
|
||||
current_season = date.today().year
|
||||
return self.filter(season=current_season)
|
||||
|
||||
@property
|
||||
def season_list(self):
|
||||
""" Return a list of all availables years/seasons.
|
||||
|
||||
It's useful for select boxes and choice fields.
|
||||
:return: list()
|
||||
"""
|
||||
values_list = self.model.objects.values_list('season', flat=True)
|
||||
return values_list.order_by('season').distinct()
|
||||
|
||||
def json_data(self, season=None):
|
||||
""" Get all Rankings for a given Season and return them as a list of
|
||||
dict objects, suitable for JSON exports and other processings.
|
||||
|
||||
:param season: Season that should be exported, current season if empty
|
||||
:return: a list() of dict() objects suiteable for JSON export.
|
||||
"""
|
||||
season = season or date.today().year
|
||||
json_data = {}
|
||||
json_data = list()
|
||||
values = self.filter(season=season, placement__isnull=False)
|
||||
values = values.values('placement', 'user_id', 'user__username',
|
||||
'user__first_name', 'user__last_name', 'avg_placement', 'avg_score',
|
||||
'user__first_name', 'user__last_name',
|
||||
'avg_placement', 'avg_score',
|
||||
'hanchan_count', 'good_hanchans', 'won_hanchans')
|
||||
for user in values:
|
||||
json_data[user['user_id']] = {
|
||||
json_data.append({
|
||||
'placement': user['placement'],
|
||||
'user_id': user['user_id'],
|
||||
'username': user['user__username'],
|
||||
@@ -105,5 +155,5 @@ class SeasonRankingManager(models.Manager):
|
||||
'hanchan_count': user['hanchan_count'],
|
||||
'good_hanchans': user['good_hanchans'],
|
||||
'won_hanchans': user['won_hanchans']
|
||||
}
|
||||
})
|
||||
return json_data
|
||||
|
||||
@@ -1,78 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 23.05.2011
|
||||
|
||||
@author: christian
|
||||
"""
|
||||
"""Middleware to defer slow denormalization at the end of a request."""
|
||||
from django.core.cache import cache
|
||||
|
||||
from mahjong_ranking import models
|
||||
from . import logger, MIN_HANCHANS_FOR_LADDER
|
||||
|
||||
|
||||
class DenormalizationUpdateMiddleware(object):
|
||||
"""
|
||||
This Class deferres the recalculation for the Otaku XP at the end of a
|
||||
response.
|
||||
"""
|
||||
event_queue = set()
|
||||
season_queue = set()
|
||||
class DenormalizationUpdateMiddleware(object): # Ignore PyLintBear (R0903)
|
||||
"""To recalculate everything in the queues at the end of a POST request."""
|
||||
|
||||
def process_response(self, request, response):
|
||||
# We only do this in POST request, to speedup the responsetime.
|
||||
if request.method == 'POST':
|
||||
print('event_ranking_queue', cache.get(
|
||||
'event_ranking_queue', set()))
|
||||
print('kyu_dan_ranking_queue', cache.get(
|
||||
'kyu_dan_ranking_queue', set()))
|
||||
print('ladder_ranking_queue', cache.get(
|
||||
'ladder_ranking_queue', set()))
|
||||
queue = cache.get('event_ranking_queue', set())
|
||||
if len(queue) > 0:
|
||||
self.recalculate_event_rankings(queue)
|
||||
cache.set('event_ranking_queue', set(), 360)
|
||||
def process_response(self, request, response): # Ignore PyLintBear (R0201)
|
||||
"""Check and process the recalculation queues on each POST request.
|
||||
|
||||
queue = cache.get('kyu_dan_ranking_queue', set())
|
||||
if len(queue) > 0:
|
||||
self.recalulate_kyu_dan_ranking(queue)
|
||||
cache.set('kyu_dan_ranking_queue', set(), 360)
|
||||
:param request: the django HttpRequest object
|
||||
:param response: the django HttpResponse object
|
||||
:return: the django HttpResponse object
|
||||
"""
|
||||
event_queue = set()
|
||||
season_queue = set()
|
||||
|
||||
queue = cache.get('ladder_ranking_queue', set())
|
||||
if len(queue) > 0:
|
||||
self.recalculate_ladder_ranking(queue)
|
||||
cache.set('ladder_ranking_queue', set(), 360)
|
||||
if request.method != 'POST':
|
||||
return response
|
||||
|
||||
for event_id in self.event_queue:
|
||||
self.update_event_placements()
|
||||
for season in self.season_queue:
|
||||
self.update_season_placements()
|
||||
|
||||
return response
|
||||
|
||||
def recalculate_event_rankings(self, queue):
|
||||
# recalculate tournament (event) rankings:
|
||||
for event_id, user_id in queue:
|
||||
ranking = models.EventRanking.objects.get_or_create(
|
||||
event_ranking_queue = cache.get('event_ranking_queue', set())
|
||||
while event_ranking_queue:
|
||||
event_id, user_id = event_ranking_queue.pop()
|
||||
event_ranking = models.EventRanking.objects.get_or_create(
|
||||
event_id=event_id, user_id=user_id)[0]
|
||||
ranking.recalculate()
|
||||
self.event_queue.add(event_id)
|
||||
return queue
|
||||
event_ranking.recalculate()
|
||||
event_queue.add(event_id)
|
||||
cache.set('event_ranking_queue', set(), 360)
|
||||
|
||||
def recalulate_kyu_dan_ranking(self, queue):
|
||||
for user_id, hanchan_start in queue:
|
||||
ranking = models.KyuDanRanking.objects.get_or_create(
|
||||
kyu_dan_ranking_queue = cache.get('kyu_dan_ranking_queue', set())
|
||||
while kyu_dan_ranking_queue:
|
||||
user_id, hanchan_start = kyu_dan_ranking_queue.pop()
|
||||
kyu_dan_ranking = models.KyuDanRanking.objects.get_or_create(
|
||||
user_id=user_id)[0]
|
||||
ranking.recalculate(hanchan_start)
|
||||
return queue
|
||||
kyu_dan_ranking.recalculate(hanchan_start)
|
||||
cache.set('kyu_dan_ranking_queue', set(), 360)
|
||||
|
||||
def recalculate_ladder_ranking(self, queue):
|
||||
for season, user_id in queue:
|
||||
ladder = models.SeasonRanking.objects.get_or_create(
|
||||
ladder_ranking_queue = cache.get('ladder_ranking_queue', set())
|
||||
while ladder_ranking_queue:
|
||||
season, user_id = ladder_ranking_queue.pop()
|
||||
ladder_ranking = models.SeasonRanking.objects.get_or_create(
|
||||
user_id=user_id, season=season)[0]
|
||||
ladder.recalculate()
|
||||
self.season_queue.add(season)
|
||||
ladder_ranking.recalculate()
|
||||
season_queue.add(season)
|
||||
cache.set('ladder_ranking_queue', ladder_ranking_queue, 360)
|
||||
|
||||
def update_event_placements(self):
|
||||
for event_id in self.event_queue:
|
||||
for event_id in event_queue:
|
||||
eventranking_set = models.EventRanking.objects.filter(
|
||||
event_id=event_id).order_by('avg_placement', '-avg_score')
|
||||
placement = 1
|
||||
@@ -81,8 +56,7 @@ class DenormalizationUpdateMiddleware(object):
|
||||
placement += 1
|
||||
ranking.save(force_update=True)
|
||||
|
||||
def update_season_placements(self):
|
||||
for season in self.season_queue:
|
||||
for season in season_queue:
|
||||
logger.info(u'Recalculate placements for Season %d', season)
|
||||
season_rankings = models.SeasonRanking.objects.filter(
|
||||
season=season, hanchan_count__gt=MIN_HANCHANS_FOR_LADDER
|
||||
@@ -92,3 +66,4 @@ class DenormalizationUpdateMiddleware(object):
|
||||
ranking.placement = placement
|
||||
ranking.save(force_update=True)
|
||||
placement += 1
|
||||
return response
|
||||
|
||||
@@ -167,11 +167,11 @@ class Hanchan(models.Model):
|
||||
_('Comment'), blank=True, max_length=255, editable=False)
|
||||
|
||||
comment = models.TextField(_('Comment'), blank=True)
|
||||
confirmed = models.BooleanField(_('Has been Confirmed'), default=True,
|
||||
help_text=_(
|
||||
'Only valid and confirmed Hanchans '
|
||||
'will be counted in the rating.')
|
||||
)
|
||||
confirmed = models.BooleanField(
|
||||
_('Has been Confirmed'), default=True,
|
||||
help_text=_('Only valid and confirmed Hanchans will be counted in the '
|
||||
'rating.')
|
||||
)
|
||||
player_names = models.CharField(max_length=255, editable=False)
|
||||
season = models.PositiveSmallIntegerField(
|
||||
_('Season'), editable=False, db_index=True)
|
||||
@@ -622,17 +622,17 @@ def update_ranking(sender, instance, **kwargs):
|
||||
{'user': user, 'start': str(instance.start.date())}
|
||||
)
|
||||
set_dirty(user=user.id, hanchan_date=instance.start.date())
|
||||
logger.debug("marking event %s for recalculation.", instance.event)
|
||||
set_dirty(event=instance.event_id, user=user.id)
|
||||
if instance.season:
|
||||
logger.debug(
|
||||
"marking %s's ladder %i season for recalculation.",
|
||||
user, instance.season
|
||||
)
|
||||
set_dirty(user=user.id, season=instance.season)
|
||||
|
||||
logger.debug("marking season %d for recalculation.", instance.season)
|
||||
set_dirty(season=instance.season)
|
||||
logger.debug("marking event %s for recalculation.", instance.event)
|
||||
set_dirty(event=instance.event_id, user=user.id)
|
||||
|
||||
|
||||
models.signals.pre_delete.connect(update_ranking, sender=Hanchan)
|
||||
# models.signals.post_save.connect(update_ranking, sender=Hanchan)
|
||||
|
||||
@@ -25,12 +25,11 @@ class KyuDanTest(TestCase):
|
||||
'test_kyu_dan_rankings.json'
|
||||
]
|
||||
|
||||
|
||||
def recalc(self):
|
||||
"""
|
||||
Test if a Kyu/Dan Ranking recalculation gives the same result as stored.
|
||||
|
||||
:return:
|
||||
Test if a Kyu/Dan Ranking recalculation gives the same result as stored.
|
||||
|
||||
:return:
|
||||
"""
|
||||
|
||||
for ranking in KyuDanRanking.objects.all():
|
||||
@@ -49,8 +48,8 @@ class KyuDanTest(TestCase):
|
||||
def test_partial_recalc(self):
|
||||
"""
|
||||
Test if partial recalclulation gives the same results.
|
||||
|
||||
:return:
|
||||
|
||||
:return:
|
||||
"""
|
||||
|
||||
for ranking in KyuDanRanking.objects.all():
|
||||
@@ -78,8 +77,8 @@ class KyuDanTest(TestCase):
|
||||
def test_points_sum(self):
|
||||
"""
|
||||
Test if the sum of the kyu / dan points equals the value in the Ranking.
|
||||
|
||||
:return: None
|
||||
|
||||
:return: None
|
||||
"""
|
||||
for ranking in KyuDanRanking.objects.all():
|
||||
dan_kyu = 'dan_points' if ranking.dan else 'kyu_points'
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
""" URLS to display the Riichi Mahjong Rankings and the Ladder system."""
|
||||
|
||||
"""
|
||||
Created on 03.10.2011
|
||||
|
||||
@author: christian
|
||||
"""
|
||||
from django.views.generic import RedirectView
|
||||
from django.conf.urls import url
|
||||
from django.views.generic import RedirectView
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', RedirectView.as_view(
|
||||
url='/ranking/mahjong-ladder/', permanent=True)
|
||||
),
|
||||
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/$',
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
from datetime import date
|
||||
|
||||
from django.contrib import auth
|
||||
from django.core.urlresolvers import reverse
|
||||
import django.forms
|
||||
import django.http
|
||||
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.utils.translation import ugettext as _
|
||||
from django.views import generic
|
||||
|
||||
from events.models import Event
|
||||
from events.views import EventDetailMixin
|
||||
from events.mixins import EventDetailMixin
|
||||
from . import forms, models
|
||||
from utils.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
|
||||
kyu_dan_order = {
|
||||
'+full_name': ('user__last_name', 'user__first_name'),
|
||||
@@ -135,7 +136,6 @@ class EventRankingList(EventDetailMixin, generic.ListView):
|
||||
|
||||
|
||||
class MahjongMixin(object):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(MahjongMixin, self).get_context_data(**kwargs)
|
||||
try:
|
||||
@@ -149,7 +149,7 @@ class MahjongMixin(object):
|
||||
'latest_hanchan_list'] = \
|
||||
models.Hanchan.objects.confirmed_hanchans()[
|
||||
:3]
|
||||
context['latest_event_list'] = Event.objects.latest_events(num=3)
|
||||
context['latest_event_list'] = Event.objects.upcoming(limit=3)
|
||||
return context
|
||||
|
||||
|
||||
@@ -239,6 +239,7 @@ class PlayerKyuScore(PlayerScore):
|
||||
|
||||
|
||||
class PlayerLadderScore(PlayerScore):
|
||||
season = None
|
||||
template_name = 'mahjong_ranking/player_ladder_score.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@@ -250,10 +251,9 @@ class PlayerLadderScore(PlayerScore):
|
||||
return context
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
try:
|
||||
self.season = int(self.request.GET.get('season'))
|
||||
except:
|
||||
self.season = date.today().year
|
||||
self.season = int(self.request.GET.get('season', date.today().year))
|
||||
hanchan_list = models.Hanchan.objects.season_hanchans(
|
||||
user=self.user, season=self.season)
|
||||
user=self.user,
|
||||
season=self.season
|
||||
)
|
||||
return hanchan_list
|
||||
|
||||
Reference in New Issue
Block a user