rosedu/wouso

View on GitHub
wouso/interface/top/models.py

Summary

Maintainability
D
1 day
Test Coverage
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)