From 35c981a2f2a524d39b1a6dd023871d3323ed4e81 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Tue, 3 Mar 2020 15:58:13 +0100 Subject: [PATCH 01/15] Add through model to user school class connection, migrate all old classes --- .../migrations/0012_auto_20200303_1258.py | 29 +++++++++++++++++++ .../migrations/0013_auto_20200303_1259.py | 24 +++++++++++++++ .../0014_remove_schoolclass_users.py | 17 +++++++++++ .../migrations/0015_auto_20200303_1306.py | 23 +++++++++++++++ server/users/models.py | 9 +++++- 5 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 server/users/migrations/0012_auto_20200303_1258.py create mode 100644 server/users/migrations/0013_auto_20200303_1259.py create mode 100644 server/users/migrations/0014_remove_schoolclass_users.py create mode 100644 server/users/migrations/0015_auto_20200303_1306.py diff --git a/server/users/migrations/0012_auto_20200303_1258.py b/server/users/migrations/0012_auto_20200303_1258.py new file mode 100644 index 00000000..3fd7f2ed --- /dev/null +++ b/server/users/migrations/0012_auto_20200303_1258.py @@ -0,0 +1,29 @@ +# Generated by Django 2.1.15 on 2020-03-03 12:58 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0011_auto_20200302_1613'), + ] + + operations = [ + migrations.CreateModel( + name='UserSchoolClassConnection', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('active', models.BooleanField(default=True)), + ('school_class', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.SchoolClass')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='schoolclass', + name='users_with_active', + field=models.ManyToManyField(blank=True, related_name='school_classes_with_active', through='users.UserSchoolClassConnection', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/server/users/migrations/0013_auto_20200303_1259.py b/server/users/migrations/0013_auto_20200303_1259.py new file mode 100644 index 00000000..6b82bc12 --- /dev/null +++ b/server/users/migrations/0013_auto_20200303_1259.py @@ -0,0 +1,24 @@ +# Generated by Django 2.1.15 on 2020-03-03 12:59 + +from django.db import migrations + +from users.models import UserSchoolClassConnection + + +def forwards(apps, schema_editor): + SchoolClass = apps.get_model('users', 'SchoolClass') + UserSchoolClassConnection = apps.get_model('users', 'UserSchoolClassConnection') + + for school_class in SchoolClass.objects.all(): + for user in school_class.users.all(): + UserSchoolClassConnection.objects.create(user=user, school_class=school_class, active=True) + + +class Migration(migrations.Migration): + dependencies = [ + ('users', '0012_auto_20200303_1258'), + ] + + operations = [ + migrations.RunPython(forwards) + ] diff --git a/server/users/migrations/0014_remove_schoolclass_users.py b/server/users/migrations/0014_remove_schoolclass_users.py new file mode 100644 index 00000000..5293ac15 --- /dev/null +++ b/server/users/migrations/0014_remove_schoolclass_users.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.15 on 2020-03-03 13:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0013_auto_20200303_1259'), + ] + + operations = [ + migrations.RemoveField( + model_name='schoolclass', + name='users', + ), + ] diff --git a/server/users/migrations/0015_auto_20200303_1306.py b/server/users/migrations/0015_auto_20200303_1306.py new file mode 100644 index 00000000..ebe19bf2 --- /dev/null +++ b/server/users/migrations/0015_auto_20200303_1306.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.15 on 2020-03-03 13:06 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0014_remove_schoolclass_users'), + ] + + operations = [ + migrations.RemoveField( + model_name='schoolclass', + name='users_with_active', + ), + migrations.AddField( + model_name='schoolclass', + name='users', + field=models.ManyToManyField(blank=True, related_name='school_classes', through='users.UserSchoolClassConnection', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/server/users/models.py b/server/users/models.py index 6477a46a..30932ea1 100644 --- a/server/users/models.py +++ b/server/users/models.py @@ -11,6 +11,7 @@ from users.managers import RoleManager, UserRoleManager, UserManager DEFAULT_SCHOOL_ID = 1 + class User(AbstractUser): last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True) avatar_url = models.CharField(max_length=254, blank=True, default='') @@ -69,7 +70,7 @@ class User(AbstractUser): 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) + users = models.ManyToManyField(get_user_model(), related_name='school_classes', blank=True, through='users.UserSchoolClassConnection') code = models.CharField('Code zum Beitreten', blank=True, null=True, max_length=10, unique=True, default=None) class Meta: @@ -175,3 +176,9 @@ class UserRole(models.Model): 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 UserSchoolClassConnection(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE) + active = models.BooleanField(default=True) From 0ea72fe598d23be682f54f82cf6d253c48ca4eb5 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 4 Mar 2020 16:32:10 +0100 Subject: [PATCH 02/15] Rename UserSchoolClassConnection to SchoolClassMember --- .../users/migrations/0013_auto_20200303_1259.py | 3 --- .../users/migrations/0016_auto_20200304_1250.py | 17 +++++++++++++++++ server/users/models.py | 5 +++-- 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 server/users/migrations/0016_auto_20200304_1250.py diff --git a/server/users/migrations/0013_auto_20200303_1259.py b/server/users/migrations/0013_auto_20200303_1259.py index 6b82bc12..8136a893 100644 --- a/server/users/migrations/0013_auto_20200303_1259.py +++ b/server/users/migrations/0013_auto_20200303_1259.py @@ -2,9 +2,6 @@ from django.db import migrations -from users.models import UserSchoolClassConnection - - def forwards(apps, schema_editor): SchoolClass = apps.get_model('users', 'SchoolClass') UserSchoolClassConnection = apps.get_model('users', 'UserSchoolClassConnection') diff --git a/server/users/migrations/0016_auto_20200304_1250.py b/server/users/migrations/0016_auto_20200304_1250.py new file mode 100644 index 00000000..7f29426f --- /dev/null +++ b/server/users/migrations/0016_auto_20200304_1250.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.15 on 2020-03-04 12:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0015_auto_20200303_1306'), + ] + + operations = [ + migrations.RenameModel( + old_name='UserSchoolClassConnection', + new_name='SchoolClassMember', + ), + ] diff --git a/server/users/models.py b/server/users/models.py index 30932ea1..51049015 100644 --- a/server/users/models.py +++ b/server/users/models.py @@ -70,7 +70,8 @@ class User(AbstractUser): 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.UserSchoolClassConnection') + 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: @@ -178,7 +179,7 @@ class UserSetting(models.Model): selected_class = models.ForeignKey(SchoolClass, blank=True, null=True, on_delete=models.CASCADE) -class UserSchoolClassConnection(models.Model): +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) From acdcc7ea131418ce764248734868a018ca4837c6 Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 4 Mar 2020 16:42:20 +0100 Subject: [PATCH 03/15] Add class member node --- server/users/models.py | 6 ++++-- server/users/schema.py | 44 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/server/users/models.py b/server/users/models.py index 51049015..209c3e80 100644 --- a/server/users/models.py +++ b/server/users/models.py @@ -11,7 +11,6 @@ from users.managers import RoleManager, UserRoleManager, UserManager DEFAULT_SCHOOL_ID = 1 - class User(AbstractUser): last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True) avatar_url = models.CharField(max_length=254, blank=True, default='') @@ -43,13 +42,16 @@ class User(AbstractUser): return User.objects.filter(school_classes__users=self.pk) def get_teacher(self): - if self.user_roles.filter(role__key='teacher').exists(): + 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) diff --git a/server/users/schema.py b/server/users/schema.py index 398c6202..310290d4 100644 --- a/server/users/schema.py +++ b/server/users/schema.py @@ -1,19 +1,21 @@ import graphene -from graphene import relay +from django.db.models import Prefetch, Value, CharField +from django.db.models.functions import Concat +from graphene import relay, ObjectType from graphene_django import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField +from graphql_relay import to_global_id -from assignments.models import StudentSubmission -from assignments.schema.types import StudentSubmissionNode from basicknowledge.models import BasicKnowledge from basicknowledge.queries import InstrumentNode from books.models import Module from books.schema.queries import ModuleNode -from users.models import User, SchoolClass +from users.models import User, SchoolClass, SchoolClassMember class SchoolClassNode(DjangoObjectType): pk = graphene.Int() + members = graphene.List('users.schema.ClassMemberNode') class Meta: model = SchoolClass @@ -23,6 +25,9 @@ class SchoolClassNode(DjangoObjectType): def resolve_pk(self, *args, **kwargs): return self.id + def resolve_members(self, info, **kwargs): + return SchoolClassMember.objects.filter(school_class=self) + class UserNode(DjangoObjectType): pk = graphene.Int() @@ -46,6 +51,37 @@ class UserNode(DjangoObjectType): return self.selected_class() +class ClassMemberNode(ObjectType): + """ + We need to build this ourselves, because we want the active property on the node, because providing it on the + Connection or Edge for a UserNodeConnection is difficult. + """ + user = graphene.Field(UserNode) + active = graphene.Boolean() + first_name = graphene.String() + last_name = graphene.String() + is_teacher = graphene.Boolean() + id = graphene.ID() + + def resolve_id(self, *args): + return to_global_id('UserNode', self.user.pk) + + def resolve_active(self, *args): + return self.active + + def resolve_first_name(self, *args): + return self.user.first_name + + def resolve_last_name(self, *args): + return self.user.last_name + + def resolve_user(self, info, **kwargs): + return self.user + + def resolve_is_teacher(self, *args): + return self.user.is_teacher() + + class UsersQuery(object): me = graphene.Field(UserNode) all_users = DjangoFilterConnectionField(UserNode) From a99a073460cd906067bf24879899bf123b73163f Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Wed, 4 Mar 2020 16:55:02 +0100 Subject: [PATCH 04/15] Distinguish between active and inactive users in class list --- client/src/components/profile/Classlist.vue | 39 ++++++++++++++------- client/src/graphql/gql/mySchoolClass.gql | 17 ++++----- client/src/pages/myClass.vue | 5 +-- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/client/src/components/profile/Classlist.vue b/client/src/components/profile/Classlist.vue index 7689be92..3f10d95f 100644 --- a/client/src/components/profile/Classlist.vue +++ b/client/src/components/profile/Classlist.vue @@ -1,10 +1,18 @@