wouso/interface/top/models.py
from django.db.models import Sum
import logging
import sys
from datetime import datetime, timedelta
from django.db import models
from django.template.loader import render_to_string
from wouso.core.common import App
from wouso.core.config.models import BoolSetting, Setting
from wouso.core.scoring import Coin
from wouso.core.ui import register_sidebar_block
from wouso.core.user.models import Player, PlayerGroup, Race
from wouso.games.challenge.models import ChallengeUser
class ObjectHistory:
@property
def disabled(self):
return Top.disabled()
class GroupHistory(ObjectHistory):
def __init__(self, group):
self.group = group
@property
def position(self):
return History.get_group_position(self.group)
@property
def position_in_parent(self):
if not self.group.parent:
return self.position
return History.get_group_position(self.group, relative_to=self.group.parent)
def week_evolution(self, relative_to=None):
""" :return: list of pairs (index, position) for the last week """
hs = History.objects.filter(group=self.group, relative_to=relative_to).order_by('-date')[:7]
tot = len(hs)
return [(tot - i, h.position) for (i, h) in enumerate(hs)]
class TopUser(ObjectHistory, Player):
_history = None
@property
def progress(self):
""" Return position difference between last two recorded positions. """
try:
yesterday = History.objects.filter(user=self, relative_to=None).order_by('-date')[0]
daybefore = History.objects.filter(user=self, relative_to=None).order_by('-date')[1]
except Exception as e:
return 0
return - (yesterday.position - daybefore.position)
@property
def played_challenges(self):
return self.get_extension(ChallengeUser).get_all_challenges().count()
@property
def won_challenges(self):
return self.get_extension(ChallengeUser).get_won_challenges().count()
@property
def lost_challenges(self):
return self.get_extension(ChallengeUser).get_lost_challenges().count()
@property
def draw_challenges(self):
return self.get_extension(ChallengeUser).get_draw_challenges().count()
@property
def win_percentage(self):
return self.get_extension(ChallengeUser).get_win_percentage()
@property
def weeklyprogress(self):
hs = self.history()
try:
yesterday = hs[0]
day1weekprior = hs[-1]
except Exception as e:
logging.exception(e)
return 0
return -(yesterday.position - day1weekprior.position)
@property
def position(self):
try:
return self.history()[0].position
except IndexError:
return 0
@property
def coin_position(self):
"""
Return the position in custom coin top, if any, as a dictionary.
Example response:
{'karma': 1}, {'gold': 10} # first in karma, 10th in gold, as of today
"""
ret = {}
for c in Top.coin_top_settings():
ret[c] = Top.get_coin_position(c, self)
return ret
def history(self):
"""
History queryset, attempt to reuse it
"""
if self._history is not None:
return self._history
self._history = list(History.objects.filter(user=self, relative_to=None).order_by('-date')[:7])
return self._history
def week_evolution(self, relative_to=None):
""" :return: list of pairs (index, position) for the last week """
if relative_to is None:
hs = self.history()
else:
hs = self.history().filter(relative_to=relative_to).order_by('-date')[:7]
tot = len(hs)
return [(tot - i, h.position) for (i, h) in enumerate(hs)]
def week_points_evolution(self):
""" :return: list of pairs (index, points) for the last week """
hs = self.history()
tot = len(hs)
return [(tot - i, h.points) for (i, h) in enumerate(hs)]
Player.register_extension('top', TopUser)
class NewHistory(models.Model):
TYPES = (('u', 'user'), ('r', 'race'), ('g', 'group'), ('c', 'coin'))
object = models.IntegerField(help_text='Object id, user, race, group')
object_type = models.CharField(max_length=1, choices=TYPES, default='u')
relative_to = models.IntegerField(help_text='Relative to id, race or group', blank=True, null=True)
relative_to_type = models.CharField(max_length=1, choices=TYPES, default=None, blank=True, null=True)
position = models.IntegerField(default=0)
points = models.FloatField(default=0)
date = models.DateField()
@classmethod
def record(cls, obj, date, relative_to=None):
relative_to_id = relative_to.id if relative_to else None
return cls.objects.get_or_create(object=obj.id,
object_type=cls._get_type(obj),
date=date,
relative_to=relative_to_id,
relative_to_type=cls._get_type(relative_to))[0]
@classmethod
def get_obj_position(cls, obj, relative_to=None):
"""
Return the latest position computed for this object (user, race, group)
"""
history = cls.objects.filter(object=obj.id, object_type=cls._get_type(obj),
relative_to=relative_to.id, relative_to_type=cls._get_type(relative_to)).order_by('-date')
if not history.count():
return 0
return history[0].position
@classmethod
def get_children_top(cls, obj, type):
date = datetime.today().date()
return type.objects.filter(id__in=cls.objects.filter(object_type=cls._get_type(type),
date=date, relative_to=obj.id,
relative_to_type=cls._get_type(obj)).order_by('position').values_list('object'))
@classmethod
def get_user_position(cls, player, relative_to=None):
"""
Return the latest position computed for this user
"""
return cls.get_obj_position(player, relative_to)
@classmethod
def get_group_position(cls, group, relative_to=None):
"""
Return the latest position of this group
"""
return cls.get_obj_position(group, relative_to)
@classmethod
def get_coin_top(cls, coin):
try:
last_day = NewHistory.objects.filter(relative_to_type='c', relative_to=coin.id).order_by('-date')[0]
except IndexError:
return NewHistory.objects.none()
return NewHistory.objects.filter(date=last_day.date, relative_to_type='c', relative_to=coin.id).order_by('position')
@classmethod
def _get_type(cls, object):
if isinstance(object, Player) or isinstance(object, TopUser) or object in (Player, TopUser):
return 'u'
if isinstance(object, Race) or object is Race:
return 'r'
if isinstance(object, PlayerGroup) or object is PlayerGroup:
return 'g'
if isinstance(object, Coin):
return 'c'
return None
class History(models.Model): # TODO: deprecate (maybe), check if NewHistory covers usage
user = models.ForeignKey('TopUser', blank=True, null=True)
group = models.ForeignKey(PlayerGroup, blank=True, null=True)
relative_to = models.ForeignKey(PlayerGroup, blank=True, null=True, related_name='relativeto')
position = models.IntegerField(default=0)
points = models.FloatField(default=0)
date = models.DateField()
@classmethod
def get_user_position(kls, user, relative_to=None):
try:
history = History.objects.filter(user=user, relative_to=relative_to).order_by('-date')[0]
return history.position
except IndexError:
return 0
except History.DoesNotExist:
return 0
@classmethod
def get_group_position(kls, group, relative_to=None):
try:
history = History.objects.filter(group=group, relative_to=relative_to).order_by('-date')[0]
return history.position
except IndexError:
return 0
except History.DoesNotExist:
return 0
def __unicode__(self):
return u"%s %s at %s, position: %d, points: %f" % ('[%s]' % self.relative_to if self.relative_to else '', self.user if self.user else self.group, self.date, self.position, self.points)
class Top(App):
@classmethod
def get_sidebar_widget(kls, context):
if kls.disabled():
return ''
top5 = TopUser.objects.exclude(user__is_superuser=True).exclude(race__can_play=False)
top5 = top5.order_by('-points')[:10]
# is_top = request.get_full_path().startswith('/top/')
is_top = context.get('top', False)
return render_to_string('top/sidebar.html',
{'topusers': top5,
'is_top': is_top,
'top': Top,
'coin_top_setting': kls.coin_top_settings(),
'config_disable_challenge_top': BoolSetting.get('setting-challenge-top').get_value() is False
})
@classmethod
def management_task(cls, now=None, stdout=sys.stdout):
now = now if now is not None else datetime.now()
today = now.date()
# Global ladder
stdout.write(' Updating players...\n')
for i, u in enumerate(Player.objects.all().order_by('-points')):
topuser = u.get_extension(TopUser)
position = i + 1
hs, new = History.objects.get_or_create(user=topuser, date=today, relative_to=None)
hs.position, hs.points = position, u.points
hs.save()
stdout.write(' Updating group history...\n')
for p in PlayerGroup.objects.filter(owner=None):
p.points = p.live_points
p.save()
for r in Race.objects.all():
for i, g in enumerate(r.children.annotate(lpoints=Sum('players__points')).order_by('-lpoints')):
hs = NewHistory.record(g, today, relative_to=r)
hs.position, hs.points = i + 1, g.points
hs.save()
stdout.write(' Updating race history...\n')
race_data = [(race, race.points) for race in Race.objects.all() if race.can_play]
# sort by number of points, decreasing order
race_data = sorted(race_data, key=lambda r: r[1], reverse=True)
for i, rd in enumerate(race_data):
hs = NewHistory.record(rd[0], today, relative_to=None)
hs.position, hs.points = i + 1, rd[1]
hs.save()
# Check for coin tops
coin_tops = cls.coin_top_settings()
for c in coin_tops:
cls.coin_top(c, today, stdout=stdout)
# I don't think these are necessary, so I'm disabling them for now
return
# In group ladder
for pg in PlayerGroup.objects.filter(owner=None):
for i, u in enumerate(pg.players.all().order_by('-points')):
topuser = u.get_extension(TopUser)
hs, new = History.objects.get_or_create(user=topuser, date=today, relative_to=pg)
hs.position, hs.points = i + 1, u.points
hs.save()
# In race ladder
for pr in Race.objects.all():
for i, u in enumerate(pr.player_set.all().order_by('-points')):
hs = NewHistory.record(u, today, relative_to=pr)
hs.position, hs.points = i + 1, u.points
hs.save()
@classmethod
def coin_top(cls, coin, now, stdout=sys.stdout):
"""
Calculate and record a new top for a coin
"""
coin_obj = Coin.get(coin)
if not coin_obj:
stdout.write('No such coin %s' % coin)
return
stdout.write(' Calculating coin %s top...' % coin)
players = list(Player.objects.filter(race__can_play=True))
players.sort(lambda b, a: a.coins.get(coin) - b.coins.get(coin))
for i, p in enumerate(players):
hs = NewHistory.record(p, now, relative_to=coin_obj)
hs.position, hs.points = i + 1, p.coins.get(coin_obj.name)
hs.save()
stdout.write('\n')
@classmethod
def coin_top_settings(cls):
"""
Return a list of coin names for which we calculate the top
Example: ['gold', 'karma']
"""
return [c for c in Setting.get('setting-top-coins').get_value().split(',') if c] or []
@classmethod
def get_coin_position(cls, coin, user):
coin = Coin.get(coin)
return NewHistory.get_obj_position(user, relative_to=coin)
register_sidebar_block('top', Top.get_sidebar_widget)
# def user_post_save(sender, instance, **kwargs):
# profile = instance.get_profile()
# profile.get_extension(TopUser)
# models.signals.post_save.connect(user_post_save, User)