Anpassungen für das Hosting bei Djangoeurope und damit verbundenen Versionen Django 1.8 und Python 2.7

This commit is contained in:
Christian Berg
2015-08-05 18:55:48 +02:00
committed by Christian Berg
parent cb4b15b3c6
commit 37d3cb78c1
1355 changed files with 7289 additions and 6858 deletions

View File

@@ -0,0 +1,52 @@
# -*- encoding: utf-8 -*-
from django.utils.log import getLogger
from django.core.cache import cache
KYU_RANKS = (
(45, 1),
(40, 2),
(35, 3),
(30, 4),
(25, 5),
(20, 6),
(15, 7),
(10, 8),
(5, 9),
(0, 10),
)
DAN_RANKS = (
(80, 9),
(70, 8),
(60, 7),
(50, 6),
(40, 5),
(30, 4),
(20, 3),
(10, 2),
(0, 1),
)
DAN_RANKS_DICT = dict([(dan, points) for points, dan in DAN_RANKS])
MIN_HANCHANS_FOR_LADDER = 10
logger = getLogger('kasu.mahjong_ranking')
def set_dirty(event=None, season=None, user=None):
if season and user:
key_to_add = (season, user)
queue_name = 'ladder_ranking_queue'
elif season:
key_to_add = season
queue_name = 'ladder_season_queue'
elif event and user:
key_to_add = (event, user)
queue_name = 'event_ranking_queue'
elif user:
key_to_add = user
queue_name = 'kyu_dan_ranking_queue'
if key_to_add and queue_name:
recalculation_queue = cache.get(queue_name, set())
recalculation_queue.add(key_to_add)
cache.set(queue_name, recalculation_queue, 360)

View File

@@ -0,0 +1,94 @@
# -*- encoding: utf-8 -*-
"""
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
from forms import PlayerFormSet
def recalculate(modeladmin, request, queryset):
if isinstance(modeladmin, HanchanAdmin):
for hanchan in queryset:
hanchan.save()
elif isinstance(modeladmin, EventRankingAdmin):
for event_ranking in queryset:
set_dirty(event=event_ranking.event_id, user=event_ranking.user_id)
elif isinstance(modeladmin, KyuDanAdmin):
for kyu_dan_ranking in queryset:
set_dirty(user=kyu_dan_ranking.user_id)
elif isinstance(modeladmin, LadderSeasonAdmin):
for ranking_season in queryset:
set_dirty(season=ranking_season.id)
elif isinstance(modeladmin, LadderRankingAdmin):
for ladder_ranking in queryset:
set_dirty(user=ladder_ranking.user_id,
season=ladder_ranking.season_id) # @IgnorePep8
recalculate.short_description = _("Recalculate")
class PlayerInline(admin.TabularInline):
extra = 4
formset = PlayerFormSet
readonly_fields = ('placement', 'kyu_points', 'dan_points', 'bonus_points',
'comment',)
max_num = 4
model = models.Player
class EventRankingAdmin(admin.ModelAdmin):
list_filter = ['event']
list_display = ('placement', 'user', 'event', 'avg_placement', 'avg_score',
'hanchan_count', 'good_hanchans', 'won_hanchans', 'dirty')
list_display_links = ('user',)
actions = [recalculate]
class HanchanAdmin(admin.ModelAdmin):
actions = [recalculate]
date_hierarchy = 'start'
list_filter = ['season', 'event']
list_display = ('event', 'start', 'player_names', 'comment',
'confirmed', 'valid', 'check_validity')
inlines = (PlayerInline,)
readonly_fields = ('valid', 'check_validity')
def save_formset(self, request, form, formset, change):
formset.save()
form.is_valid()
form.save()
class KyuDanAdmin(admin.ModelAdmin):
actions = [recalculate]
list_display = ('user', 'kyu', 'kyu_points', 'dan', 'dan_points',
'hanchan_count', 'dirty')
class LadderRankingAdmin(admin.ModelAdmin):
actions = [recalculate]
list_display = ('placement', 'season', 'user', 'avg_placement',
'avg_score', 'hanchan_count', 'good_hanchans',
'won_hanchans', 'dirty')
list_display_links = ('user',)
list_filter = ('season',)
class LadderSeasonAdmin(admin.ModelAdmin):
actions = [recalculate]
list_display = ('name', 'start', 'end', 'dirty')
admin.site.register(models.EventRanking, EventRankingAdmin)
admin.site.register(models.Hanchan, HanchanAdmin)
admin.site.register(models.KyuDanRanking, KyuDanAdmin)
admin.site.register(models.LadderSeason, LadderSeasonAdmin)
admin.site.register(models.LadderRanking, LadderRankingAdmin)

View File

@@ -0,0 +1,111 @@
# -*- encoding: utf-8 -*-
"""
Created on 04.10.2011
@author: christian
"""
from django.contrib.auth import get_user_model
import django.forms
from django.forms.models import BaseInlineFormSet, inlineformset_factory
from django.utils import timezone
from django.utils.translation import ugettext as _
from utils.html5 import forms
from . import models
class HanchanForm(forms.ModelForm):
error_css_class = 'error'
required_css_class = 'required'
start = forms.DateTimeField(label=_('start'), required=True)
class Meta(object):
model = models.Hanchan
fields = ('event', 'start', 'comment')
widgets = {
'event': forms.HiddenInput(),
'comment': forms.widgets.Textarea(attrs={'rows': 4, 'cols': 40})
}
def clean_start(self):
u"""
Das Datum darf nicht in der Zukunft liegen und es muss innerhalb der
Dauer des Events liegen.
"""
start = self.cleaned_data['start']
event = self.cleaned_data['event']
if start > timezone.now():
raise django.forms.ValidationError(
_("It's not allowed to enter future games."))
if not event.start <= start <= event.end:
raise django.forms.ValidationError(
_("Only games running during this event are allowed."))
return start
class HanchanAdminForm(HanchanForm):
class Meta(object):
model = models.Hanchan
fields = ('event', 'start', 'comment', 'confirmed')
widgets = {
'event': forms.HiddenInput(),
'comment': forms.widgets.Textarea(attrs={'rows': 4, 'cols': 40})
}
class PlayerForm(forms.ModelForm):
error_css_class = 'error'
required_css_class = 'required'
player_choices = get_user_model().objects.filter(groups__in=(1, 2))
player_choices = player_choices.order_by('username').distinct()
user = forms.ModelChoiceField(player_choices, required=True)
comment = forms.CharField(
widget=forms.widgets.TextInput(attrs={'maxlength': 255}),
required=False
)
def __init__(self, *args, **kwargs):
super(PlayerForm, self).__init__(*args, **kwargs)
self.fields['bonus_points'].widget.attrs['readonly'] = True
self.fields['bonus_points'].widget.attrs['size'] = 2
self.fields['comment'].widget.attrs['readonly'] = True
self.fields['score'].widget.attrs['size'] = 6
self.fields['score'].widget.attrs['type'] = 'number'
class Meta(object):
model = models.Player
fields = ('hanchan', 'user', 'score', 'bonus_points', 'comment')
class PlayerInlineFormSet(BaseInlineFormSet):
def clean(self):
"""Checks that no two articles have the same title."""
for form in self.forms:
if form.is_valid() and not form.cleaned_data.get('user'):
raise forms.ValidationError(
_("A valid Hanchan needs 4 players"))
self.validate_unique()
return False
class SeasonSelectForm(django.forms.Form):
season = django.forms.ChoiceField(label='', choices=('a', 'b', 'c'))
def __init__(self, user, data=None, *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'] = django.forms.ChoiceField(choices=season_list)
PlayerFormSet = inlineformset_factory(
models.Hanchan,
models.Player,
max_num=4,
extra=4,
form=PlayerForm,
formset=PlayerInlineFormSet
)

Binary file not shown.

View File

@@ -0,0 +1,391 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: kasu.mahjong_ranking\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-01-05 19:23+0100\n"
"PO-Revision-Date: 2015-01-04 11:58+0100\n"
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
"Language-Team: Kasu <verein@kasu.at>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Translated-Using: django-rosetta 0.7.2\n"
"X-Generator: Poedit 1.7.1\n"
#: admin.py:35
msgid "Recalculate"
msgstr "Neuberechnen"
#: forms.py:21
msgid "start"
msgstr "Beginn"
#: forms.py:40 models.py:168
msgid "It's not allowed to enter future games."
msgstr "Spiele in der Zukunft sind nicht erlaubt."
#: forms.py:43
msgid "Only games running during this event are allowed."
msgstr "Nur Spiele während der Veranstaltung sind erlaubt."
#: forms.py:88
msgid "A valid Hanchan needs 4 players"
msgstr "Eine gültige Hanchan braucht 4 Spieler"
#: models.py:84 models.py:613 templates/mahjong_ranking/hanchan_form.html:30
#: templates/mahjong_ranking/player_dan_score.html:19
#: templates/mahjong_ranking/player_invalid_score.html:18
msgid "Comment"
msgstr "Kommentar"
#: models.py:85
msgid "Has been Confirmed"
msgstr "Wurde bestätigt"
#: models.py:87
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:93 templates/mahjong_ranking/hanchan_form.html:23
#: templates/mahjong_ranking/player_dan_score.html:17
#: templates/mahjong_ranking/player_invalid_score.html:15
#: templates/mahjong_ranking/player_kyu_score.html:18
#: templates/mahjong_ranking/player_ladder_score.html:17
msgid "Players"
msgstr "Spieler"
#: models.py:97 templates/mahjong_ranking/ladderranking_list.html:8
#: templates/mahjong_ranking/player_dan_score.html:15
#: templates/mahjong_ranking/player_invalid_score.html:14
#: templates/mahjong_ranking/player_kyu_score.html:16
#: templates/mahjong_ranking/player_ladder_score.html:15
msgid "Start"
msgstr "Beginn"
#: models.py:98
msgid "This is crucial to get the right Hanchans that scores"
msgstr "Wichtig damit die richtigen Hanchans in die Wertung kommen."
#: models.py:99
msgid "Is Valid"
msgstr "Ist gültig"
#: models.py:103
msgid "Hanchan"
msgstr "Hanchan"
#: models.py:104
msgid "Hanchans"
msgstr "Hanchans"
#: models.py:125
msgid "For a Hanchan exactly 4 players are needed."
msgstr "Eine Hanchan benötigt genau 4 Spieler."
#: models.py:166
msgid "Hanchan has no event"
msgstr "Hanchan gehört zu keiner Veranstaltung."
#: models.py:170
msgid "Only games during the event are allowed"
msgstr "Nur Spiele während der Veranstaltung zählen."
#: models.py:238
msgid "Kyū/Dan Ranking"
msgstr "Kyū/Dan Wertung"
#: models.py:239
msgid "Kyū/Dan Rankings"
msgstr "Kyū/Dan Wertungen"
#: models.py:500
msgid "Ladder Season"
msgstr "Saison"
#: models.py:501
msgid "Ladder Seasons"
msgstr "Saisons"
#: views.py:148 views.py:165
msgid "Event does not exist"
msgstr "Veranstaltung existiert nicht"
#: views.py:206
msgid "Season does not exist"
msgstr "Saison existiert nicht"
#: views.py:300
#, python-format
msgid "No user found matching the name %s"
msgstr "Kein Benutzer mit dem Namen %s gefunden"
#: templates/mahjong_ranking/eventranking_list.html:5
#: templates/mahjong_ranking/eventranking_list.html:6
msgid "Tournament Ranking"
msgstr "Turnierwertung"
#: templates/mahjong_ranking/eventranking_list.html:13
#: templates/mahjong_ranking/kyudanranking_list.html:25
#: templates/mahjong_ranking/ladderranking_list.html:20
msgid "Rank"
msgstr "Rang"
#: templates/mahjong_ranking/eventranking_list.html:14
#: templates/mahjong_ranking/kyudanranking_list.html:12
#: templates/mahjong_ranking/ladderranking_list.html:21
msgid "Avatar"
msgstr "Avatar"
#: templates/mahjong_ranking/eventranking_list.html:15
#: templates/mahjong_ranking/kyudanranking_list.html:15
#: templates/mahjong_ranking/ladderranking_list.html:22
msgid "Nickname"
msgstr "Spitzname"
#: templates/mahjong_ranking/eventranking_list.html:16
#: templates/mahjong_ranking/ladderranking_list.html:23
msgid "Name"
msgstr "Name"
#: templates/mahjong_ranking/eventranking_list.html:17
#: templates/mahjong_ranking/ladderranking_list.html:24
msgid "Average"
msgstr "Durchschnitt"
#: templates/mahjong_ranking/eventranking_list.html:21
#: templates/mahjong_ranking/ladderranking_list.html:28
#: templates/mahjong_ranking/player_dan_score.html:16
#: templates/mahjong_ranking/player_invalid_score.html:16
#: templates/mahjong_ranking/player_kyu_score.html:17
#: templates/mahjong_ranking/player_ladder_score.html:16
msgid "Placement"
msgstr "Platzierung"
#: templates/mahjong_ranking/eventranking_list.html:22
#: templates/mahjong_ranking/hanchan_form.html:28
#: templates/mahjong_ranking/hanchan_list.html:16
#: templates/mahjong_ranking/kyudanranking_list.html:30
#: templates/mahjong_ranking/ladderranking_list.html:29
msgid "Score"
msgstr "Punkte"
#: templates/mahjong_ranking/eventranking_list.html:23
#: templates/mahjong_ranking/ladderranking_list.html:30
msgid "count"
msgstr "Anzahl"
#: templates/mahjong_ranking/eventranking_list.html:24
#: templates/mahjong_ranking/ladderranking_list.html:31
msgid "good"
msgstr "gut"
#: templates/mahjong_ranking/eventranking_list.html:25
#: templates/mahjong_ranking/ladderranking_list.html:32
msgid "won"
msgstr "gewonnen"
#: templates/mahjong_ranking/eventranking_list.html:58
#: templates/mahjong_ranking/ladderranking_list.html:77
msgid "Ladder Archive"
msgstr "Ladder Archiv"
#: templates/mahjong_ranking/eventranking_list.html:65
#: templates/mahjong_ranking/ladderranking_list.html:71
msgid "Latest Events"
msgstr "Letzte Veranstaltungen"
#: templates/mahjong_ranking/eventranking_list.html:72
#: templates/mahjong_ranking/ladderranking_list.html:61
msgid "Latest Hanchans"
msgstr "Letzten Hanchans"
#: templates/mahjong_ranking/hanchan_confirm_delete.html:4
#: templates/mahjong_ranking/hanchan_confirm_delete.html:10
#: templates/mahjong_ranking/hanchan_list.html:36
#: templates/mahjong_ranking/player_dan_score.html:47
#: templates/mahjong_ranking/player_invalid_score.html:34
#: templates/mahjong_ranking/player_kyu_score.html:42
#: templates/mahjong_ranking/player_ladder_score.html:39
msgid "Delete Hanchan"
msgstr "Hanchan löschen"
#: templates/mahjong_ranking/hanchan_confirm_delete.html:14
#: templates/mahjong_ranking/hanchan_confirm_delete.html:15
msgid "Cancel"
msgstr "Abbruch"
#: templates/mahjong_ranking/hanchan_confirm_delete.html:18
#: templates/mahjong_ranking/hanchan_confirm_delete.html:19
#: templates/mahjong_ranking/hanchan_list.html:35
#: templates/mahjong_ranking/player_dan_score.html:46
#: templates/mahjong_ranking/player_invalid_score.html:34
#: templates/mahjong_ranking/player_kyu_score.html:42
#: templates/mahjong_ranking/player_ladder_score.html:39
msgid "Delete"
msgstr "Löschen"
#: templates/mahjong_ranking/hanchan_form.html:5
#: templates/mahjong_ranking/hanchan_form.html:20
#: templates/mahjong_ranking/hanchan_list.html:41
#: templates/mahjong_ranking/player_dan_score.html:52
#: templates/mahjong_ranking/player_invalid_score.html:37
#: templates/mahjong_ranking/player_kyu_score.html:45
#: templates/mahjong_ranking/player_ladder_score.html:42
msgid "Edit Hanchan"
msgstr "Hanchan bearbeiten"
#: templates/mahjong_ranking/hanchan_form.html:5
#: templates/mahjong_ranking/hanchan_form.html:20
#: templates/mahjong_ranking/hanchan_list.html:54
msgid "Add Hanchan"
msgstr "Hanchan hinzufügen"
#: templates/mahjong_ranking/hanchan_form.html:27
msgid "User"
msgstr "Benutzer"
#: templates/mahjong_ranking/hanchan_form.html:29
msgid "Bonus"
msgstr "Bonus"
#: templates/mahjong_ranking/hanchan_form.html:51
msgid "back"
msgstr "Zurück"
#: templates/mahjong_ranking/hanchan_form.html:52
msgid "save"
msgstr "Speichern"
#: templates/mahjong_ranking/hanchan_list.html:7
msgid "Played Hanchans"
msgstr "Gespielte Hanchans"
#: templates/mahjong_ranking/hanchan_list.html:15
msgid "Place"
msgstr "Platz"
#: templates/mahjong_ranking/hanchan_list.html:18
#: templates/mahjong_ranking/player_dan_score.html:18
msgid "Dan Points"
msgstr "Dan Punkte"
#: templates/mahjong_ranking/hanchan_list.html:20
#: templates/mahjong_ranking/player_invalid_score.html:17
#: templates/mahjong_ranking/player_kyu_score.html:19
msgid "Kyu Points"
msgstr "Kyu Punkte"
#: templates/mahjong_ranking/hanchan_list.html:40
#: templates/mahjong_ranking/player_dan_score.html:51
#: templates/mahjong_ranking/player_invalid_score.html:37
#: templates/mahjong_ranking/player_kyu_score.html:45
#: templates/mahjong_ranking/player_ladder_score.html:42
msgid "Edit"
msgstr "Bearbeiten"
#: templates/mahjong_ranking/hanchan_list.html:45
msgid "No Hanchan has been added to this event yet."
msgstr "Für diese Veranstaltung wurde noch keine Hanchan eingetragen."
#: templates/mahjong_ranking/hanchan_list.html:52
#: templates/mahjong_ranking/hanchan_list.html:54
msgid "Add"
msgstr "Hinzufügen"
#: templates/mahjong_ranking/hanchan_list.html:52
msgid "Edit Event"
msgstr "Veranstaltung bearbeiten"
#: templates/mahjong_ranking/kyudanranking_list.html:4
#: templates/mahjong_ranking/kyudanranking_list.html:5
msgid "Player List"
msgstr "Spieler Liste"
#: templates/mahjong_ranking/kyudanranking_list.html:20
msgid "Full Name"
msgstr "Voller Name"
#: templates/mahjong_ranking/kyudanranking_list.html:35
msgid "Games Total"
msgstr "Spiele total"
#: templates/mahjong_ranking/ladderranking_list.html:9
msgid "End"
msgstr "Ende"
#: templates/mahjong_ranking/ladderranking_list.html:10
msgid "Participants"
msgstr "Teilnehmer"
#: templates/mahjong_ranking/ladderranking_list.html:79
#: templates/mahjong_ranking/player_ladder_score.html:51
msgid "Season"
msgstr "Saison"
#: templates/mahjong_ranking/page.html:5
msgid "Archive"
msgstr "Archiv"
#: templates/mahjong_ranking/page.html:7
msgid "Add Event"
msgstr "Veranstaltung hinzufügen"
#: templates/mahjong_ranking/player_dan_score.html:4
#: templates/mahjong_ranking/player_dan_score.html:5
msgid "Dan Score for"
msgstr "Dan Wertung für"
#: templates/mahjong_ranking/player_dan_score.html:9
msgid "Hanchans that apply to the Dan Score"
msgstr "Hanchans welche zur Dan Wertung zählen"
#: templates/mahjong_ranking/player_dan_score.html:13
#: templates/mahjong_ranking/player_kyu_score.html:14
#: templates/mahjong_ranking/player_ladder_score.html:13
msgid "Date"
msgstr "Datum"
#: 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:14
msgid "Event"
msgstr "Veranstaltung"
#: templates/mahjong_ranking/player_invalid_score.html:4
#: templates/mahjong_ranking/player_kyu_score.html:4
#: templates/mahjong_ranking/player_kyu_score.html:6
msgid "Kyu Score for"
msgstr "Kyu Wertung für"
#: templates/mahjong_ranking/player_invalid_score.html:6
#: templates/mahjong_ranking/player_invalid_score.html:10
msgid "Invalid hanchans with"
msgstr "Ungültige Hanchans mit"
#: templates/mahjong_ranking/player_kyu_score.html:10
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
msgid "Ladder Score for"
msgstr "Ladder Wertung für"
#: templates/mahjong_ranking/player_ladder_score.html:9
msgid "Hanchans that apply to the Ladder Score"
msgstr "Hanchans welche in der Ladder zählen"
#: templates/mahjong_ranking/player_ladder_score.html:51
msgid "Go"
msgstr "Los"
#~ msgid "Tournament Hanchans"
#~ msgstr "Turnierwertungen"

View File

@@ -0,0 +1 @@
#!/usr/bin/python

View File

@@ -0,0 +1 @@
#!/usr/bin/python

View File

@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
"""
Generate Randum Mahjong Hanchans to the the Raning System
"""
import random
from datetime import timedelta
from django.contrib import auth
from django.core.management.base import BaseCommand
from events.models import Event
from mahjong_ranking import models
class Command(BaseCommand):
help = "Deletes all expired user registrations from the database"
def add_players(self, hanchan):
user_list = set()
while len(user_list) < 4:
random_user = random.choice(self.user_list)
user_list.add(random_user)
player_list = list()
ostwind_list = list()
for user in user_list:
player_list.append(
models.Player(user=user, hanchan=hanchan, score=25000))
for player in player_list:
player.save()
end_of_game = False
ostwind_list.extend(player_list)
ostwind_list.extend(player_list)
ostwind = ostwind_list.pop()
while not end_of_game:
score = random.randrange(1300, 8000, 100)
loser = player_list[random.randrange(0, 4, 1)]
winner = player_list[random.randrange(0, 4, 1)]
winner.score += score
print 'Ostwind: %s, Gewinner: %s, Verlierer: %s, %d Punkte' % (
ostwind.user,
winner.user,
loser.user,
score,
)
if winner == loser:
# Player wins with Tsumo: Everybody pays a third of the score.
print "Tsumo!"
for player in player_list:
if player != winner:
player.score -= score / 3
else:
loser.score -= score
for player in player_list:
if player.score <= 0:
player.score = 0
end_of_game = True
if winner == ostwind:
print "Wind bleibt"
else:
print "Wind wird gewechselt."
try:
ostwind = ostwind_list.pop()
except IndexError:
end_of_game = True
print '---------------------------------------------------------------------'
for player in player_list:
print "%s: %s" % (player.user, player.score)
player.save()
print ""
def create_hanchan(self, event):
start = event.start + timedelta(minutes=random.randrange(00, 300, 15))
print event.name, start
print '=' * 80
hanchan = models.Hanchan(event=event, start=start)
hanchan.save()
self.add_players(hanchan)
hanchan.save()
def handle(self, *args, **options):
num_hanchans = int(options.get('hanchans', 4))
self.user_list = list(auth.get_user_model().objects.all())
for event in Event.objects.all():
for i in range(random.randrange(2, 8)):
self.create_hanchan(event)

View File

@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
"""
Created on 23.05.2011
@author: christian
"""
from django.core.cache import cache
from django.db import transaction
from mahjong_ranking import models
from . import logger
class DenormalizationUpdateMiddleware(object):
"""
This Class deferres the recalculation for the Otaku XP at the end of a
response.
"""
@transaction.atomic
def process_response(self, request, response):
if request.method != 'POST':
# We only do this in POST request, to speedup the responsetime.
return response
# recalculate tournament (event) rankings:
event_ranking_queue = cache.get('event_ranking_queue', set())
if len(event_ranking_queue) > 0:
while len(event_ranking_queue) > 0:
event_id, user_id = event_ranking_queue.pop()
logger.info("recalculate %d tournament Ranking in %s", user_id,
event_id)
ranking = models.EventRanking.objects.get_or_create(
event_id=event_id, user_id=user_id)[0]
ranking.recalculate()
self.recalculate_event_placement(event_id)
cache.set('event_ranking_queue', event_ranking_queue, 360)
# recalculate dirty kyu/dan rankings:
kyu_dan_ranking_queue = cache.get('kyu_dan_ranking_queue', set())
while len(kyu_dan_ranking_queue) > 0:
user_id = kyu_dan_ranking_queue.pop()
ranking = models.KyuDanRanking.objects.get_or_create(
user_id=user_id)[0]
ranking.recalculate()
cache.set('kyu_dan_ranking_queue', kyu_dan_ranking_queue, 360)
# Recaluclate Dirty LadderRankings:
ladder_ranking_queue = cache.get('ladder_ranking_queue', set())
while len(ladder_ranking_queue) > 0:
season_id, user_id = ladder_ranking_queue.pop()
if season_id and user_id:
ranking = models.LadderRanking.objects.get_or_create(
user_id=user_id, season_id=season_id)[0]
ranking.recalculate()
else:
logger.error('Season: %i; Benutzer Nr. %i - existiert nicht!',
season_id, user_id)
cache.set('ladder_ranking_queue', ladder_ranking_queue, 360)
# Recaluclate Dirty Season Placements:
ladder_season_queue = cache.get('ladder_season_queue', set())
for season_id in ladder_season_queue:
season = models.LadderSeason.objects.get_or_create(pk=season_id)[0]
season.recalculate()
cache.set('ladder_season_queue', ladder_season_queue, 360)
return response
def recalculate_event_placement(self, event_id):
eventranking_set = models.EventRanking.objects.filter(
event_id=event_id).order_by('avg_placement', '-avg_score')
eventranking_set.update(placement=None)
placement = 1
for ranking in eventranking_set:
ranking.placement = placement
placement += 1
ranking.save(force_update=True)

View File

@@ -0,0 +1,657 @@
# -*- encoding: utf-8 -*-
from datetime import date, timedelta
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.aggregates import Sum
from django.utils import timezone
from django.utils.translation import ugettext as _
from events.models import Event
from . import KYU_RANKS, DAN_RANKS, DAN_RANKS_DICT, MIN_HANCHANS_FOR_LADDER
from . import logger, set_dirty
kyu_dan_rankings = set()
ladder_rankings = set()
ladder_seasons = set()
class EventRanking(models.Model):
"""
Event Rankings funktionieren genauso wie Season Rankings.
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)
placement = models.PositiveIntegerField(blank=True, null=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)
dirty = models.BooleanField(default=True, editable=False)
class Meta(object):
ordering = ('placement', 'avg_placement', '-avg_score',)
def get_absolute_url(self):
return reverse('event-ranking', args=[self.tourney_id])
def recalculate(self):
"""
Berechnet die durschnittliche Platzierung und Punkte, u.v.m. neu.
Diese Daten werden benötigt um die Platzierung zu erstellen. Sie
können zwar sehr leicht errechnet werden, es macht trotzdem Sinn
sie zwischen zu speichern.
Das Eigenschaft dirty ist ein altes Überbleibsel, um das Objekt
zur neuberrechnung zu markieren. Mittlerweile wird ein lokaler
Cache dafür verwendet, das ist schneller.
"""
logger.info(u'Recalculate EventRanking for Player %s in %s', self.user,
self.event.name) # @IgnorePep8
event_hanchans = Player.objects.valid_hanchans(user=self.user_id,
event=self.event_id) # @IgnorePep8
aggregator = event_hanchans.aggregate(
models.Avg('placement'),
models.Avg('score'),
models.Count('pk'))
self.avg_placement = aggregator['placement__avg']
self.avg_score = aggregator['score__avg']
self.hanchan_count = aggregator['pk__count']
self.good_hanchans = event_hanchans.filter(placement__lt=3).count()
self.won_hanchans = event_hanchans.filter(placement=1).count()
self.dirty = False
if self.hanchan_count <= 0:
self.delete()
else:
self.save()
class Hanchan(models.Model):
"""
Ein komplette Runde Mahjong, die aus genau 4 Spielern bestehen muss.
Es werden aber noch andere Tests durchgeführt, ob sie gültig ist.
Außerdem gehört jede Hanchan zu einer Veranstaltung.
"""
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.')) # @IgnorePep8
event = models.ForeignKey(Event)
player_names = models.CharField(max_length=127, editable=False)
players = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through='Player',
verbose_name=_('Players')
)
season = models.ForeignKey('LadderSeason', blank=True, null=True,
editable=False) # @IgnorePep8
start = models.DateTimeField(_('Start'), help_text=_(
'This is crucial to get the right Hanchans that scores')) # @IgnorePep8
valid = models.BooleanField(_('Is Valid'), default=False)
class Meta(object):
ordering = ('-start',)
verbose_name = _(u'Hanchan')
verbose_name_plural = _(u'Hanchans')
def __str__(self):
return "Hanchan am {0:%d.%m.%Y} um {0:%H:%M} ({1})".format(self.start,
self.player_names)
def check_validity(self):
"""
Prüft ob die Hanchan gültig ist.
4 Spieler müssen genau 100.000 Punkte erreichen, mehr sind nur erlaubt
wenn midestens ein Spieler ins Minus (auf 0) geraten ist. Ansonsten
wird die Hanchan als ungültig markiert aber trotzdem abgespeichert,
außerdem wird die Begründung zurück gegeben, was nicht gestimmt hat.
"""
logger.debug("Hanchan wird geprüft ob er valide ist...")
if not self.pk:
self.valid = False
return
elif self.player_set.distinct().count() != 4:
self.valid = False
return _('For a Hanchan exactly 4 players are needed.')
score_sum = self.player_set.aggregate(Sum('score'))['score__sum']
if score_sum == 100000:
self.valid = True
return '4 Spieler, 100.000 Endpunktestand, die Hanchan ist \
korrekt!'
elif score_sum > 100000 and self.player_set.filter(score=0):
self.valid = True
return 'Endpunktestand über 100.000, aber jemand ist auf 0 \
gefallen. Die Hanchan stimmt.'
elif score_sum < 100000:
self.valid = False
return 'Endpunktestand weniger als 100.000 Punkte.'
elif score_sum > 100000 and not self.player_set.filter(score=0):
self.valid = False
return 'Endpunktestand über 100.000, aber niemand ist auf 0 \
gefallen.'
else:
self.valid = False
return 'Wir wissen nicht warum, aber das kann nicht passen...'
def clean(self):
"""
Prüft ob wichtige Vorrausetzungen gegeben sind und aktualisiert ein
paar Zwischenspeicher, bevor gespeichert wird.
Die Hanchan muss 4 (unterschiedliche) Spieler haben, muss in der
Veranstaltungzeit liegen, und darf nicht in der Zukunft liegen.
Ansonsten wird ein ValidationError ausgegeben und die Hanchan nicht
gespeichert.
Die Gültigkeit wird geprüft und die Sasion in der die Hanchan liegt
wird aktualisert.
"""
super(Hanchan, self).clean()
# if self.pk and self.player_set.distinct().count() != 4:
# raise ValidationError(
# _('For a Hanchan exactly 4 players are needed.'))
if not self.event_id:
raise ValidationError(_("Hanchan has no event"))
elif not self.start:
raise ValidationError(_("Hanchan has no start time set"))
elif self.start > timezone.now():
raise ValidationError(_("It's not allowed to enter future games."))
elif not (self.event.start <= self.start <= self.event.end):
raise ValidationError(_("Only games during the event are allowed"))
return self
def compute_player_placements(self):
u"""
Bestimmt die Platzierung eines der Spieler einer Hanchan und speichert
diese beim jeweiligen Spieler ab.
"""
logger.debug("Berechne die Platzierungen neu...")
attending_players = self.player_set.select_related('hanchan', 'user')
attending_players = attending_players.order_by('-score')
other_player_placement = 0
other_player_score = 0
placement = 1
player_list = []
logger.info("Compute player pacements for Hanchan Nr. %d", self.pk)
for player in attending_players:
player_list.append(player.user.username)
if player.score <= 0:
player.placement = 4
elif player.score == other_player_score:
player.placement = other_player_placement
else:
player.placement = placement
placement += 1
other_player_placement = player.placement
other_player_score = player.score
player.save(season_id=self.season_id, mark_dirty=True)
def get_absolute_url(self):
"""
URL zur Hanchanliste des Events wo diese Hanchan gelistet wurde.
"""
url = reverse('event-hanchan-list', kwargs={'event': self.event.pk})
return u'%(url)s#%(pk)d' % {'url': url, 'pk': self.pk}
def save(self, **kwargs):
logger.debug("Hanchan save() wurde getriggert!")
self.season = self.season or LadderSeason.objects.get_by_date(
self.start)
if self.pk:
self.check_validity()
self.compute_player_placements()
self.player_names = ', '.join(self.player_set.values_list(
'user__username', flat=True).order_by('-score'))
return models.Model.save(self, **kwargs)
class KyuDanRanking(models.Model):
u"""
Die Einstufung des Spieles im Kyu bzw. Dan System.
Im Gegensatz zum Ladder Ranking ist das nicht Saison gebunden.
daher läuft es getrennt.
"""
user = models.OneToOneField(settings.AUTH_USER_MODEL)
dan = models.PositiveSmallIntegerField(blank=True, null=True)
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)
dirty = models.BooleanField(default=True, editable=False)
wins_in_a_row = 0
class Meta(object):
ordering = ('-dan_points', '-kyu_points',)
verbose_name = _(u'Kyū/Dan Ranking')
verbose_name_plural = _(u'Kyū/Dan Rankings')
def __unicode__(self):
if self.dan_points:
return u"%s - %d. Dan" % (self.user.username, self.dan)
else:
return u"%s - %d. Kyu" % (self.user.username, self.kyu)
def append_3_in_a_row_bonuspoints(self, hanchan):
u"""
Wenn der Spieler 3 Siege in folge hatte, bekommt er so viele Punkte
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:
self.wins_in_a_row += 1
else:
self.wins_in_a_row = 0
if self.dan and self.wins_in_a_row > 2:
logger.info('adding bonuspoints for 3 wins in a row for %s',
self.user) # @IgnorePep8
new_dan_rank = self.dan + 1
new_dan_points = DAN_RANKS_DICT[new_dan_rank] + 1
bonus_points = new_dan_points - self.dan_points
logger.debug("Stats for %s:", self.user)
logger.debug("current dan_points: %d", self.dan_points)
logger.debug("current dan: %d", self.dan)
logger.debug("min required points for the next dan: %d",
new_dan_points) # @IgnorePep8
logger.debug("bonus points to add: %d", bonus_points)
hanchan.dan_points += bonus_points
hanchan.bonus_points += bonus_points
hanchan.comment += '3 Siege in Folge: +%d Punkte auf den \
%d. Dan. ' % (bonus_points, new_dan_rank)
self.dan_points += bonus_points
def append_tournament_bonuspoints(self, player):
"""
Prüft ob es die letzte Hanchan in einem Turnier war. Wenn ja werden
bei Bedarf Bonuspunkte vergeben, falls der Spieler das Turnier gewonnen
hat.
:param player: Ein Player Objekt
"""
bonus_points = 0
current_event = player.hanchan.event
if not current_event.is_tournament:
return False # Kein Tunrier, den rest können wir uns sparen.
hanchans_this_event = Player.objects.filter(
user=self.user, hanchan__event=current_event
)
hanchans_this_event = hanchans_this_event.order_by('-hanchan__start')
last_hanchan_this_event = hanchans_this_event[0].hanchan
if player.hanchan != last_hanchan_this_event:
return # Das bruacht nur am Ende eines Turnieres gemacht werden.
event_ranking = EventRanking.objects.get(
user=self.user,
event=current_event)
if event_ranking.placement == 1:
bonus_points += 4
player.comment += '+4 Punkte für den Sieg des Turnieres. '
if event_ranking.avg_placement == 1:
bonus_points += 8
player.comment += '+8 Pkt: alle Spiele des Turnieres gewonnen. '
player.bonus_points += bonus_points
if bonus_points and self.dan:
player.dan_points += bonus_points
self.dan_points += bonus_points
elif bonus_points:
player.kyu_points += bonus_points
self.kyu_points += bonus_points
def get_absolute_url(self):
if self.dan:
return reverse('player-dan-score', args=[self.user.username])
else:
return reverse('player-kyu-score', args=[self.user.username])
def recalculate(self):
"""
Fetches all valid Hanchans from this Player and recalculates his
Kyu/Dan Ranking.
"""
logger.debug("recalculating Kyu/Dan punkte for %s...", self.user)
valid_hanchans = Player.objects.valid_hanchans(user=self.user_id)
valid_hanchans = valid_hanchans.order_by('hanchan__start')
self.kyu_points = 0
self.dan_points = 0
self.dan = None
self.kyu = 10
self.hanchan_count = valid_hanchans.count()
self.won_hanchans = valid_hanchans.filter(placement=1).count()
self.good_hanchans = valid_hanchans.filter(placement=2).count()
logger.info("Neuberechnung der Punkte von %s", self.user)
for hanchan in valid_hanchans:
self.update_points(hanchan)
self.append_tournament_bonuspoints(hanchan)
self.update_rank()
self.append_3_in_a_row_bonuspoints(hanchan)
self.update_rank()
hanchan.save(force_update=True, mark_dirty=False)
self.save(force_update=True)
def update_points(self, player):
"""
Berechne die Kyu bzw. Dan Punkte für ein Spiel neu.
:param player: Das Player Objekt das neuberechnet werden soll.
"""
player.bonus_points = 0
player.comment = ""
player.dan_points = None
player.kyu_points = None
if player.hanchan.event.is_tournament:
tourney_points = 4 - player.placement
if self.dan:
player.dan_points = tourney_points
else:
player.kyu_points = tourney_points
elif self.dan:
if player.score >= 60000:
player.dan_points = 3
elif player.score == 0:
player.dan_points = -3
elif player.placement == 1:
player.dan_points = 2
elif player.placement == 2:
player.dan_points = 1
elif player.placement == 3:
player.dan_points = -1
elif player.placement == 4:
player.dan_points = -2
elif player.score >= 60000:
player.kyu_points = 3
elif player.score >= 30000:
player.kyu_points = 1
elif player.score < 10000:
player.kyu_points = -1
else:
player.kyu_points = 0
# Add the hanchans points to the players score
if player.dan_points:
if self.dan_points + player.dan_points < 0:
# Only substract so much points that player has 0 Points:
player.dan_points -= (self.dan_points + player.dan_points)
self.dan_points += player.dan_points
elif player.kyu_points:
if self.kyu_points + player.kyu_points < 0:
# Only substract so much points that player has 0 Points:
player.kyu_points -= (self.kyu_points + player.kyu_points)
self.kyu_points += player.kyu_points
def update_rank(self):
if self.dan and self.dan_points < 0:
self.dan_points = 0
self.dan = 1
elif self.dan:
old_dan = self.dan
for min_points, dan_rank in DAN_RANKS:
if self.dan_points > min_points:
self.dan = dan_rank
break
if self.dan > old_dan:
self.wins_in_a_row = 0
elif self.kyu_points < 1:
self.kyu_points = 0
self.kyu = 10
elif self.kyu_points > 50:
self.dan = 1
self.kyu = None
self.dan_points = 0
self.kyu_points = 0
else:
for min_points, kyu_rank in KYU_RANKS:
if self.kyu_points > min_points:
self.kyu = kyu_rank
break
class LadderRanking(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
season = models.ForeignKey('LadderSeason')
placement = models.PositiveIntegerField(blank=True, null=True)
avg_placement = models.FloatField(blank=True, null=True)
avg_score = models.FloatField(blank=True, null=True)
hanchan_count = models.PositiveIntegerField(default=0)
good_hanchans = models.PositiveIntegerField(default=0)
won_hanchans = models.PositiveIntegerField(default=0)
dirty = models.BooleanField(default=True, editable=False)
class Meta(object):
ordering = ('placement', 'avg_placement', '-avg_score',)
def get_absolute_url(self):
return reverse('player-ladder-score', args=[self.user.username])
def recalculate(self):
logger.info(u'Recalculate LadderRanking for Player %s in Season %s',
self.user, self.season) # @IgnorePep8
ladder_hanchans = Player.objects.ladder_hanchans(
self.user_id, self.season_id)
aggregate = ladder_hanchans.aggregate(
models.Avg('placement'),
models.Avg('score'),
models.Count('pk')
)
self.dirty = False
self.placement = None
self.hanchan_count = aggregate['pk__count']
self.avg_placement = aggregate['placement__avg']
self.avg_score = aggregate['score__avg']
self.good_hanchans = ladder_hanchans.filter(placement=2).count()
self.won_hanchans = ladder_hanchans.filter(placement=1).count()
self.save(force_update=True)
class LadderSeasonManager(models.Manager):
def current(self):
"""
Returns the current season and caches the result for 12 hours
"""
current_season = cache.get('current_mahjong_season')
if not current_season:
try:
today = date.today()
current_season = self.filter(start__lte=today, end__gte=today)
current_season = current_season[0]
except IndexError:
current_season = None
cache.set('current_mahjong_season', current_season, 4320)
return current_season
def get_by_date(self, deadline):
"""
returns the season that where running on the given date.
:param deadline: the date you're intrested in
"""
try:
season = self.filter(start__lte=deadline, end__gte=deadline)
return season[0]
except IndexError:
return None
class LadderSeason(models.Model):
u"""
Eine Saison für das Kasu interne Ladder-Ranking.
"""
name = models.CharField(max_length=100)
start = models.DateField()
end = models.DateField()
objects = LadderSeasonManager()
dirty = models.BooleanField(default=True, editable=False)
class Meta(object):
ordering = ('start',)
verbose_name = _('Ladder Season')
verbose_name_plural = _('Ladder Seasons')
def __unicode__(self):
return self.name
def get_absolute_url(self):
"""
URL zur Hanchanliste des Events wo diese Hanchan gelistet wurde.
"""
return reverse('mahjong-ladder', kwargs={'season': self.pk})
@property
def participants(self):
return self.ladderranking_set.filter(placement__isnull=False).count()
def recalculate(self):
logger.info(u'Recalculate LadderSeason %s', self.name)
self.ladderranking_set.update(placement=None)
ladderrankings_for_placement = self.ladderranking_set.filter(
hanchan_count__gt=MIN_HANCHANS_FOR_LADDER
).order_by(
'avg_placement', '-avg_score'
)
placement = 1
for ranking in ladderrankings_for_placement:
ranking.placement = placement
ranking.save(force_update=True)
placement += 1
self.dirty = False
self.save(force_update=True)
class PlayerManager(models.Manager):
use_for_related_fields = True
def dan_hanchans(self, user=None):
dan_hanchans = self.valid_hanchans(user)
dan_hanchans = dan_hanchans.filter(dan_points__isnull=False)
return dan_hanchans.select_related()
def kyu_hanchans(self, user=None):
queryset = self.valid_hanchans(user).order_by('-hanchan__start')
queryset = queryset.filter(kyu_points__isnull=False).select_related()
return queryset
def ladder_hanchans(self, user=None, season=None, num_hanchans=None,
max_age=None): # @IgnorePep8
queryset = self.valid_hanchans(user).order_by('-hanchan__start')
queryset = queryset.select_related()
season = season or LadderSeason.objects.current()
if season:
queryset = queryset.filter(hanchan__season_id=season)
if user:
queryset = queryset.filter(user=user)
if max_age:
expiration_date = date.today() - timedelta(days=max_age)
queryset = queryset.filter(start__gt=expiration_date)
if num_hanchans:
hanchan_list = queryset.values_list('id', flat=True)[:num_hanchans]
hanchan_list = set(hanchan_list)
queryset = self.valid_hanchans(user).filter(id__in=hanchan_list)
queryset = queryset.select_related().order_by('-hanchan__start')
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 non_counting_hanchans(self, user=None):
queryset = self.exclude(hanchan__valid=True, hanchan__confirmed=True)
if user:
queryset = queryset.filter(user=user)
queryset = queryset.select_related()
return queryset.order_by('-hanchan__start')
def valid_hanchans(self, user=None, event=None):
queryset = self.filter(hanchan__valid=True, hanchan__confirmed=True)
if user:
queryset = queryset.filter(user=user)
if event:
queryset = queryset.filter(hanchan__event_id=event)
queryset = queryset.select_related()
return queryset.order_by('-hanchan__start')
class Player(models.Model):
hanchan = models.ForeignKey(Hanchan)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
score = models.PositiveIntegerField(default=0)
placement = models.PositiveSmallIntegerField(
blank=True,
null=True,
default=None
)
kyu_points = models.PositiveSmallIntegerField(
blank=True,
null=True,
default=None
)
dan_points = models.PositiveSmallIntegerField(
blank=True,
null=True,
default=None
)
bonus_points = models.PositiveSmallIntegerField(
blank=True,
null=True,
default=0
)
comment = models.TextField(_('Comment'), blank=True)
objects = PlayerManager()
class Meta(object):
unique_together = ('hanchan', 'user')
ordering = ['-score']
def __str__(self):
return "{0}'s Punkte vom {1: %d.%m.%Y um %H:%M}".format(
self.user.username, self.hanchan.start)
def save(self, mark_dirty=True, season_id=None, *args, **kwargs):
season_id = season_id or self.hanchan.season_id
super(Player, self).save(*args, **kwargs)
# Set the correct Ladder Ranking to dirty, create it if necessary
if mark_dirty:
logger.debug("Marking %s's kyu/dan for recalculation", self.user)
set_dirty(user=self.user_id)
else:
return self
if season_id:
logger.debug(
"Marking %s's season no. %i ranking for recalculation.",
self.user, season_id) # @IgnorePep8
set_dirty(season=season_id, user=self.user_id)
logger.debug("Marking season no %i for recalculation.", season_id)
set_dirty(season=season_id)
if self.hanchan.event.is_tournament:
logger.debug("Marking tournament %s for recalculation.",
self.hanchan.event) # @IgnorePep8
set_dirty(event=self.hanchan.event_id, user=self.user_id)
return self
def update_ranking_delete(sender, instance, **kwargs): # @UnusedVariable
for player in instance.player_set.all():
set_dirty(user=player.user_id)
if instance.season_id:
set_dirty(season=instance.season_id, user=player.user_id)
models.signals.pre_delete.connect(update_ranking_delete, sender=Hanchan)

View File

@@ -0,0 +1,79 @@
{% extends "events/event_detail.html" %}
{% load i18n comments%}
{% block title %}{% trans "Tournament Ranking" %}: {{ event.name }}{% endblock %}
{% block teaser %}<h1>{% trans "Tournament Ranking" %}: {{ event.name }}</h1>{% endblock %}
{% block maincontent %}
<div class="grid_12">
<table>
<thead>
<tr>
<th rowspan="2">{% trans "Rank" %}</th>
<th rowspan="2">{% trans "Avatar" %}</th>
<th rowspan="2">{% trans "Nickname" %}</th>
<th rowspan="2">{% trans "Name" %}</th>
<th colspan="2">{% trans 'Average' %}</th>
<th colspan="3">Hanchans</th>
</tr>
<tr>
<th>{% trans 'Placement' %}</th>
<th>{% trans "Score" %}</th>
<th>{% trans "count" %}</th>
<th>{% trans "good" %}</th>
<th>{% trans "won" %}</th>
</tr>
</thead>
{% for player in eventranking_list %}
{% with player.user.get_profile as profile %}
<tr>
<td class="center">{{player.placement}}.</td>
<td class="avatar"><a href="{{ player.user.get_absolute_url }}">
{% if profile.thumbnail %}
<img src="{{profile.thumbnail.url}}" alt="" />
{% else %}
<img src="{{STATIC_URL}}img/unknown_thumbnail.png" alt=""/>
{% endif %}
</a></td>
<td><a href="{{ player.user.get_absolute_url }}">{{player.user}}</a></td>
<td>{% if user.is_authenticated %}{{profile.last_name}} {{profile.first_name}}{% else %} ---{% endif %}</td>
<td class="center">{{player.avg_placement|floatformat:2 }}</td>
<td class="right">{{player.avg_score|floatformat:0 }}</td>
<td class="right">{{player.hanchan_count}}</td>
<td class="right">{{player.good_hanchans}}</td>
<td class="right">{{player.won_hanchans}}</td>
</tr>
{% endwith %}
{% empty %}
<tr><td colspan="8">Leider hat es noch niemand in das Ranking geschafft.
Ein Spieler wird erst ins Ranking genommen wenn er 5 Hanchans absolviert hat.</td></tr>
{% endfor %}
</table>
</div>
{% endblock %}
{% block sidebar %}
{% if is_archive %}
<h2>{% trans 'Ladder Archive' %}</h2>
<ul class="list">
{% for season in season_archive %}
<li class="season"><a href="{% url 'mahjong-ladder-archive' season.id %}">{{season.name}}</a></li>
{% endfor %}
</ul>
{% else %}
<h2>{% trans 'Latest Events' %}</h2>
<ul class="list">
{% for event in latest_event_list %}
<li class="event"><a href="{% url 'event-hanchan-list' event.pk %}">{{event.name}}</a></li>
{% endfor %}
</ul>
<h2>{% trans 'Latest Hanchans' %}</h2>
<ul class="comment_list">
{% for hanchan in latest_hanchan_list %}
<li class="hanchan"><a href="{% url 'event-hanchan-list' hanchan.event.pk %}">{{hanchan.event.name}}</a> {{hanchan.start|time:'H:i'}}: {{hanchan.player_names}}</li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "events/event_detail.html" %}
{% load i18n comments %}
{% block meta_title %}{% trans 'Delete Hanchan' %}{% endblock %}
{% block maincontent %}
<form method="post" class="grid_12">
{% csrf_token %}
<fieldset>
<legend>{% trans "Delete Hanchan" %}</legend>
{% include 'form.html' %}
<p class="buttonbar">
<button type="button" onclick="window.history.back()">
<img src="{{STATIC_URL}}icons/cancel.png" alt="{% trans 'Cancel' %}"/>
{% trans 'Cancel' %}
</button>
<button type="submit">
<img src="{{STATIC_URL}}icons/table_delete.png" alt="{% trans 'Delete' %}"/>
{% trans 'Delete' %}
</button>
</p>
</fieldset>
</form>
{% endblock %}
{% block buttonbar %}{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "events/event_detail.html" %}
{% load i18n comments fieldset_extras %}
{% block title %}
{% if hanchan.id %}{% trans "Edit Hanchan" %}{% else %}{% trans "Add Hanchan" %}{% endif %}
{% endblock %}
{% block maincontent %}
{% get_fieldset "event, start" from form as event_formset %}
{% if perms.mahjong_ranking.delete_hanchan %}
{% get_fieldset "comment, confirmed" from form as hanchan_formset %}
{% else %}
{% get_fieldset "comment" from form as hanchan_formset %}
{% endif %}
{% for hidden in form.hidden_fields %}{% if hidden.errors %}{{ hidden.errors }}{% endif %}{% endfor %}
<form class="grid_12" method="post">
{% csrf_token %}
<fieldset>
<legend>{% if hanchan.id %}{% trans "Edit Hanchan" %}{% else %} {% trans "Add Hanchan" %}{% endif %}</legend>
{% with event_formset as form %}{% include "form.html" %}{% endwith %}
<div>
<label class="field_name required">{% trans 'Players' %}</label>
{{ formset.management_form }}
<table>
<tr>
<th>{% trans 'User' %}</th>
<th>{% trans 'Score' %}</th>
<th>{% trans 'Bonus' %}</th>
<th width="75%">{% trans 'Comment' %}</th>
</tr>
{% for form in formset %}
<tr>
<td class="{{form.user.css_classes}}">{{form.id}} {{form.user}} {% for error in form.user.errors %}<br />{{error}}{% endfor %}</td>
<td class="{{form.score.css_classes}}">{{form.score}} {% for error in form.score.errors %}<br />{{error}}{% endfor %}</td>
<td class="{{form.bonus_points.css_classes}}">{{form.bonus_points}} {% for error in form.bonus_points.errors %}<br />{{error}}{% endfor %}</td>
<td class="{{form.comment.css_classes}}" width="*">{{form.comment}} {% for error in form.comment.errors %}<br />{{error}}{% endfor %}</td>
</tr>
{% endfor %}
</table>
</div>
{% for error in formset.errors %}<p class="error">{{error}}</p>{% endfor %}
{% if hanchan.pk and not hanchan.valid %}<p class="error">{{hanchan.check_validity}}</p>{% endif %}
{% with hanchan_formset as form %}{% include "form.html" %}{% endwith %}
{% for error in form.non_field_errors %}<p class="error">{{error}}</p>{% endfor %}
<p class="buttonbar">
<a href="{{hanchan.get_absolute_url}}" class="button"><img src="{{STATIC_URL}}icons/arrow_undo.png" alt="{% trans 'back' %}" /> {% trans 'back' %}</a>
<button type="submit"><img src="{{STATIC_URL}}icons/table_save.png" alt="{% trans 'save' %}" /> {% trans 'save' %}</button>
</p>
</fieldset>
</form>
{% endblock %}
{% block buttonbar %}{% endblock %}

View File

@@ -0,0 +1,56 @@
{% extends "events/event_detail.html" %}
{% load i18n humanize %}
{% block title %}Hanchans: {{ event.name }}{% endblock %}
{% block maincontent %}
<h2 class="grid_12">{% trans 'Played Hanchans' %}</h2>
<p>&nbsp;</p>
{% for hanchan in hanchan_list %}
<div class="grid_12" id="{{ hanchan.pk }}"><h3>{{hanchan.start|time:'H:i'}}: {{ hanchan.player_names }}</h3></div>
{% for player in hanchan.player_set.all %}
<a class="grid_1" href="{% url 'player-ladder-score' player.user %}"><img src="{% if player.user.get_profile.thumbnail %}{{player.user.get_profile.thumbnail.url}}{% else %}{{STATIC_URL}}img/unknown_thumbnail.png{% endif %}" class="avatar" alt="{{ player.user }}" title="{{ player.user }}"/></a>
<div class="grid_2">
<a class="player" href="{% url 'player-ladder-score' player.user %}" title="{{ player.comment }}">{{ player.user }}</a>
<p><strong>{{ player.placement|ordinal }} {% trans 'Place' %}</strong><br />
<strong>{% trans 'Score' %}:</strong> {{ player.score|intcomma }}<br />
{% if player.dan_points != None %}
<strong>{% trans 'Dan Points' %}:</strong> {{ player.dan_points }}
{% else %}
<strong>{% trans 'Kyu Points' %}:</strong> {{ player.kyu_points|default:0 }}
{% endif %}
</p>
</div>
{% endfor %}
{% if not hanchan.valid %}
<p class="grid_12 error"><strong>Ungültig:</strong> {{hanchan.check_validity}}</p>
{% elif not hanchan.confirmed %}
<p class="grid_12 error">Diese Hanchan wurde nicht anerkannt und wird daher nicht gezählt.</p>
{% elif hanchan.comment %}
<p class="grid_12">{{ hanchan.comment }}</p>
{% endif %}
<p class="grid_12 more_link">
{% if perms.mahjong_ranking.delete_hanchan %}
<a href="{% url 'delete-hanchan' hanchan.pk %}" class="button"><img src="{{STATIC_URL}}icons/table_delete.png"
alt="{% trans 'Delete' %}"/>
{% trans 'Delete Hanchan' %}</a>
{% endif %}
{% if perms.mahjong_ranking.change_hanchan %}
<a href="{% url 'edit-hanchan' hanchan.pk %}" class="button"><img src="{{STATIC_URL}}icons/table_edit.png"
alt="{% trans 'Edit' %}"/>
{% trans 'Edit Hanchan' %}</a>
{% endif %}
</p>
{% empty %}
<h3 class="grid_12">{% trans 'No Hanchan has been added to this event yet.'%}</h3>
{% endfor %}
{% endblock %}
{% block buttonbar %}
{% if perms.mahjong_ranking.add_hanchan %}
<a class="button" href="{{event.get_edit_url}}"><img src="{{STATIC_URL}}icons/calendar_edit.png"
alt="{% trans 'Add' %}"/> {% trans 'Edit Event' %}</a>
<a class="button" href="{% url 'add-hanchan-form' event.id %}"><img src="{{STATIC_URL}}icons/table_add.png"
alt="{% trans 'Add' %}"/> {% trans 'Add Hanchan' %}</a>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,60 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans 'Player List' %}{% endblock %}
{% block teaser %}<h2>{% trans 'Player List' %}</h2>{% endblock %}
{% block maincontent %}
<main class="grid_12">
<table>
<thead>
<tr>
<th>{% trans 'Avatar' %}</th>
<th>
<a href="{% url 'kyudanranking-list' order_by='+username' %}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_up.png" alt="▴"/></a>
{% trans 'Nickname' %}
<a href="{% url 'kyudanranking-list' order_by='-username' %}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_down.png" alt="▾"/></a>
</th>
<th>
<a href="{% url 'kyudanranking-list' order_by='+full_name'%}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_up.png" alt="▴"/></a>
{% trans 'Full Name' %}
<a href="{% url 'kyudanranking-list' order_by='-full_name' %}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_down.png" alt="▾"/></a>
</th>
<th>
<a href="{% url 'kyudanranking-list' order_by='+rank' %}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_up.png" alt="▴"/></a>
{% trans 'Rank' %}
<a href="{% url 'kyudanranking-list' order_by='-rank' %}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_down.png" alt="▾"/></a>
</th>
<th>
<a href="{% url 'kyudanranking-list' order_by='+score' %}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_up.png" alt="▴"/></a>
{% trans 'Score' %}
<a href="{% url 'kyudanranking-list' order_by='-score' %}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_down.png" alt="▾"/></a>
</th>
<th>
<a href="{% url 'kyudanranking-list' order_by='+hanchan_count'%}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_up.png" alt="▴"/></a>
{% trans 'Games Total' %}
<a href="{% url 'kyudanranking-list' order_by='-hanchan_count'%}?page={{page_obj.number}}"><img src="{{STATIC_URL}}icons/bullet_arrow_down.png" alt="▾"/></a>
</th>
</tr>
</thead>
<tbody>
{% for ranking in kyudanranking_list %}{% with ranking.user.get_profile as profile %}
<tr>
<td>
{% if profile.thumbnail %}
<a href="{{ ranking.get_absolute_url }}"><img src="{{profile.thumbnail.url}}" alt="" /></a>
{% else %}
<a href="{{ ranking.get_absolute_url }}"><img src="{{STATIC_URL}}img/unknown_thumbnail.png" alt=""/></a>
{% endif %}
</td>
<td><a href="{{ ranking.get_absolute_url }}">{{ ranking.user }}</a></td>
<td>{% if user.is_authenticated %}{{profile.last_name}} {{profile.first_name}}{% else %} ---{% endif %}</td>
<td>{% if ranking.dan %} {{ranking.dan}}. Dan {% else %} {{ranking.kyu}} Kyu {% endif %}</td>
<td class="right">{% if ranking.dan %} {{ranking.dan_points}} {% else %} {{ranking.kyu_points}} {% endif %}</td>
<td class="right">{{ranking.hanchan_count}}</td>
</tr>
{% endwith %}{% endfor %}
</tbody>
</table>
</main>
{% endblock %}

View File

@@ -0,0 +1,86 @@
{% extends "base.html" %}
{% load i18n comments%}
{% block teaser %}
<h2>Mahjong Ranking - {{season.name}}</h2>
<div id="teaser_text">
<ul class="info">
<li class="date">{% trans 'Start' %}: {{season.start}}</li>
<li class="date">{% trans 'End' %}: {{season.end}}</li>
<li class="user">{% trans 'Participants' %}: {{season.participants}}</li>
</ul>
</div>
{% endblock %}
{% block maincontent %}
<main class="grid_12">
<table>
<thead>
<tr>
<th rowspan="2">{% trans "Rank" %}</th>
<th rowspan="2">{% trans "Avatar" %}</th>
<th rowspan="2">{% trans "Nickname" %}</th>
<th rowspan="2">{% trans "Name" %}</th>
<th colspan="2">{% trans 'Average' %}</th>
<th colspan="3">Hanchans</th>
</tr>
<tr>
<th>{% trans 'Placement' %}</th>
<th>{% trans "Score" %}</th>
<th>{% trans "count" %}</th>
<th>{% trans "good" %}</th>
<th>{% trans "won" %}</th>
</tr>
</thead>
{% for player in ladderranking_list %}
{% with player.user.get_profile as profile %}
<tr>
<td class="center">{{player.placement}}.</td>
<td><a href="{{ player.get_absolute_url }}?season={{season.id}}"><img src="{% if profile.thumbnail %}{{profile.thumbnail.url}}{% else %}{{STATIC_URL}}img/unknown_thumbnail.png{% endif %}" class="avatar" alt=""/></a></td>
<td><a href="{{ player.get_absolute_url }}?season={{season.id}}">{{player.user}}</a></td>
<td>{% if user.is_authenticated %}{{profile.last_name}} {{profile.first_name}}{% else %} ---{% endif %}</td>
<td class="center">{{player.avg_placement|floatformat:2 }}</td>
<td class="right">{{player.avg_score|floatformat:0 }}</td>
<td class="right">{{player.hanchan_count}}</td>
<td class="right">{{player.good_hanchans}}</td>
<td class="right">{{player.won_hanchans}}</td>
</tr>
{% endwith %}
{% empty %}
<tr>
<td colspan="8">Leider hat es noch niemand in das Ranking geschafft.
Ein Spieler wird erst ins Ranking genommen wenn er 5 Hanchans absolviert hat.
</td>
</tr>
{% endfor %}
</table>
</main>
{% endblock %}
{% block redbox %}
<h2>{% trans 'Latest Hanchans' %}</h2>
<ul class="list">
{% for hanchan in latest_hanchan_list %}
<li class="hanchan">
<a href="{% url 'event-hanchan-list' hanchan.event.pk %}">{{hanchan.event.name}}</a>
{{hanchan.start|time:'H:i'}}:
<small>{{hanchan.player_names}}</small>
</li>
{% endfor %}
</ul>
<h3>{% trans 'Latest Events' %}</h3>
<ul class="list">
{% for event in latest_event_list %}
<li class="event"><a href="{% url 'event-hanchan-list' event.pk %}">{{event.name}}</a></li>
{% endfor %}
</ul>
<h3>{% trans 'Ladder Archive' %}</h3>
<form name="season_select">
<label for="season">{% trans 'Season' %}</label>
<select id="season" name="season" size="1" onChange="window.location.href = document.season_select.season.options[document.season_select.season.selectedIndex].value;">
{% for season_link in season_list%}
<option value="{{ season_link.get_absolute_url }}" {% ifequal season.id season_link.id %}selected="selected"{% endifequal %}>{{ season_link.name }}</option>
{% endfor %}
</select>
</form>
{% endblock %}

View File

@@ -0,0 +1,9 @@
{% extends "base.html" %}
{% load i18n %}
{% block additional_nav_elements %}
<a href="{% url 'season_ranking-archive' %}" class="{% if is_archive %}active{% endif %}">{% trans 'Archive' %}</a>
{% if perms.events.add_event %}
<a href="{% url 'event-form' %}" class="{% ifequal request.path '/events/add/' %}active{% endifequal %}">{% trans 'Add Event' %}</a>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,59 @@
{% extends "membership/membership_detail.html" %}
{% load i18n %}
{% block title %} {% trans 'Dan Score for' %} {{membership.user.username}} {% endblock %}
{% block teaser %}<h2>{% trans 'Dan Score for' %} {{membership.user.username}}</h2>{% endblock %}
{% block score_list %}
<div class="grid_12">
<h2>{% trans 'Hanchans that apply to the Dan Score' %}</h2>
<table>
<thead>
<tr>
<th rowspan="2">{% trans 'Date' %}</th>
<th rowspan="2">{% trans 'Event' %}</th>
<th rowspan="2">{% trans 'Start' %}</th>
<th rowspan="2">{% trans 'Placement' %}</th>
<th colspan="4">{% trans 'Players' %}</th>
<th rowspan="2">{% trans 'Dan Points' %}</th>
<th rowspan="2">{% trans 'Comment' %}</th>
<th rowspan="2"></th>
</tr>
<tr>
<th>1.</th>
<th>2.</th>
<th>3.</th>
<th>4.</th>
</tr>
</thead>
{% for result in player_list %}
<tr>
<td>{{ result.hanchan.start|date:'SHORT_DATE_FORMAT' }}</td>
<td><a href="{{ result.hanchan.get_absolute_url }}">{{ result.hanchan.event.name }}</a></td>
<td>{{ result.hanchan.start|time:'H:i' }}</td>
<td class="center">{{result.placement}}.</td>
{% for player in result.hanchan.player_set.all %}
<td class="center"><a href="{% url 'player-dan-score' player.user.username %}">{{player.user}}</a><br/>
{{player.score }}
</td>
{% endfor %}
<td class="center">{{result.dan_points}}</td>
<td>{{ result.comment }}</td>
<td>
{% if perms.mahjong_ranking.delete_hanchan %}
<a href="{% url 'delete-hanchan' result.hanchan.pk %}"><img src="{{STATIC_URL}}icons/table_delete.png"
alt="{% trans 'Delete' %}"
title="{% trans 'Delete Hanchan' %}"/></a>
{% endif %}
{% if perms.mahjong_ranking.change_hanchan %}
<a href="{% url 'edit-hanchan' result.hanchan.pk %}"><img src="{{STATIC_URL}}icons/table_edit.png"
alt="{% trans 'Edit' %}"
title="{% trans 'Edit Hanchan' %}"/></a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View File

@@ -0,0 +1,44 @@
{% extends "membership/membership_detail.html" %}
{% load i18n %}
{% block title %} {% trans 'Kyu Score for' %} {{player.username}} {% endblock %}
{% block teaser %}<h2>{% trans 'Invalid hanchans with' %} {{membership.user.username}}</h2>{% endblock %}
{% block score_list %}
<div class="grid_12">
<h2>{% trans 'Invalid hanchans with' %} {{membership.user.username}}</h2>
<table>
<thead><tr>
<th>{% trans 'Event' %}</th>
<th>{% trans 'Start' %}</th>
<th colspan="4">{% trans 'Players' %}</th>
<th>{% trans 'Placement' %}</th>
<th>{% trans 'Kyu Points' %}</th>
<th>{% trans 'Comment' %}</th>
<th colspan="2"></th>
</tr></thead>
{% for result in player_list %}
<tr>
<td><a href="{{ result.hanchan.get_absolute_url }}">{{ result.hanchan.event.name }}</a></td>
<td>{{ result.hanchan.start|time:'H:i' }}</td>
{% for player in result.hanchan.player_set.all %}
<td class="center"><a href="{% url 'player-invalid-score' player.user.username %}">{{player.user}}</a><br/> {{player.score }}</td>
{% endfor %}
<td class="center">{{result.placement}}</td>
<td class="center">{{result.kyu_points}}</td>
<td>{{ result.comment }}</td>
<td>
{% if perms.mahjong_ranking.delete_hanchan %}
<a href="{% url 'delete-hanchan' result.hanchan.pk %}"><img src="{{STATIC_URL}}icons/table_delete.png" alt="{% trans 'Delete' %}" title="{% trans 'Delete Hanchan' %}"/></a>
{% endif %}
{% if perms.mahjong_ranking.change_hanchan %}
<a href="{% url 'edit-hanchan' result.hanchan.pk %}"><img src="{{STATIC_URL}}icons/table_edit.png" alt="{% trans 'Edit' %}" title="{% trans 'Edit Hanchan' %}"/></a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View File

@@ -0,0 +1,52 @@
{% extends "membership/membership_detail.html" %}
{% load i18n %}
{% block title %} {% trans 'Kyu Score for' %} {{membership.user.username}} {% endblock %}
{% block teaser %}<h2>{% trans 'Kyu Score for' %}{{membership.user.username}}</h2>{% endblock %}
{% block score_list %}
<div class="grid_12">
<h2>{% trans 'Hanchans that apply to the Kyu Score' %}</h2>
<table>
<thead><tr>
<tr>
<th rowspan="2">{% trans 'Date' %}</th>
<th rowspan="2">{% trans 'Event' %}</th>
<th rowspan="2">{% trans 'Start' %}</th>
<th rowspan="2">{% trans 'Placement' %}</th>
<th colspan="4">{% trans 'Players' %}</th>
<th rowspan="2">{% trans 'Kyu Points' %}</th>
<th rowspan="2"></th>
</tr>
<tr>
<th>1.</th>
<th>2.</th>
<th>3.</th>
<th>4.</th>
</tr>
</thead>
{% for result in player_list %}
<tr>
<td>{{ result.hanchan.start|date:'SHORT_DATE_FORMAT' }}</td>
<td><a href="{{ result.hanchan.get_absolute_url }}">{{ result.hanchan.event.name }}</a></td>
<td>{{ result.hanchan.start|time:'H:i' }}</td>
<td class="center">{{result.placement}}.</td>
{% for player in result.hanchan.player_set.select_related %}
<td class="center"><a href="{% url 'player-kyu-score' player.user.username %}">{{player.user}}</a><br/> {{player.score }}</td>
{% endfor %}
<td class="center">{{result.kyu_points}}</td>
<td>
{% if perms.mahjong_ranking.delete_hanchan %}
<a href="{% url 'delete-hanchan' result.hanchan.pk %}"><img src="{{STATIC_URL}}icons/table_delete.png" alt="{% trans 'Delete' %}" title="{% trans 'Delete Hanchan' %}"/></a>
{% endif %}
{% if perms.mahjong_ranking.change_hanchan %}
<a href="{% url 'edit-hanchan' result.hanchan.pk %}"><img src="{{STATIC_URL}}icons/table_edit.png" alt="{% trans 'Edit' %}" title="{% trans 'Edit Hanchan' %}"/></a>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View File

@@ -0,0 +1,57 @@
{% extends "membership/membership_detail.html" %}
{% load i18n %}
{% block title %} {% trans 'Ladder Score for' %} {{membership.user.username}} {% endblock %}
{% block teaser %}<h2>{% trans 'Ladder Score for' %} {{membership.user.username}}</h2>{% endblock %}
{% block score_list %}
<div class="grid_12">
<h2>{% trans 'Hanchans that apply to the Ladder Score' %} - {{season.name}}</h2>
<table>
<thead>
<tr>
<th rowspan="2">{% trans 'Date' %}</th>
<th rowspan="2">{% trans 'Event' %}</th>
<th rowspan="2">{% trans 'Start' %}</th>
<th rowspan="2">{% trans 'Placement' %}</th>
<th colspan="4">{% trans 'Players' %}</th>
<th rowspan="2"></th>
</tr>
<tr>
<th>1.</th>
<th>2.</th>
<th>3.</th>
<th>4.</th>
</tr>
</thead>
<tbody>
{% for result in player_list %}
<tr>
<td>{{ result.hanchan.start|date:'SHORT_DATE_FORMAT' }}</td>
<td><a href="{{ result.hanchan.get_absolute_url }}">{{ result.hanchan.event.name }}</a></td>
<td>{{ result.hanchan.start|time:'H:i' }}</td>
<td class="center">{{result.placement}}.</td>
{% for player in result.hanchan.player_set.select_related %}
<td class="center"><a href="{% url 'player-ladder-score' player.user.username %}?season={{season.id}}">{{player.user}}</a><br/> {{player.score}}</td>
{% endfor %}
<td>
{% if perms.mahjong_ranking.delete_hanchan %}
<a href="{% url 'delete-hanchan' result.hanchan.pk %}"><img src="{{STATIC_URL}}icons/table_delete.png" alt="{% trans 'Delete' %}" title="{% trans 'Delete Hanchan' %}"/></a>
{% endif %}
{% if perms.mahjong_ranking.change_hanchan %}
<a href="{% url 'edit-hanchan' result.hanchan.pk %}"><img src="{{STATIC_URL}}icons/table_edit.png" alt="{% trans 'Edit' %}" title="{% trans 'Edit Hanchan' %}"/></a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
<tfooter>
<tr><td colspan="9" class="right">
<form action="{% url 'player-ladder-score' membership.user.username %}">
<label for="id_season">{% trans 'Season' %}:</label> {{seasons_select_form.season}} <button type="submit">{% trans 'Go' %}</button>
</form>
</td></tr>
</tfooter>
</table>
</div>
{% endblock %}

View File

@@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

View File

@@ -0,0 +1,42 @@
# -*- encoding: utf-8 -*-
"""
Created on 03.10.2011
@author: christian
"""
from django.conf.urls import * # @UnusedWildImport
from django.views.generic import RedirectView
import views
urlpatterns = patterns(
'',
url('^$', RedirectView.as_view(pattern_name='mahjong-ladder', permanent=True)),
url(r'players/$', views.KyuDanRankingList.as_view(),
name="kyudanranking-list"),
url(r'players/(?P<order_by>[\+\-\w]+)/$', views.KyuDanRankingList.as_view(),
name="kyudanranking-list"),
url(r'ranking/$', views.LadderRankingList.as_view(), name="mahjong-ladder"),
url(r'ranking/(?P<season>[\d]+)/$', views.LadderRankingList.as_view(),
name="mahjong-ladder"),
url(r'ranking/event/(?P<event>[\d]+)/$', views.EventHanchanList.as_view(),
name="event-hanchan-list"),
url(r'ranking/event/(?P<event>[\d]+)/ranking/$',
views.EventRankingList.as_view(), name="event-ranking"),
url(r'ranking/event/(?P<event>[\d]+)/add-hanchan/$',
views.HanchanForm.as_view(), name="add-hanchan-form"),
url(r'hanchan/(?P<hanchan>[\d]+)/edit/$', views.HanchanForm.as_view(),
name="edit-hanchan"),
url(r'hanchan/(?P<hanchan>[\d]+)/delete/$', views.DeleteHanchan.as_view(),
name="delete-hanchan"),
url(r'player/(?P<username>[\-\.\d\w]+)/dan/$',
views.PlayerDanScore.as_view(), name="player-dan-score"),
url(r'player/(?P<username>[\-\.\d\w]+)/invalid/$',
views.PlayerInvalidScore.as_view(), name="player-invalid-score"),
url(r'player/(?P<username>[\-\.\d\w]+)/kyu/$',
views.PlayerKyuScore.as_view(), name="player-kyu-score"),
url(r'player/(?P<username>[\-\.\d\w]+)/ladder/$',
views.PlayerLadderScore.as_view(), name="player-ladder-score"),
)

View File

@@ -0,0 +1,364 @@
# -*- encoding: utf-8 -*-
import urllib
from django.contrib import auth, messages
from django.core.urlresolvers import reverse
import django.forms
import django.http
from django.utils.translation import gettext as _
from django.views import generic
import xlwt
from events.models import Event
from events.views import EventDetailMixin
from . import forms, models
from membership.models import Membership
from utils.mixins import LoginRequiredMixin, PermissionRequiredMixin
kyu_dan_order = {
'+full_name': ('user__last_name', 'user__first_name'),
'-full_name': ('-user__last_name', '-user__first_name'),
'+hanchan_count': ('hanchan_count',),
'-hanchan_count': ('-hanchan_count',),
'+rank': ('-kyu', 'dan'),
'-rank': ('-dan', 'kyu'),
'+score': ('dan_points', 'kyu_points'),
'-score': ('-dan_points', '-kyu_points'),
'+username': ('user__username',),
'-username': ('-user__username',)
}
class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin, generic.DeleteView):
"""
Fragt zuerst nach, ob die Hanchan wirklich gelöscht werden soll.
Wir die Frage mit "Ja" beantwortet, wird die die Hanchan gelöscht.
"""
form_class = forms.HanchanForm
model = models.Hanchan
permission_required = 'mahjong_ranking.delete_hanchan'
pk_url_kwarg = 'hanchan'
def get_success_url(self):
return reverse('event-hanchan-list',
kwargs={'event': self.object.event.pk})
class HanchanForm(EventDetailMixin, PermissionRequiredMixin, generic.UpdateView):
"""
Ein Formular um eine neue Hanchan anzulegen, bzw. eine bestehende zu
bearbeitsen
"""
form_class = forms.HanchanForm
model = models.Hanchan
permission_required = 'mahjong_ranking.add_hanchan'
def form_valid(self, form, formset):
if not self.object.pk:
self.object = form.save()
formset.save()
self.object.save()
else:
formset.save()
self.object = form.save()
return django.http.HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(
form=form,
formset=formset,
object=self.object,
))
def get_form_class(self):
"""
Returns the form class to use in this view
"""
if self.request.user.has_perm('mahjong_ranking.delete_hanchan'):
return forms.HanchanAdminForm
else:
return forms.HanchanForm
def get_formset(self):
return forms.PlayerFormSet(
data=self.request.POST or None,
instance=self.object
)
def get_object(self, queryset=None):
if self.kwargs.get('hanchan'):
hanchan = models.Hanchan.objects.get(id=self.kwargs['hanchan'])
self.event = hanchan.event
elif self.kwargs.get('event'):
self.event = models.Event.objects.get(id=self.kwargs['event'])
hanchan = models.Hanchan(
event=self.event,
start=self.event.start
)
else:
hanchan = self.model()
if hanchan.id and not hanchan.valid:
messages.warning(self.request, hanchan.check_validity())
return hanchan
def get_context_data(self, **kwargs):
context = generic.UpdateView.get_context_data(self, **kwargs)
context['event'] = self.event
return context
def get(self, request, *args, **kwargs):
"""
:param request:
"""
self.object = self.get_object()
form = self.get_form(self.get_form_class())
formset = self.get_formset()
return self.render_to_response(
self.get_context_data(form=form, formset=formset)
)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = self.get_formset()
if form.is_valid() and formset.is_valid():
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
class EventHanchanList(EventDetailMixin, generic.ListView):
"""
Auflistung aller Hanchan die während der Veranstaltung gespielt wurden.
"""
model = models.Hanchan
def get_queryset(self):
try:
self.event = models.Event.objects.get(pk=self.kwargs['event'])
queryset = models.Hanchan.objects.filter(event=self.event)
queryset = queryset.prefetch_related('player_set__user')
queryset = queryset.order_by('start')
return queryset
except models.Event.DoesNotExist:
raise django.http.Http404(_('Event does not exist'))
class EventRankingList(EventDetailMixin, generic.ListView):
"""
Anzeige des Eventrankings, daß erstellt wurde falls der Termin als internes
Turnier markiert wurde.
"""
model = models.EventRanking
def get_queryset(self):
try:
self.event = models.Event.objects.get(pk=self.kwargs['event'])
queryset = models.EventRanking.objects.filter(event=self.event)
queryset = queryset.prefetch_related()
return queryset
except models.Event.DoesNotExist:
raise django.http.Http404(_('Event does not exist'))
class KyuDanRankingList(generic.ListView):
"""
Anzeige aller Spiele mit ihrem Kyu bzw Dan Grad.
"""
default_order = '-score'
order_by = ''
paginate_by = 25
def dispatch(self, request, *args, **kwargs):
self.order_by = kyu_dan_order[
kwargs.get('order_by', self.default_order)] # @IgnorePep8
return generic.ListView.dispatch(self, request, *args, **kwargs)
def get_queryset(self):
queryset = models.KyuDanRanking.objects.all()
queryset = queryset.select_related('user__membership')
queryset = queryset.order_by(*self.order_by)
return queryset
class LadderRankingList(generic.ListView):
model = models.LadderRanking
paginate_by = 25
season = None
is_archive = False
def get_queryset(self):
try:
if self.kwargs.get('season'):
self.season = models.LadderSeason.objects.get(
pk=self.kwargs['season'])
self.is_archive = True
elif self.kwargs.get('is_archive'):
self.season = models.LadderSeason.objects.order_by('-pk')[1]
self.is_archive = True
else:
self.season = models.LadderSeason.objects.current()
except models.LadderSeason.DoesNotExist:
raise django.http.Http404(_('Season does not exist'))
queryset = models.LadderRanking.objects.filter(season=self.season,
placement__isnull=False).select_related()
return queryset
def get_context_data(self, **kwargs):
context = generic.ListView.get_context_data(self, **kwargs)
context['is_archive'] = self.is_archive
context['season'] = self.season
context['season_list'] = models.LadderSeason.objects.all()
context['latest_hanchan_list'] = models.Hanchan.objects.filter(
valid=True)[:3]
context['latest_event_list'] = Event.objects.archive()[:3]
return context
class LadderRankingExcel(generic.View):
center = xlwt.easyxf('align: horiz centre')
# Format für Datumswerte
date = xlwt.easyxf('align: horiz right', num_format_str='d.MM.YYYY')
# Format für Überschriften
header = xlwt.easyxf('font: bold on; align: wrap on, vert centre, \
horiz center; pattern: back_color grey25;')
filename = u"ladderranking.xls"
def get(self, request, **kwargs):
self.queryset = self.team.members.all()
response = django.http.HttpResponse(mimetype=u'application/msexcel')
filename = urllib.quote(self.filename.encode('utf-8'))
response[
'Content-Disposition'] = "attachment; filename*=UTF-8''%s" % filename
field_names = [u"Login", u"Name", u"E-Mail", u"Telefon", u"Handy",
u"Geburtstag",
u"T-Shirt", u"Teams", u"Gr.", u"Kg", u"Notfall Adresse",
u"Notfall Nummer", u"Notizen", ]
# Erstelle ein Workbook (Das ist eine Excel Datei)
workbook = xlwt.Workbook(encoding='utf-8')
# Das Sheet ist eine Tabelle in der Excel Datei.
# Dort wird die erste Spalte fixiert und mit den Zeilenüberschriften befüllt.
sheet = workbook.add_sheet('Mitglieder', cell_overwrite_ok=False)
sheet.set_panes_frozen(True)
sheet.set_horz_split_pos(1) # in general, freeze after last heading row
sheet.set_remove_splits(
True) # if user does unfreeze, don't leave a split there
for column in range(0, len(field_names)):
sheet.write(0, column, field_names[column], style=header)
# Dann fangen wir an ab der 2. Zeile die einzelnen Datensätze einzuspielen.
current_row = 2
for user in self.queryset:
self.set_col(sheet, current_row, 0, user.username)
self.set_col(sheet, current_row, 1, user.get_full_name())
self.set_col(sheet, current_row, 2, user.email)
try:
profile = user.get_profile()
self.set_col(sheet, current_row, 3, profile.telephone or None)
self.set_col(sheet, current_row, 4, profile.mobilephone or None)
self.set_col(sheet, current_row, 5, profile.birthday or None,
style=self.date)
self.set_col(sheet, current_row, 6, profile.shirt_size or None,
style=self.center)
self.set_col(sheet, current_row, 7, self.get_other_teams(user))
self.set_col(sheet, current_row, 8, profile.height or None)
self.set_col(sheet, current_row, 9, profile.weight or None)
self.set_col(sheet, current_row, 10,
profile.emergency_contact or None)
self.set_col(sheet, current_row, 11,
profile.emergency_phone or None)
except Membership.DoesNotExist:
pass
current_row += 1
for column in range(0, 13):
sheet.col(column).width = (self.max_colwidth[column] + 1) * 256
workbook.save(response)
return response
class PlayerScore(LoginRequiredMixin, generic.ListView):
paginate_by = 25
def get(self, request, *args, **kwargs):
try:
user_model = auth.get_user_model()
self.user = user_model.objects.get(
username=self.kwargs.get('username'))
self.membership = Membership.objects.get(user=self.user)
except user_model.DoesNotExist:
raise django.http.Http404(
_("No user found matching the name %s") % self.kwargs.get(
'username'))
return generic.ListView.get(self, request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = generic.ListView.get_context_data(self, **kwargs)
context['membership'] = self.membership
try:
context['kyu_dan_ranking'] = models.KyuDanRanking.objects.get(
user=self.user)
except models.KyuDanRanking.DoesNotExist:
context['ranking'] = None
try:
context['ladder_ranking'] = models.LadderRanking.objects.get(
user=self.user,
season=models.LadderSeason.objects.current())
except models.LadderRanking.DoesNotExist:
context['ladder_ranking'] = models.LadderRanking(user=self.user)
return context
class PlayerDanScore(PlayerScore):
template_name = 'mahjong_ranking/player_dan_score.html'
def get_queryset(self):
return models.Player.objects.dan_hanchans(self.user)
class PlayerInvalidScore(PlayerScore):
template_name = 'mahjong_ranking/player_invalid_score.html'
def get_queryset(self):
return models.Player.objects.non_counting_hanchans(self.user)
class PlayerKyuScore(PlayerScore):
template_name = 'mahjong_ranking/player_kyu_score.html'
def get_queryset(self):
return models.Player.objects.kyu_hanchans(self.user)
class PlayerLadderScore(PlayerScore):
template_name = 'mahjong_ranking/player_ladder_score.html'
def get_context_data(self, **kwargs):
context = PlayerScore.get_context_data(self, **kwargs)
season_list = models.LadderRanking.objects.filter(
user=self.user).select_related('user')
season_list = season_list.values_list('id', 'season__name')
context['season'] = self.season
context['seasons_select_form'] = forms.SeasonSelectForm(user=self.user)
context['seasons_select_field'] = django.forms.ChoiceField(
choices=season_list)
return context
def get_queryset(self, **kwargs):
if self.request.GET.get('season'):
self.season = models.LadderSeason.objects.get(
pk=self.request.GET['season'])
else:
self.season = models.LadderSeason.objects.current()
return models.Player.objects.ladder_hanchans(user=self.user,
season=self.season)