diff --git a/server/assignments/migrations/0001_initial.py b/server/assignments/migrations/0001_initial.py index 08e7d87c..219b39fd 100644 --- a/server/assignments/migrations/0001_initial.py +++ b/server/assignments/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.6 on 2018-10-11 13:45 +# Generated by Django 2.0.6 on 2018-10-15 12:37 from django.db import migrations, models import django.db.models.deletion diff --git a/server/assignments/migrations/0002_auto_20181011_1345.py b/server/assignments/migrations/0002_auto_20181015_1237.py similarity index 95% rename from server/assignments/migrations/0002_auto_20181011_1345.py rename to server/assignments/migrations/0002_auto_20181015_1237.py index e990f22a..be98e4e7 100644 --- a/server/assignments/migrations/0002_auto_20181011_1345.py +++ b/server/assignments/migrations/0002_auto_20181015_1237.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.6 on 2018-10-11 13:45 +# Generated by Django 2.0.6 on 2018-10-15 12:37 from django.conf import settings from django.db import migrations, models @@ -10,9 +10,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('books', '0001_initial'), ('assignments', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('books', '0001_initial'), ] operations = [ diff --git a/server/books/migrations/0001_initial.py b/server/books/migrations/0001_initial.py index 7dbe8218..256b26ed 100644 --- a/server/books/migrations/0001_initial.py +++ b/server/books/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.6 on 2018-10-11 13:45 +# Generated by Django 2.0.6 on 2018-10-15 12:37 from django.db import migrations, models import django.db.models.deletion diff --git a/server/books/migrations/0002_auto_20181011_1345.py b/server/books/migrations/0002_auto_20181015_1237.py similarity index 93% rename from server/books/migrations/0002_auto_20181011_1345.py rename to server/books/migrations/0002_auto_20181015_1237.py index 9a5cf6fb..7f7433f8 100644 --- a/server/books/migrations/0002_auto_20181011_1345.py +++ b/server/books/migrations/0002_auto_20181015_1237.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.6 on 2018-10-11 13:45 +# Generated by Django 2.0.6 on 2018-10-15 12:37 from django.db import migrations, models diff --git a/server/core/management/commands/dummy_data.py b/server/core/management/commands/dummy_data.py index 95d573e5..ef894e46 100644 --- a/server/core/management/commands/dummy_data.py +++ b/server/core/management/commands/dummy_data.py @@ -12,7 +12,7 @@ from wagtail.core.models import Page from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory from core.factories import UserFactory from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory -from users.services import create_school_with_users +from users.services import create_users data = [ { @@ -730,10 +730,12 @@ class Command(BaseCommand): u = UserFactory( username='test', is_staff=True, - is_superuser=True + is_superuser=True, + first_name='Nicol', + last_name='Bolas' ) - create_school_with_users('skillbox', user_data) + create_users(user_data) for book_idx, book_data in enumerate(data): book = BookFactory.create(parent=site.root_page, **self.filter_data(book_data, 'topics')) diff --git a/server/objectives/migrations/0001_initial.py b/server/objectives/migrations/0001_initial.py index 3e83b9ab..28cf08f4 100644 --- a/server/objectives/migrations/0001_initial.py +++ b/server/objectives/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.6 on 2018-10-11 13:45 +# Generated by Django 2.0.6 on 2018-10-15 12:37 from django.db import migrations, models import django.db.models.deletion diff --git a/server/objectives/migrations/0002_auto_20181011_1345.py b/server/objectives/migrations/0002_auto_20181015_1237.py similarity index 93% rename from server/objectives/migrations/0002_auto_20181011_1345.py rename to server/objectives/migrations/0002_auto_20181015_1237.py index 3e6e7c57..e85ed9dc 100644 --- a/server/objectives/migrations/0002_auto_20181011_1345.py +++ b/server/objectives/migrations/0002_auto_20181015_1237.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.6 on 2018-10-11 13:45 +# Generated by Django 2.0.6 on 2018-10-15 12:37 from django.conf import settings from django.db import migrations, models @@ -11,8 +11,8 @@ class Migration(migrations.Migration): dependencies = [ ('objectives', '0001_initial'), - ('books', '0002_auto_20181011_1345'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('books', '0002_auto_20181015_1237'), ] operations = [ diff --git a/server/rooms/migrations/0001_initial.py b/server/rooms/migrations/0001_initial.py index 02a8e9b5..0a5ad1c6 100644 --- a/server/rooms/migrations/0001_initial.py +++ b/server/rooms/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.6 on 2018-10-11 13:45 +# Generated by Django 2.0.6 on 2018-10-15 12:37 from django.db import migrations, models import django_extensions.db.fields diff --git a/server/rooms/migrations/0002_auto_20181011_1345.py b/server/rooms/migrations/0002_auto_20181015_1237.py similarity index 95% rename from server/rooms/migrations/0002_auto_20181011_1345.py rename to server/rooms/migrations/0002_auto_20181015_1237.py index 8c617e80..44325a9f 100644 --- a/server/rooms/migrations/0002_auto_20181011_1345.py +++ b/server/rooms/migrations/0002_auto_20181015_1237.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.6 on 2018-10-11 13:45 +# Generated by Django 2.0.6 on 2018-10-15 12:37 from django.conf import settings from django.db import migrations, models @@ -10,8 +10,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('users', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('users', '0001_initial'), ('rooms', '0001_initial'), ] diff --git a/server/users/admin.py b/server/users/admin.py index 5d9de542..ad435bf1 100644 --- a/server/users/admin.py +++ b/server/users/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin -from .models import User, SchoolClass, School, SchoolRole, UserSchoolRole +from .models import User, SchoolClass, Role, UserRole admin.site.register(User, UserAdmin) @@ -12,23 +12,15 @@ class SchoolClassAdmin(admin.ModelAdmin): list_filter = ('year',) -@admin.register(School) -class SchoolAdmin(admin.ModelAdmin): +@admin.register(Role) +class SchoolRoleAdmin(admin.ModelAdmin): list_display = ('name',) list_filter = ('name',) - readonly_fields = [] -@admin.register(SchoolRole) -class SchoolRoleAdmin(admin.ModelAdmin): - list_display = ('school', 'name') - list_filter = ('school', 'name') - readonly_fields = ('school', ) - - -@admin.register(UserSchoolRole) +@admin.register(UserRole) class UserSchoolRoleAdmin(admin.ModelAdmin): - list_display = ('user', 'school_role') - list_filter = ('school_role__school',) + list_display = ('user', 'role') + # list_filter = ('role__school',) readonly_fields = [] - #search_fields = ('user__email', 'user__username') + # search_fields = ('user__email', 'user__username') diff --git a/server/users/managers.py b/server/users/managers.py new file mode 100644 index 00000000..90afec13 --- /dev/null +++ b/server/users/managers.py @@ -0,0 +1,87 @@ +from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext_lazy as _ +from django.db import models + + +class RoleManager(models.Manager): + use_in_migrations = True + + TEACHER_KEY = 'teacher' + STUDENT_KEY = 'student' + PARENT_KEY = 'parent' + + DEFAULT_ROLES = { + TEACHER_KEY: _(u'Lehrperson'), + STUDENT_KEY: _(u'Schüler'), + # PARENT_KEY: _(u'Aufsichtsperson'), + # SCHOOL_ADMIN_KEY: _(u'Schuladministrator') + } + + READONLY_ROLES = [] + + DEFAULT_ROLE_KEYS = DEFAULT_ROLES.keys() + + def is_key_in_defaults(self, key): + return key in self.DEFAULT_ROLE_KEYS + + def is_key_readonly(self, key): + return key in self.READONLY_ROLES + + def get_roles_for_user(self, user): + return self.model.objects.filter(user_roles__user=user) + + def create_default_roles(self): + for key, value in self.DEFAULT_ROLES.items(): + role = self.create(name=value, key=key) + role.save() + + can_manage_school_class_content, = self._create_default_permissions() + + if key == "teacher": + role.role_permission.add(can_manage_school_class_content.id) + + # elif key == "school_admin": + # role.role_permission.add() + # + # elif key == "student": + # role.role_permission.add() + + def get_default_teacher_role(self): + return self._get_default_role(self.TEACHER_KEY) + + def get_default_student_role(self): + return self._get_default_role(self.STUDENT_KEY) + + def _get_default_role(self, key): + try: + return self.get(name=self.DEFAULT_ROLES[key]) + except self.model.DoesNotExist: + return None + + def _create_default_permissions(self): + content_type = ContentType.objects.get_for_model(self.model) + # edit_events = Permission.objects.get(content_type=content_type, codename="can_edit_events") + # edit_own_comments = Permission.objects.get(content_type=content_type, codename="can_edit_own_comments") + # delete_comments = Permission.objects.get(content_type=content_type, codename="can_delete_comments") + # admin_school = Permission.objects.get(content_type=content_type, codename="can_admin_school") + can_manage_school_class_content = Permission.objects.get(content_type=content_type, + codename='can_manage_school_class_content') + + return can_manage_school_class_content, + + +class UserRoleManager(models.Manager): + def create_role_for_user(self, user, role_key): + from users.models import Role + try: + role = Role.objects.get(key=role_key) + except Role.DoesNotExist: + return None + + return self._create_user_role(user, role) + + def _create_user_role(self, user, role): + user_role = self.model(user=user, role=role) + user_role.save() + return user_role diff --git a/server/users/migrations/0001_initial.py b/server/users/migrations/0001_initial.py index e2c6d96b..3bf54fa2 100644 --- a/server/users/migrations/0001_initial.py +++ b/server/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.6 on 2018-10-11 13:45 +# Generated by Django 2.0.6 on 2018-10-15 12:37 from django.conf import settings import django.contrib.auth.models @@ -46,10 +46,18 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='School', + name='Role', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(max_length=100, unique=True, verbose_name='Key')), ('name', models.CharField(max_length=100, verbose_name='Name')), + ('role_permission', models.ManyToManyField(blank=True, related_name='role_set', related_query_name='role', to='auth.Permission', verbose_name='Role permission')), + ], + options={ + 'permissions': (('can_manage_school_class_content', 'Can manage contents for assigned school clases'),), + }, + managers=[ + ('objects', users.models.RoleManager()), ], ), migrations.CreateModel( @@ -59,36 +67,15 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=100)), ('year', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1900), django.core.validators.MaxValueValidator(2200)])), ('is_deleted', models.BooleanField(default=False)), - ('school', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.School')), ('users', models.ManyToManyField(related_name='school_classes', to=settings.AUTH_USER_MODEL)), ], ), migrations.CreateModel( - name='SchoolRole', + name='UserRole', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('key', models.CharField(max_length=100, verbose_name='Key')), - ('name', models.CharField(max_length=100, verbose_name='Name')), - ('role_permission', models.ManyToManyField(blank=True, related_name='role_set', related_query_name='role', to='auth.Permission', verbose_name='Role permission')), - ('school', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.School')), - ], - options={ - 'permissions': (('can_manage_school_class_content', 'Can manage contents for assigned school clases'),), - }, - managers=[ - ('objects', users.models.SchoolRoleManager()), - ], - ), - migrations.CreateModel( - name='UserSchoolRole', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('school_role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.SchoolRole')), + ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Role')), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), - migrations.AlterUniqueTogether( - name='schoolrole', - unique_together={('key', 'school')}, - ), ] diff --git a/server/users/models.py b/server/users/models.py index dbc6c370..764d8e96 100644 --- a/server/users/models.py +++ b/server/users/models.py @@ -3,16 +3,17 @@ from django.contrib.auth.models import AbstractUser, Permission from django.contrib.contenttypes.models import ContentType from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models -from django.db.models import Q from django.utils.translation import ugettext_lazy as _ +from users.managers import RoleManager, UserRoleManager + DEFAULT_SCHOOL_ID = 1 class User(AbstractUser): - def get_school_permissions(self, school): + def get_role_permissions(self): perms = set() - for role in SchoolRole.objects.get_school_roles_for_user(self): + 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()) ) @@ -25,123 +26,34 @@ class User(AbstractUser): :return: django permissions and school permissions for single default school """ django_permissions = super().get_all_permissions(obj) - return django_permissions.union(self.get_school_permissions(School.objects.get(pk=DEFAULT_SCHOOL_ID))) + 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) -class School(models.Model): - name = models.CharField(_(u'Name'), max_length=100, null=False, blank=False) - - @classmethod - def create_school(cls, name, id=None): - school = School(name=name, id=id) - school.save() - # Label.create_labels_for_school(school) - SchoolRole.objects.create_default_roles_for_school(school) - return school - - def __str__(self): - return self.name - - class SchoolClass(models.Model): name = models.CharField(max_length=100, blank=False, null=False) - year = models.PositiveIntegerField(blank=False, null=False, validators=[MinValueValidator(1900), MaxValueValidator(2200)]) + year = models.PositiveIntegerField(blank=False, null=False, + validators=[MinValueValidator(1900), MaxValueValidator(2200)]) is_deleted = models.BooleanField(blank=False, null=False, default=False) users = models.ManyToManyField(get_user_model(), related_name='school_classes') - school = models.ForeignKey('School', null=False, on_delete=models.CASCADE) def __str__(self): return 'SchoolClass {}-{}-{}'.format(self.id, self.name, self.year) -class SchoolRoleManager(models.Manager): - use_in_migrations = True - TEACHER_KEY = 'teacher' - STUDENT_KEY = 'student' - PARENT_KEY = 'parent' - SCHOOL_ADMIN_KEY = 'school_admin' - - DEFAULT_ROLES = { - TEACHER_KEY: _(u'Lehrperson'), - STUDENT_KEY: _(u'Schüler'), - # PARENT_KEY: _(u'Aufsichtsperson'), - # SCHOOL_ADMIN_KEY: _(u'Schuladministrator') - } - - READONLY_ROLES = [SCHOOL_ADMIN_KEY] - - DEFAULT_ROLE_KEYS = DEFAULT_ROLES.keys() - - def is_key_in_defaults(self, key): - return key in self.DEFAULT_ROLE_KEYS - - def is_key_readonly(self, key): - return key in self.READONLY_ROLES - - def get_school_roles_for_user(self, hc_user): - return self.model.objects.filter(userschoolrole__user=hc_user) - - def create_default_roles_for_school(self, school): - for key, value in self.DEFAULT_ROLES.items(): - role = self.create(name=value, school=school, key=key) - role.save() - - can_manage_school_class_content, = self._create_default_permissions() - - if key == "teacher": - role.role_permission.add(can_manage_school_class_content.id) - - # elif key == "school_admin": - # role.role_permission.add() - # - # elif key == "student": - # role.role_permission.add() - - def get_default_admin_role_for_school(self, school): - return self._get_default_role_for_school(school, self.SCHOOL_ADMIN_KEY) - - def get_default_teacher_role_for_school(self, school): - return self._get_default_role_for_school(school, self.TEACHER_KEY) - - def get_default_student_role_for_school(self, school): - return self._get_default_role_for_school(school, self.STUDENT_KEY) - - def get_default_parent_for_school(self, school): - return self._get_default_role_for_school(school, self.PARENT_KEY) - - def _get_default_role_for_school(self, school, key): - try: - return self.get(school=school, name=self.DEFAULT_ROLES[key]) - except self.model.DoesNotExist: - return None - - def _create_default_permissions(self): - content_type = ContentType.objects.get_for_model(self.model) - #edit_events = Permission.objects.get(content_type=content_type, codename="can_edit_events") - #edit_own_comments = Permission.objects.get(content_type=content_type, codename="can_edit_own_comments") - #delete_comments = Permission.objects.get(content_type=content_type, codename="can_delete_comments") - #admin_school = Permission.objects.get(content_type=content_type, codename="can_admin_school") - can_manage_school_class_content = Permission.objects.get(content_type=content_type, codename='can_manage_school_class_content') - - return can_manage_school_class_content, - - -class SchoolRole(models.Model): - - key = models.CharField(_('Key'), max_length=100, blank=False, null=False) +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) - school = models.ForeignKey('School', blank=False, null=False, on_delete=models.CASCADE) role_permission = models.ManyToManyField(Permission, verbose_name=_('Role permission'), blank=True, related_name="role_set", related_query_name="role") - objects = SchoolRoleManager() + objects = RoleManager() def __str__(self): - return u'{} - {}'.format(self.school, self.name) + return self.name def permissions_as_code(self): return self.role_permission.values_list('codename', flat=True) @@ -166,69 +78,30 @@ class SchoolRole(models.Model): # ("can_admin_school", "Can admin school"), ) - unique_together = ('key', 'school',) -class UserSchoolRoleManager(models.Manager): +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') - def create_userrole_for_user_and_school(self, hc_user, school, role_key): - try: - school_role = SchoolRole.objects.get(school=school, key=role_key) - except SchoolRole.DoesNotExist: - return None - - return self._create_user_schoolrole(hc_user, school_role) - - def create_role_and_userrole_for_user_and_school(self, hc_user, school, role_name): - created = False - role_key = role_name.lower() - try: - school_role = SchoolRole.objects.get(school=school, name=role_name) - except SchoolRole.DoesNotExist: - school_role = SchoolRole(school=school, key=role_key, name=role_name) - school_role.save() - created = True - - return self._create_user_schoolrole(hc_user, school_role), created - - def _create_user_schoolrole(self, hc_user, school_role): - user_school_role = self.model(user=hc_user, school_role=school_role) - user_school_role.save() - return user_school_role - - -class UserSchoolRole(models.Model): - user = models.ForeignKey(User, blank=False, null=False, on_delete=models.CASCADE) - school_role = models.ForeignKey(SchoolRole, blank=False, null=False, on_delete=models.CASCADE) - - objects = UserSchoolRoleManager() + objects = UserRoleManager() @property def groups(self): - return SchoolClass.objects.filter(Q(school_id=self.school_role.school.id) & Q(users=self.user.id)) + return SchoolClass.objects.filter(users=self.user.id) @property def group_ids(self): return map(lambda g: g.id, self.groups) - def addgroup(self, group): - group.user_set.add(self.user.user) - self.user.user.groups.add(group) - self.user.user.save() - @classmethod def get_roles_for_user(cls, user): - roles = UserSchoolRole.objects.filter(user=user) + roles = UserRole.objects.filter(user=user) return roles - # TODO: This method is only valid as long as a user can only belong to one school (today's setup) @classmethod def get_role_for_user(cls, user): - roles = UserSchoolRole.get_roles_for_user(user) - if len(roles) > 0: - return roles[0] - else: - return None + return UserRole.get_roles_for_user(user).first() def __str__(self): - return '%s: %s' % (self.school_role, self.user) + return '%s: %s' % (self.role, self.user) diff --git a/server/users/schema.py b/server/users/schema.py index 31debb94..b415101d 100644 --- a/server/users/schema.py +++ b/server/users/schema.py @@ -29,8 +29,8 @@ class UserNode(DjangoObjectType): # todo: wayyyy to complicated, do we need such a sophisticated role system? def resolve_role(self, info, **kwargs): - role = self.userschoolrole_set.first() - return role.school_role.key if role is not None else 'student' + role = self.user_roles.first() + return role.role.key if role is not None else 'student' class SchoolClassNode(DjangoObjectType): diff --git a/server/users/services.py b/server/users/services.py index 90b1e660..85eb2ee7 100644 --- a/server/users/services.py +++ b/server/users/services.py @@ -1,22 +1,22 @@ from core.factories import UserFactory from users.factories import SchoolClassFactory -from users.models import School, SchoolRole, UserSchoolRole, DEFAULT_SCHOOL_ID +from users.models import Role, UserRole, DEFAULT_SCHOOL_ID -def create_school_with_users(school_name, data=None): - school = School.create_school(school_name, id=DEFAULT_SCHOOL_ID) +def create_users(data=None): + Role.objects.create_default_roles() - teacher_role = SchoolRole.objects.get_default_teacher_role_for_school(school) - student_role = SchoolRole.objects.get_default_student_role_for_school(school) + teacher_role = Role.objects.get_default_teacher_role() + student_role = Role.objects.get_default_student_role() if data is None: teacher = UserFactory(username='teacher') - UserSchoolRole.objects.create(user=teacher, school_role=teacher_role) + UserRole.objects.create(user=teacher, role=teacher_role) for i in range(1, 7): student = UserFactory(username='student{}'.format(i)) - UserSchoolRole.objects.create(user=student, school_role=student_role) - SchoolClassFactory(users=[teacher, student], school=school) + UserRole.objects.create(user=student, role=student_role) + SchoolClassFactory(users=[teacher, student]) else: for school_class in data: @@ -27,7 +27,7 @@ def create_school_with_users(school_name, data=None): last_name=last, email='{}.{}@skillbox.example'.format(first, last).lower() ) - UserSchoolRole.objects.create(user=teacher, school_role=teacher_role) + UserRole.objects.create(user=teacher, role=teacher_role) students = [] for first, last in school_class.get('students'): @@ -37,12 +37,11 @@ def create_school_with_users(school_name, data=None): last_name=last, email='{}.{}@skillbox.example'.format(first, last).lower() ) - UserSchoolRole.objects.create(user=student, school_role=student_role) + UserRole.objects.create(user=student, role=student_role) students.append(student) SchoolClassFactory( users=students + [teacher], - school=school, name=school_class.get('class'), year='2018' )