Source code for vulyk.models.user
# -*- coding: utf-8 -*-
"""Module contains all models related to member entity."""
import datetime
from itertools import chain
from typing import Dict, Optional, Type
from flask_login import AnonymousUserMixin, UserMixin
from flask_mongoengine import Document
from mongoengine import (PULL, BooleanField, DateTimeField, IntField,
ListField, ReferenceField, StringField,
ValidationError, signals)
[docs]class Group(Document):
"""
Class was introduced to serve the permissions purpose
"""
id = StringField(max_length=100, primary_key=True)
description = StringField(max_length=200)
allowed_types = ListField(StringField(max_length=100))
meta = {'collection': 'groups'}
def __str__(self) -> str:
return str(self.id)
def __repr__(self) -> str:
return 'Group ID: {id}. Allowed types: {types}'.format(
id=self.id,
types=self.allowed_types)
[docs]class User(Document, UserMixin):
"""
Main model for member entity.
"""
username = StringField(max_length=200)
password = StringField(max_length=200, default='')
name = StringField(max_length=100)
email = StringField()
active = BooleanField(default=True)
admin = BooleanField(default=False)
groups = ListField(
ReferenceField(Group, reverse_delete_rule=PULL, default=None))
last_login = DateTimeField(default=datetime.datetime.now)
processed = IntField(default=0)
[docs] def is_active(self) -> bool:
return self.active
[docs] def is_admin(self) -> bool:
return self.admin or False
[docs] def is_eligible_for(self, task_type: str) -> bool:
"""
Check that user is authorized to work with this tasks type
:param task_type: Tasks type name
:type task_type: str
:return: True if user is eligible
:rtype: bool
:raises: AssertionError - if no `task_type` specified
"""
assert task_type, 'Empty parameter `task_type` passed'
return self.admin \
or task_type in chain(*(g.allowed_types for g in self.groups))
[docs] def get_stats(self, task_type) -> Dict[str, int]:
"""
Returns member's stats containing the number of tasks finished and
the position in the global rank.
:param task_type: Task type instance.
:type task_type: vulyk.models.task_types.AbstractTaskType
:return: Dictionary that contains total finished tasks count and the
position in the global rank.
:rtype: Dict[str, int]
"""
leaders = task_type.get_leaders()
i = 0
prev_val = -1
total = 0
for user, freq in leaders:
if freq != prev_val:
i += 1
prev_val = freq
if user == self.id:
total = freq
break
return {
'total': total,
'position': i
}
[docs] def as_dict(self) -> Dict[str, str]:
"""
Converts the model-instance into a safe dict that will include some
basic info about member.
:return: Reduced set of information about member.
:rtype: Dict[str, str]
"""
return {
'username': self.username,
'email': self.email
}
[docs] @classmethod
def pre_save(cls, sender: Type, document: Document, **kwargs: Dict) -> Document:
"""
A signal handler which will put a new member into a default group if
any hasn't been assigned yet.
:param sender: Type of signal emitter.
:type sender: Type
:param document: New instance of User model.
:type document: User
:param kwargs: Additional parameters
:type kwargs: Dict
:return: Modified User instance.
:rtype: User
"""
if all(map(lambda x: x.id != 'default', document.groups)):
try:
document.groups = [Group.objects.get(id='default')]
except Group.DoesNotExist:
raise Group.DoesNotExist('Please run \'manage.py init ...\'')
return document
[docs] @classmethod
def get_by_id(cls, user_id: str) -> Optional[Document]:
"""
:param user_id: Needed user ID
:type user_id: str
:return: The user
:rtype: Optional[User]
"""
try:
return cls.objects.get(id=user_id)
except (cls.DoesNotExist, ValidationError):
return None
def __str__(self) -> str:
return self.username
[docs]class Anonymous(AnonymousUserMixin):
name = 'Anonymous'
signals.pre_save.connect(User.pre_save, sender=User)