Anfänglicher Commit: Producion Version Stand: Oktober 2014
This commit is contained in:
53
src/mahjong_ranking/__init__.py
Normal file
53
src/mahjong_ranking/__init__.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# -*- 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'
|
||||
|
||||
recalculation_queue = cache.get(queue_name, set())
|
||||
recalculation_queue.add(key_to_add)
|
||||
cache.set(queue_name, recalculation_queue, 360)
|
||||
91
src/mahjong_ranking/admin.py
Normal file
91
src/mahjong_ranking/admin.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
'''
|
||||
Created on 19.09.2011
|
||||
|
||||
@author: christian
|
||||
'''
|
||||
# import stuff we need from django
|
||||
from django.contrib import admin
|
||||
from . import models, set_dirty
|
||||
from forms import PlayerFormSet
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
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',)
|
||||
list_filter = ('event',)
|
||||
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):
|
||||
print "Custom save_formset"
|
||||
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)
|
||||
112
src/mahjong_ranking/forms.py
Normal file
112
src/mahjong_ranking/forms.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
'''
|
||||
Created on 04.10.2011
|
||||
|
||||
@author: christian
|
||||
'''
|
||||
from django.contrib.auth.models import User
|
||||
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 = User.objects.filter(groups__in=(1, 2)).distinct()
|
||||
player_choices = player_choices.order_by('username')
|
||||
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
|
||||
)
|
||||
1
src/mahjong_ranking/management/__init__.py
Normal file
1
src/mahjong_ranking/management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
#!/usr/bin/python
|
||||
1
src/mahjong_ranking/management/commands/__init__.py
Normal file
1
src/mahjong_ranking/management/commands/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
#!/usr/bin/python
|
||||
94
src/mahjong_ranking/management/commands/random-ranking.py
Normal file
94
src/mahjong_ranking/management/commands/random-ranking.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Generate Randum Mahjong Hanchans to the the Raning System
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from events.models import Event
|
||||
from mahjong_ranking import models
|
||||
import random
|
||||
from datetime import timedelta
|
||||
|
||||
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(User.objects.all())
|
||||
|
||||
for event in Event.objects.all():
|
||||
for i in range(random.randrange(2,8)):
|
||||
self.create_hanchan(event)
|
||||
|
||||
|
||||
83
src/mahjong_ranking/middleware.py
Normal file
83
src/mahjong_ranking/middleware.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# -*- 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.
|
||||
'''
|
||||
|
||||
def process_response(self, request, response):
|
||||
if request.method != 'POST':
|
||||
# We only do this in POST request, to speedup the responsetime.
|
||||
return response
|
||||
|
||||
# Start an SQL Transaction
|
||||
transaction.enter_transaction_management()
|
||||
transaction.managed(True)
|
||||
|
||||
# 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)
|
||||
transaction.commit()
|
||||
|
||||
# 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)
|
||||
transaction.commit()
|
||||
|
||||
# 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; User %i - existiert nicht!', season_id, user_id)
|
||||
cache.set('ladder_ranking_queue', ladder_ranking_queue, 360)
|
||||
transaction.commit()
|
||||
|
||||
# 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)
|
||||
transaction.commit()
|
||||
|
||||
transaction.leave_transaction_management()
|
||||
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)
|
||||
602
src/mahjong_ranking/models.py
Normal file
602
src/mahjong_ranking/models.py
Normal file
@@ -0,0 +1,602 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from datetime import date, timedelta
|
||||
from django.contrib.auth.models import User
|
||||
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(User)
|
||||
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(User, through='Player',verbose_name=_('Players')) # @IgnorePep8
|
||||
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.
|
||||
'''
|
||||
logger.debug("Hanchan clean() wurde getriggert!")
|
||||
|
||||
# if self.pk and self.player_set.distinct().count() != 4:
|
||||
# raise ValidationError(
|
||||
# _('For a Hanchan exactly 4 players are needed.'))
|
||||
if self.start and 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(User)
|
||||
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(User)
|
||||
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 recalculate(self):
|
||||
logger.info(u'Recalculate LadderSeason %s', self.name)
|
||||
self.ladderranking_set.update(placement=None)
|
||||
placement = 1
|
||||
ladder_rankings = self.ladderranking_set.filter(hanchan_count__gt=MIN_HANCHANS_FOR_LADDER) # @IgnorePep8
|
||||
ladder_rankings = ladder_rankings.order_by('avg_placement', '-avg_score') # @IgnorePep8
|
||||
for ranking in ladder_rankings:
|
||||
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(User)
|
||||
score = models.PositiveIntegerField(default=0)
|
||||
placement = models.PositiveSmallIntegerField(blank=True, null=True, default=None) # @IgnorePep8
|
||||
kyu_points = models.PositiveSmallIntegerField(blank=True, null=True, default=None) # @IgnorePep8
|
||||
dan_points = models.PositiveSmallIntegerField(blank=True, null=True, default=None) # @IgnorePep8
|
||||
bonus_points = models.PositiveSmallIntegerField(blank=True, null=True, default=0) # @IgnorePep8
|
||||
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)
|
||||
16
src/mahjong_ranking/tests.py
Normal file
16
src/mahjong_ranking/tests.py
Normal 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)
|
||||
55
src/mahjong_ranking/urls.py
Normal file
55
src/mahjong_ranking/urls.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
'''
|
||||
Created on 03.10.2011
|
||||
|
||||
@author: christian
|
||||
'''
|
||||
from django.conf.urls import * # @UnusedWildImport
|
||||
import views
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$',
|
||||
views.LadderRankingList.as_view(),
|
||||
name="mahjong-ladder"),
|
||||
url(r'archive/$',
|
||||
views.LadderRankingList.as_view(),
|
||||
kwargs={'is_archive': True},
|
||||
name="mahjong-ladder-archive"),
|
||||
url(r'archive/(?P<season>[\d]+)/$',
|
||||
views.LadderRankingList.as_view(),
|
||||
name="mahjong-ladder-archive"),
|
||||
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'event/(?P<event>[\d]+)/$',
|
||||
views.EventHanchanList.as_view(),
|
||||
name="event-hanchan-list"),
|
||||
url(r'event/(?P<event>[\d]+)/ranking/$',
|
||||
views.EventRankingList.as_view(),
|
||||
name="event-ranking"),
|
||||
url(r'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'dan_score/(?P<username>[\-\.\d\w]+)/$',
|
||||
views.PlayerDanScore.as_view(),
|
||||
name="player-dan-score"),
|
||||
url(r'invalid_score/(?P<username>[\-\.\d\w]+)/$',
|
||||
views.PlayerInvalidScore.as_view(),
|
||||
name="player-invalid-score"),
|
||||
url(r'kyu_score/(?P<username>[\-\.\d\w]+)/$',
|
||||
views.PlayerKyuScore.as_view(),
|
||||
name="player-kyu-score"),
|
||||
url(r'ladder_score/(?P<username>[\-\.\d\w]+)/$',
|
||||
views.PlayerLadderScore.as_view(),
|
||||
name="player-ladder-score"),
|
||||
)
|
||||
360
src/mahjong_ranking/views.py
Normal file
360
src/mahjong_ranking/views.py
Normal file
@@ -0,0 +1,360 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from django.contrib import auth, messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect, Http404
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views import generic
|
||||
from events.models import Event
|
||||
from mahjong_ranking import forms, models
|
||||
from utils.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
import django.forms
|
||||
import xlwt
|
||||
from django import http
|
||||
import urllib
|
||||
from membership.models import Membership
|
||||
|
||||
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(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(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 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()
|
||||
print form.is_valid()
|
||||
print formset.is_valid()
|
||||
if form.is_valid() and formset.is_valid():
|
||||
return self.form_valid(form, formset)
|
||||
else:
|
||||
return self.form_invalid(form, formset)
|
||||
|
||||
|
||||
class EventHanchanList(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 Http404(_('Event does not exist'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = generic.ListView.get_context_data(self, **kwargs)
|
||||
context['event'] = self.event
|
||||
return context
|
||||
|
||||
|
||||
class EventRankingList(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 Http404(_('Event does not exist'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = generic.ListView.get_context_data(self, **kwargs)
|
||||
context['event'] = self.event
|
||||
return context
|
||||
|
||||
|
||||
class KyuDanRankingList(generic.ListView):
|
||||
'''
|
||||
Anzeige aller Spiele mit ihrem Kyu bzw Dan Grad.
|
||||
'''
|
||||
models.KyuDanRanking
|
||||
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)
|
||||
print self.order_by
|
||||
# print queryset.query
|
||||
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 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_archive'] = models.LadderSeason.objects.all()
|
||||
context['latest_hanchan_list'] = models.Hanchan.objects.filter(valid=True)[:5]
|
||||
context['latest_event_list'] = Event.objects.archive()[:5]
|
||||
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 = 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 models.MemberProfile.DoesNotExist:
|
||||
pass
|
||||
current_row += 1
|
||||
|
||||
for column in range(0, 13):
|
||||
print column, self.max_colwidth[column]
|
||||
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:
|
||||
self.user = auth.models.User.objects.get(username=self.kwargs.get('username'))
|
||||
self.membership = Membership.objects.get_or_create(user=self.user)[0]
|
||||
except auth.models.User.DoesNotExist:
|
||||
raise 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:
|
||||
context['ranking'] = None
|
||||
try:
|
||||
context['ladder_ranking'] = models.LadderRanking.objects.get(
|
||||
user=self.user,
|
||||
season=models.LadderSeason.objects.current())
|
||||
except:
|
||||
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)
|
||||
Reference in New Issue
Block a user