skillbox/server/users/models.py

272 lines
9.7 KiB
Python

import re
from datetime import datetime
import string
import random
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser, Permission
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils.translation import ugettext_lazy as _
from core.hep_client import HepClient, MYSKILLBOX_LICENSES
from users.managers import RoleManager, UserRoleManager, UserManager, LicenseManager
DEFAULT_SCHOOL_ID = 1
class User(AbstractUser):
last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True)
recent_modules = models.ManyToManyField('books.Module', related_name='+', through='books.RecentModule')
last_topic = models.ForeignKey('books.Topic', related_name='+', on_delete=models.SET_NULL, null=True)
avatar_url = models.CharField(max_length=254, blank=True, default='')
email = models.EmailField(_('email address'), unique=True)
hep_id = models.PositiveIntegerField(null=True, blank=False)
hep_group_id = models.PositiveIntegerField(null=True, blank=False)
license_expiry_date = models.DateField(blank=False, null=True, default=None)
onboarding_visited = models.BooleanField(default=False)
objects = UserManager()
def get_role_permissions(self):
perms = set()
for role in Role.objects.get_roles_for_user(self):
perms = perms.union(
('{}.{}'.format(r.content_type.app_label, r.codename) for r in role.role_permission.all())
)
return perms
def get_all_permissions(self, obj=None):
"""
works as long as we have only a single school
:param obj:
:return: django permissions and school permissions for single default school
"""
django_permissions = super().get_all_permissions(obj)
return django_permissions.union(self.get_role_permissions())
def has_perm(self, perm, obj=None):
return super(User, self).has_perm(perm, obj) or perm in self.get_all_permissions(obj)
def users_in_same_school_class(self):
return User.objects.filter(school_classes__users=self.pk)
def get_teacher(self):
if self.is_teacher():
return self
elif self.school_classes.count() > 0:
return self.school_classes.first().get_teacher()
else:
return None
def is_teacher(self):
return self.user_roles.filter(role__key='teacher').exists()
def selected_class(self):
try:
settings = UserSetting.objects.get(user=self)
return settings.selected_class
except ObjectDoesNotExist:
if self.school_classes.count() > 0:
default_selected_class = self.school_classes.first()
UserSetting.objects.create(selected_class=default_selected_class, user=self)
return default_selected_class
else:
return None
def sync_with_hep_data(self, hep_data):
data_has_changed = False
if self.email != hep_data['email']:
self.email = hep_data['email']
self.username = hep_data['email']
data_has_changed = True
if self.first_name != hep_data['firstname']:
self.first_name = hep_data['firstname']
data_has_changed = True
if self.last_name != hep_data['lastname']:
self.last_name = hep_data['lastname']
data_has_changed = True
if data_has_changed:
self.save()
def set_selected_class(self, school_class):
user_settings, created = UserSetting.objects.get_or_create(user=self)
user_settings.selected_class = school_class
user_settings.save()
@property
def full_name(self):
return self.get_full_name()
class SchoolClass(models.Model):
name = models.CharField(max_length=100, blank=False, null=False, unique=True)
is_deleted = models.BooleanField(blank=False, null=False, default=False)
users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True,
through='users.SchoolClassMember')
code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None)
class Meta:
verbose_name = 'Schulklasse'
verbose_name_plural = 'Schulklassen'
def __str__(self):
return '{}'.format(self.name)
@classmethod
def generate_default_group_name(cls, user=None):
prefix = 'Meine Klasse'
if user is not None:
return f'{prefix} {user.pk}'
prefix_regex = r'Meine Klasse (\d+)'
initial_default_group = '{} 1'.format(prefix)
my_group_filter = cls.objects.filter(name__startswith=prefix).order_by('-pk')
if len(my_group_filter) == 0:
return initial_default_group
match = re.search(prefix_regex, my_group_filter[0].name)
if not match:
return initial_default_group
index = int(match.group(1))
return '{} {}'.format(prefix, index + 1)
@classmethod
def create_default_group_for_teacher(cls, user):
default_class_name = cls.generate_default_group_name(user)
default_class = cls.objects.create(name=default_class_name)
SchoolClassMember.objects.create(
user=user,
school_class=default_class
)
def is_user_in_schoolclass(self, user):
return user.is_superuser or user.school_classes.filter(pk=self.id).count() > 0
def get_teacher(self):
return self.users.filter(user_roles__role__key='teacher').first()
def generate_code(self):
letters = string.ascii_lowercase
digits = string.digits
code = ''.join(random.choice(letters) for i in range(4)) + ''.join(random.choice(digits) for i in range(2))
try:
SchoolClass.objects.get(code=code)
self.generate_code()
except SchoolClass.DoesNotExist:
self.code = code.upper()
self.save()
def save(self, *args, **kwargs):
if self.code == '': # '' can't be unique, so we null it
self.code = None
super().save(*args, **kwargs)
class Role(models.Model):
key = models.CharField(_('Key'), max_length=100, blank=False, null=False, unique=True)
name = models.CharField(_('Name'), max_length=100, blank=False, null=False)
role_permission = models.ManyToManyField(Permission, verbose_name=_('Role permission'),
blank=True, related_name="role_set", related_query_name="role")
objects = RoleManager()
def __str__(self):
return self.name
def permissions_as_code(self):
return self.role_permission.values_list('codename', flat=True)
def has_permission(self, permission_name):
try:
self.role_permission.get(codename=permission_name)
return True
except Permission.DoesNotExist:
return False
@staticmethod
def create_key(name):
return name.lower()
class Meta:
permissions = (
# ("can_edit_events", "Can edit events"),
# ("can_edit_own_comments", "Can edit own comments"),
# ("can_delete_comments", "Can delete comments"),
('can_manage_school_class_content', 'Can manage contents for assigned school clases'),
# ("can_admin_school", "Can admin school"),
)
class UserRole(models.Model):
user = models.ForeignKey(User, blank=False, null=False, on_delete=models.CASCADE, related_name='user_roles')
role = models.ForeignKey(Role, blank=False, null=False, on_delete=models.CASCADE, related_name='user_roles')
objects = UserRoleManager()
@property
def groups(self):
return SchoolClass.objects.filter(users=self.user.id)
@property
def group_ids(self):
return map(lambda g: g.id, self.groups)
@classmethod
def get_roles_for_user(cls, user):
roles = UserRole.objects.filter(user=user)
return roles
@classmethod
def get_role_for_user(cls, user):
return UserRole.get_roles_for_user(user).first()
def __str__(self):
return '%s: %s' % (self.role, self.user)
class UserSetting(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name='user_setting')
selected_class = models.ForeignKey(SchoolClass, blank=True, null=True, on_delete=models.CASCADE)
class UserData(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE, related_name='user_data')
accepted_terms = models.BooleanField(default=False)
class License(models.Model):
for_role = models.ForeignKey(Role, blank=False, null=True, on_delete=models.CASCADE)
licensee = models.ForeignKey(User, blank=False, null=True, on_delete=models.CASCADE)
expire_date = models.DateField(blank=False, null=True, )
order_id = models.IntegerField(blank=False, null=False, default=-1)
raw = models.TextField(default='')
isbn = models.CharField(max_length=50, blank=False, null=False,
default=list(MYSKILLBOX_LICENSES.keys())[0]) # student license
objects = LicenseManager()
def is_teacher_license(self):
return self.for_role.key == RoleManager.TEACHER_KEY
def is_valid(self):
return HepClient.is_product_active(
datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day), self.isbn)
def __str__(self):
return f'License for role: {self.for_role}'
class SchoolClassMember(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE)
active = models.BooleanField(default=True)