Source code for vulyk.blueprints.gamification.services

# -*- coding: utf-8 -*-
"""
Services module
"""
from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Optional

from vulyk.blueprints.gamification.core.events import DonateEvent
from vulyk.blueprints.gamification.core.state import UserState
from vulyk.blueprints.gamification.models.events import EventModel
from vulyk.blueprints.gamification.models.foundations import FundModel
from vulyk.blueprints.gamification.models.state import UserStateModel
from vulyk.ext.worksession import WorkSessionManager
from vulyk.models.tasks import AbstractTask
from vulyk.models.user import User

__all__ = [
    'DonationResult',
    'DonationsService',
    'StatsService'
]


[docs]class DonationResult(Enum): """ An enumeration to represent different results of donation process. """ SUCCESS = 0 STINGY = 1 BEGGAR = 2 LIAR = 3 ERROR = 666
[docs]class DonationsService: """ Class hides mildly complex donation logic from cruel outer world. """ def __init__(self, user: User, fund_id: str, amount: Decimal) -> None: """ Constructor. :param user: Current user :type user: User """ self._amount = amount self._user = user self._fund = FundModel.find_by_id(fund_id)
[docs] def donate(self) -> DonationResult: """ Perform a donation process: - check if there is enough active money to spare - check if fund exists - try to decrease amount of coins on current account - create and save an event :return: One of `DonationResult` enum values: SUCCESS - everything went okay; STINGY - you tried to donate nothing; BEGGAR - you have less money than tried to spare; LIAR - you passed non-existent fund; ERROR - sh*t happened :( . :rtype: DonationResult """ if self._amount <= 0: return DonationResult.STINGY if self._fund is None: return DonationResult.LIAR try: if UserStateModel.withdraw(user=self._user, amount=self._amount): EventModel.from_event( DonateEvent( timestamp=datetime.now(), user=self._user, coins=-self._amount, acceptor_fund=self._fund) ).save() return DonationResult.SUCCESS else: return DonationResult.BEGGAR except (Exception, IOError): return DonationResult.ERROR
[docs]class StatsService: """ Facade, the root stats collector to provide aggregated data from different repositories. """
[docs] @classmethod def tasks_done_by_user(cls, user: User) -> int: """ Returns optional of the total number of tasks were finished by user. :param user: Current user :type user: User :return: Number of tasks done or None :rtype: int """ return EventModel.count_of_tasks_done_by_user(user)
[docs] @classmethod def projects_count(cls, user: User) -> int: """ Aggregate the number of batches in which user has done at least single tiny task. :param user: Current user :type user: User :return: Number of batches :rtype: int """ return len(list(EventModel.batches_user_worked_on(user)))
[docs] @classmethod def total_time_for_user(cls, user: User) -> int: """ Count and return number of hours, spent on the site doing tasks. :param user: Current user :type user: User :return: Full hours :rtype: int """ from vulyk.app import TASKS_TYPES seconds = 0 for task_type in TASKS_TYPES.values(): ws = task_type.work_session_manager # type: WorkSessionManager # TODO: must be changed after time tracking on frontend is done seconds += ws.work_session.get_total_user_time_approximate(user.id) return seconds // 3600
[docs] @classmethod def total_number_of_open_tasks(cls) -> int: """ Count and return number of open tasks in all projects :return: Number of open tasks :rtype: int """ return AbstractTask.objects.filter(closed=False).count()
[docs] @classmethod def total_number_of_users(cls) -> int: """ Count and return number of users registered in the system :return: Number of active users :rtype: int """ return User.objects.filter(active=True).count()
[docs] @classmethod def total_money_donated(cls) -> float: """ Count and return total amount of money donated by all users to all foundations :return: Total amount in UAH :rtype: float """ return EventModel.amount_of_money_donated(None)
[docs] @classmethod def total_money_donated_by_user(cls, user: User) -> float: """ Count and return total amount of money donated by current user :return: Total amount in UAH :rtype: float """ return EventModel.amount_of_money_donated(user)
[docs] @classmethod def total_money_earned(cls) -> float: """ Count and return total amount of money earned by all users on all tasks :return: Total amount in UAH :rtype: float """ return EventModel.amount_of_money_earned(None)
[docs] @classmethod def state_of_user(cls, user: User) -> Optional[UserState]: """ Return current state of given user :return: Object which holds aggregated values on user current state for the registered user and None otherwise :rtype: Optional[UserState] """ if user.is_anonymous: return None return UserStateModel.get_or_create_by_user(user)