rosedu/wouso

View on GitHub
wouso/core/user/models.py

Summary

Maintainability
B
6 hrs
Test Coverage
# coding=utf-8
import logging
from md5 import md5
from datetime import datetime, timedelta
from random import shuffle
from django.db import models
from django.db.models import Sum, Q
from django.contrib.auth.models import User, Permission
from django.conf import settings
from wouso.core.decorators import cached_method, drop_cache
from wouso.core.game.models import Game
from wouso.core.magic.manager import MagicManager
from wouso.core.god import God
from wouso.core.magic.models import Spell
from .. import deprecated


class Race(models.Model):
    """ Groups a large set of players together and it's used extensively for 'can_play' checks.
    """
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=100, default='', blank=True)

    artifacts = models.ManyToManyField('magic.Artifact', blank=True, through='magic.RaceArtifactAmount')

    can_play = models.BooleanField(default=True, blank=True)

    logo = models.ImageField(upload_to=settings.MEDIA_ARTIFACTS_DIR, null=True, blank=True)

    @property
    def points(self):
        """ Sum of race members points
        """
        return self.player_set.aggregate(points=Sum('points'))['points'] or 0

    @property
    def children(self):
        return self.playergroup_set.all()

    @property
    def sisters(self):
        return Race.objects.filter(can_play=self.can_play).exclude(pk=self.pk)

    def __unicode__(self):
        return self.name if not self.title else self.title


class PlayerGroup(models.Model):
    """ Group players together """
    # The group owner. If null, it belongs to the core game.
    owner = models.ForeignKey(Game, blank=True, null=True)

    name = models.CharField(max_length=100)
    title = models.CharField(max_length=100, default='', blank=True)
    parent = models.ForeignKey('Race', default=None, null=True, blank=True)

    artifacts = models.ManyToManyField('magic.Artifact', blank=True, through='magic.GroupArtifactAmount')
    players = models.ManyToManyField('user.Player', blank=True)

    # used only for sorting and position
    points = models.FloatField(default=0, editable=False)

    @property
    def live_points(self):
        """ Calculate sum of user points dynamically """
        p = self.players.aggregate(total=models.Sum('points'))
        if p['total'] is not None:
            return int(p['total'])
        else:
            return 0

    @property
    @deprecated('Please get rid of me')
    def children(self):
        """ All groups with parent set to this group, cached """
        return []

    @property
    @deprecated('Please get rid of me')
    def sisters(self):
        """ All groups with the same parent as this group or of the same
        class, if parent is not set.
        """
        if self.parent:
            return list(self.parent.children.exclude(id=self.id))
        else:
            return []

    @property
    def online_players(self):
        oldest = datetime.now() - timedelta(minutes=10)

        res = self.players.filter(last_seen__gte=oldest)
        return res

    def destroy(self):
        """
        Delete the group and free its members
        """
        for p in self.members:
            p.group = None
            p.save()
        self.delete()

    def __unicode__(self):
        return self.name if self.title == '' else self.title


class Player(models.Model):
    """ Base class for the game user. This is extended by game specific
    player models.
    """
    user = models.ForeignKey(User, unique=True, related_name="%(class)s_related")

    full_name = models.CharField(max_length=200)
    # Unique differentiator for ladder
    # Do not modify it manually, use scoring.score instead
    points = models.FloatField(default=0, blank=True, null=True, editable=False)

    level_no = models.IntegerField(default=1, blank=True, null=True)

    # The maximum reached level by the user
    max_level = models.IntegerField(default=0, blank=False, null=False)

    last_seen = models.DateTimeField(null=True, blank=True)

    # artifacts available for using
    artifacts = models.ManyToManyField('magic.Artifact', blank=True, through='magic.PlayerArtifactAmount')
    # spells available for casting
    spells_collection = models.ManyToManyField(Spell, blank=True, through='magic.PlayerSpellAmount', related_name='spell_collection')
    nickname = models.CharField(max_length=20, null=True, blank=False, default="admin")
    # race
    race = models.ForeignKey(Race, blank=False, default=None, null=True)
    description = models.TextField(max_length=600, blank=True)

    EXTENSIONS = {}

    def get_neighbours_from_top(self, count, user_race=None, spell_type=None):
        """ Returns an array of neighbouring players from top: count up and count down
            user_race and spell_type are used by mass spells for neighbours list.
        """
        base_query = Player.objects.exclude(user__is_superuser=True).exclude(race__can_play=False)

        allUsers = list(base_query.order_by('-points'))
        try:
            pos = allUsers.index(self)
        except ValueError:
            return []

        if (spell_type is not None) and (user_race is not None) and (spell_type != 'o'):
            if spell_type == 'p':
                allUsers = [user for user in allUsers if user.race.name == user_race.name]
            else:
                allUsers = [user for user in allUsers if user.race.name != user_race.name]

        if len(allUsers) <= 2*count+1:
            return allUsers

        start = max(pos-count, 0)
        if pos + count >= len(allUsers):
            start = len(allUsers)-2*count-1

        players = allUsers[start:start+2*count+1]
        return players

    def get_division(self, count):
        from wouso.interface.top.models import TopUser
        all_users = list(TopUser.objects.all())

        try:
            curr_user = TopUser.objects.get(id=self.id)
        except Exception:
            return []

        curr_user_pos = curr_user.position

        division = [user for user in all_users if abs(curr_user.position - user.position) < 20]
        shuffle(division)

        return division

    def user_name(self):
        return self.user.username

    def in_staff_group(self):
        return self.user.has_perm('config.change_setting')

    @property
    def race_name(self):
        return self.race.name if self.race else ''

    # Magic manager
    @property
    def magic(self):
        return MagicManager(self)

    # Other stuff
    @property
    def level(self):
        """ Return an artifact object for the current level_no.
        Ask God about the right artifact object, given the player instance.
        In the future, God may check players race and give specific artifacts.
        """
        return God.get_user_level(self.level_no, player=self)

    @property
    def coins(self):
        # TODO check usage and deprecate this function
        from wouso.core.scoring.models import History
        return History.user_coins(self.user)

    @property
    def group(self):
        return self._group()

    @cached_method
    def _group(self):
        """ Return the core game group, if any
        """
        try:
            group = self.playergroup_set.filter(owner=None).get()
        except (PlayerGroup.DoesNotExist, PlayerGroup.MultipleObjectsReturned):
            group = None
        return group

    def set_group(self, group):
        """
         Set the core group, which is unique
        """
        for g in self.playergroup_set.filter(owner=None):
            g.players.remove(self)

        group.players.add(self)
        drop_cache(self._group, self)
        return group

    def level_progress(self):
        """ Return a dictionary with: points_gained, points_left, next_level """
        return God.get_level_progress(self)

    @property
    def avatar(self):
        return self._avatar()

    @cached_method
    def _avatar(self):
        avatar = "http://www.gravatar.com/avatar/%s.jpg?d=%s" % (md5(self.user.email).hexdigest(), settings.AVATAR_DEFAULT)
        return avatar

    # special:
    #@cached_method
    def get_extension(self, cls):
        if self.__class__ is cls:
            return self
        if cls == Player:
            return self.user.get_profile()
        if self.__class__ != Player:
            obj = self.user.get_profile()
        else:
            obj = self
        return obj._get_extension(cls)

    def _get_extension(self, cls):
        """ Search for an extension of this object, with the type cls
        Create instance if there isn't any.

        Using an workaround, while: http://code.djangoproject.com/ticket/7623 gets fixed.
        Also see: http://code.djangoproject.com/ticket/11618
        """
        try:
            extension = cls.objects.get(user=self.user)
        except cls.DoesNotExist:
            extension = cls(player_ptr=self)
            for f in self._meta.local_fields:
                setattr(extension, f.name, getattr(self, f.name))
            extension.save()
        return extension

    @classmethod
    def register_extension(cls, attr, ext_cls):
        """
        Register new attribute with an ext_cls
        """
        cls.EXTENSIONS[attr] = ext_cls

    @classmethod
    def get_quest_gods(cls):
        from wouso.core.scoring.models import History
        from wouso.games.quest.models import QuestGame, QuestResult

        def quest_points(user):
            return int(History.objects.filter(game=QuestGame.get_instance(),
                                              user=user).aggregate(points=Sum('amount'))['points'] or 0)

        users = list(cls.objects.exclude(race__can_play=False).filter(
            id__in=QuestResult.objects.values_list('user')))
        users.sort(lambda b, a: quest_points(a) - quest_points(b))
        gods = users[:10]
        return gods

    @classmethod
    def get_by_permission(cls, permission):
        perm = Permission.objects.get(codename=permission)
        users = User.objects.filter(Q(groups__permissions=perm) |
                                    Q(user_permissions=perm)).distinct()
        return Player.objects.filter(user__in=users)

    @property
    def race_name(self):
        return self._race_name()

    @cached_method
    def _race_name(self):
        if self.race:
            return self.race.name
        return ''

    def save(self, **kwargs):
        """ Clear cache for extensions
        """
        #for k, v in self.EXTENSIONS.iteritems():
        #    drop_cache(self.get_extension, self, v)
        #drop_cache(self.get_extension, self, self.__class__)
        drop_cache(self._race_name, self)
        drop_cache(self._group, self)
        update_display_name(self, save=False)
        return super(Player, self).save(**kwargs)

    def __getitem__(self, item):
        if item in self.__class__.EXTENSIONS:
            return self.get_extension(self.__class__.EXTENSIONS[item])
        return super(Player, self).__getitem__(item)

    def __unicode__(self):
        return self.full_name or self.user.__unicode__()


# Hack for having user and user's profile always in sync
def user_post_save(sender, instance, **kwargs):
    profile, new = Player.objects.get_or_create(user=instance)
    if new:
        # add in default group
        from wouso.core.config.models import ChoicesSetting
        try:
            default_group = PlayerGroup.objects.get(pk=int(ChoicesSetting.get('default_group').get_value()))
        except (PlayerGroup.DoesNotExist, ValueError):
            pass
        else:
            default_group.players.add(profile)

        try:
            default_race = Race.objects.get(pk=int(ChoicesSetting.get('default_race').get_value()))
        except (Race.DoesNotExist, ValueError):
            pass
        else:
            profile.race = default_race
            profile.save()
        profile.nickname = profile.user.username
        profile.save()
    update_display_name(profile)

models.signals.post_save.connect(user_post_save, User)


def update_display_name(player, save=True):
    display_name = unicode(settings.DISPLAY_NAME).format(first_name=player.user.first_name,
                                                         last_name=player.user.last_name,
                                                         nickname=player.nickname).strip()
    player.full_name = display_name
    if save:
        player.save()