from datetime import datetime import graphene from django.db.models import Q from django.utils.dateformat import format from django_filters import FilterSet, OrderingFilter from graphene import ObjectType, relay from graphene_django import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField from graphql_relay import to_global_id 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 SchoolClass, SchoolClassMember, Team, User class SchoolClassNode(DjangoObjectType): pk = graphene.Int() members = graphene.List('users.schema.ClassMemberNode') code = graphene.String() read_only = graphene.Boolean() class Meta: model = SchoolClass only_fields = ['name', 'code', 'members', 'pk', 'read_only'] filter_fields = ['name'] interfaces = (relay.Node,) def resolve_pk(self, *args, **kwargs): return self.id def resolve_members(self, info, **kwargs): return SchoolClassMember.objects.filter(school_class=self) def resolve_code(self: SchoolClass, info, **kwargs): if not info.context.user.is_teacher(): return None if self.code is None: self.generate_code() return self.code @staticmethod def resolve_read_only(root: SchoolClass, info, **kwargs): user = info.context.user member = SchoolClassMember.objects.get(user=user, school_class=root) return not member.active class TeamNode(DjangoObjectType): class Meta: model = Team filter_fields = ['name'] interfaces = (relay.Node,) pk = graphene.Int() members = graphene.List('users.schema.PublicUserNode') def resolve_pk(self, *args, **kwargs): return self.id def resolve_members(self, *args, **kwargs): return self.members.all() class RecentModuleFilter(FilterSet): class Meta: model = Module fields = ('recent_modules',) order_by = OrderingFilter( fields=( ('recent_modules__visited', 'visited'), ) ) class PublicUserNode(DjangoObjectType): full_name = graphene.String(required=True) is_me = graphene.Boolean() class Meta: model = User only_fields = ['full_name', 'first_name', 'last_name', 'avatar_url'] interfaces = (relay.Node,) @staticmethod def resolve_is_me(parent: User, info, **kwargs): return info.context.user.pk == parent.pk class PrivateUserNode(DjangoObjectType): class Meta: model = User filter_fields = ['username', 'email'] only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module', 'last_topic', 'avatar_url', 'selected_class', 'expiry_date', 'onboarding_visited', 'team', 'read_only'] interfaces = (relay.Node,) pk = graphene.Int() permissions = graphene.List(graphene.String) selected_class = graphene.Field(SchoolClassNode) expiry_date = graphene.String() is_teacher = graphene.Boolean() old_classes = graphene.List(SchoolClassNode) school_classes = graphene.List(SchoolClassNode) recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter) team = graphene.Field(TeamNode) read_only = graphene.Boolean() def resolve_pk(self, info, **kwargs): return self.id def resolve_permissions(self, info): return self.get_all_permissions() @staticmethod def resolve_selected_class(root: User, info): return root.selected_class @staticmethod def resolve_expiry_date(root: User, info): if not root.hep_id: # concerns users that already have an (old) account return format(datetime(2020, 7, 31), 'U') # just set some expiry date else: return root.license_expiry_date def resolve_is_teacher(root: User, info): return root.is_teacher() @staticmethod def resolve_school_classes(root: User, info): if root.selected_class is None: # then we don't have any class to return return SchoolClass.objects.none() return SchoolClass.objects.filter( Q(schoolclassmember__active=True, schoolclassmember__user=root) | Q(pk=root.selected_class.pk)).distinct() def resolve_old_classes(self: User, info): return SchoolClass.objects.filter(schoolclassmember__active=False, schoolclassmember__user=self) def resolve_recent_modules(self, info, **kwargs): # see https://docs.graphene-python.org/projects/django/en/latest/filtering/ return RecentModuleFilter(kwargs).qs.filter(recent_modules__user=self) def resolve_team(self, info, **kwargs): return self.team 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('users.schema.PublicUserNode') active = graphene.Boolean() first_name = graphene.String() last_name = graphene.String() is_teacher = graphene.Boolean() id = graphene.ID() is_me = graphene.Boolean() def resolve_id(self, *args): return to_global_id('PublicUserNode', 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() @staticmethod def resolve_is_me(parent, info, **kwargs): return info.context.user.pk == parent.user.pk class UsersQuery(object): me = graphene.Field(PrivateUserNode) all_users = DjangoFilterConnectionField(PrivateUserNode) my_activity = DjangoFilterConnectionField(ModuleNode) my_instrument_activity = DjangoFilterConnectionField(InstrumentNode) def resolve_me(self, info, **kwargs): return info.context.user def resolve_all_users(self, info, **kwargs): if not info.context.user.is_superuser: return User.objects.none() else: return User.objects.all() def resolve_my_activity(self, info, **kwargs): return Module.objects.all() def resolve_my_instrument_activity(self, info, **kwargs): return BasicKnowledge.objects.all() class AllUsersQuery(object): me = graphene.Field(PrivateUserNode) all_users = DjangoFilterConnectionField(PrivateUserNode) def resolve_all_users(self, info, **kwargs): if not info.context.user.is_superuser: return User.objects.none() else: return User.objects.all()