Add new test for school class read only

Also clean up some code
This commit is contained in:
Ramon Wenger 2021-07-28 16:19:42 +02:00
parent 462348dd32
commit d071afbd67
11 changed files with 62 additions and 48 deletions

View File

@ -24,7 +24,7 @@ class UpdateSolutionVisibility(relay.ClientIDMutation):
slug = args.get('slug') slug = args.get('slug')
enabled = args.get('enabled') enabled = args.get('enabled')
user = info.context.user user = info.context.user
selected_class = user.selected_class() selected_class = user.selected_class
if 'users.can_manage_school_class_content' not in user.get_role_permissions(): if 'users.can_manage_school_class_content' not in user.get_role_permissions():
raise PermissionError() raise PermissionError()

View File

@ -45,7 +45,7 @@ class ModuleNode(DjangoObjectType):
return self.get_parent().specific return self.get_parent().specific
def resolve_solutions_enabled(self, info, **kwargs): def resolve_solutions_enabled(self, info, **kwargs):
school_class = info.context.user.selected_class() school_class = info.context.user.selected_class
return self.solutions_enabled_for.filter(pk=school_class.pk).exists() if school_class is not None else False return self.solutions_enabled_for.filter(pk=school_class.pk).exists() if school_class is not None else False
def resolve_bookmark(self, info, **kwags): def resolve_bookmark(self, info, **kwags):

View File

@ -30,7 +30,7 @@ class ModuleSolutionVisibilityTest(TestCase):
) )
self.teacher = User.objects.get(username="teacher") self.teacher = User.objects.get(username="teacher")
self.selected_class = self.teacher.selected_class() self.selected_class = self.teacher.selected_class
self.student = User.objects.get(username="student1") self.student = User.objects.get(username="student1")
student_request = RequestFactory().get('/') student_request = RequestFactory().get('/')
student_request.user = self.student student_request.user = self.student
@ -54,7 +54,7 @@ class ModuleSolutionVisibilityTest(TestCase):
""" """
def test_hide_solutions_for_students_and_then_show_them(self): def test_hide_solutions_for_students_and_then_show_them(self):
self.assertEqual(self.student.selected_class(), self.teacher.selected_class()) self.assertEqual(self.student.selected_class, self.teacher.selected_class)
student_result = self.student_client.execute(self.query, variables={ student_result = self.student_client.execute(self.query, variables={
'id': self.content_block_id 'id': self.content_block_id

View File

@ -3,7 +3,7 @@ from users.models import User
def are_solutions_enabled_for(user: User, module: Module): def are_solutions_enabled_for(user: User, module: Module):
school_class = user.selected_class() school_class = user.selected_class
return module.solutions_enabled_for.filter(pk=school_class.pk).exists() return module.solutions_enabled_for.filter(pk=school_class.pk).exists()

View File

@ -370,7 +370,6 @@ type CreateTeamPayload {
} }
type CustomMutation { type CustomMutation {
redeemCoupon(input: CouponInput!): CouponPayload
spellCheck(input: SpellCheckInput!): SpellCheckPayload spellCheck(input: SpellCheckInput!): SpellCheckPayload
addNote(input: AddNoteInput!): AddNotePayload addNote(input: AddNoteInput!): AddNotePayload
updateNote(input: UpdateNoteInput!): UpdateNotePayload updateNote(input: UpdateNoteInput!): UpdateNotePayload
@ -920,6 +919,7 @@ type SchoolClassNode implements Node {
rooms(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, appearance: String): RoomNodeConnection! rooms(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, appearance: String): RoomNodeConnection!
pk: Int pk: Int
members: [ClassMemberNode] members: [ClassMemberNode]
readOnly: Boolean
} }
type SchoolClassNodeConnection { type SchoolClassNodeConnection {

View File

@ -7,9 +7,11 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser, Permission from django.contrib.auth.models import AbstractUser, Permission
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from django.utils.functional import cached_property
from django.utils.timezone import make_aware, is_aware from django.utils.timezone import make_aware, is_aware
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from typing import Union
from users.licenses import MYSKILLBOX_LICENSES from users.licenses import MYSKILLBOX_LICENSES
from users.managers import RoleManager, UserRoleManager, UserManager, LicenseManager from users.managers import RoleManager, UserRoleManager, UserManager, LicenseManager
@ -75,7 +77,8 @@ class User(AbstractUser):
def is_teacher(self): def is_teacher(self):
return self.user_roles.filter(role__key='teacher').exists() return self.user_roles.filter(role__key='teacher').exists()
def selected_class(self): @cached_property
def selected_class(self) -> Union['SchoolClass', None]:
try: try:
settings = UserSetting.objects.get(user=self) settings = UserSetting.objects.get(user=self)
return settings.selected_class return settings.selected_class

View File

@ -4,7 +4,7 @@ import graphene
from django.db.models import Q from django.db.models import Q
from django.utils.dateformat import format from django.utils.dateformat import format
from django_filters import FilterSet, OrderingFilter from django_filters import FilterSet, OrderingFilter
from graphene import relay, ObjectType from graphene import ObjectType, relay
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
from graphql_relay import to_global_id from graphql_relay import to_global_id
@ -13,13 +13,14 @@ from basicknowledge.models import BasicKnowledge
from basicknowledge.queries import InstrumentNode from basicknowledge.queries import InstrumentNode
from books.models import Module from books.models import Module
from books.schema.queries import ModuleNode from books.schema.queries import ModuleNode
from users.models import User, SchoolClass, SchoolClassMember, Team from users.models import SchoolClass, SchoolClassMember, Team, User
class SchoolClassNode(DjangoObjectType): class SchoolClassNode(DjangoObjectType):
pk = graphene.Int() pk = graphene.Int()
members = graphene.List('users.schema.ClassMemberNode') members = graphene.List('users.schema.ClassMemberNode')
code = graphene.String() code = graphene.String()
read_only = graphene.Boolean()
class Meta: class Meta:
model = SchoolClass model = SchoolClass
@ -32,13 +33,19 @@ class SchoolClassNode(DjangoObjectType):
def resolve_members(self, info, **kwargs): def resolve_members(self, info, **kwargs):
return SchoolClassMember.objects.filter(school_class=self) return SchoolClassMember.objects.filter(school_class=self)
def resolve_code(self, info, **kwargs): def resolve_code(self: SchoolClass, info, **kwargs):
if not info.context.user.is_teacher(): if not info.context.user.is_teacher():
return None return None
if self.code is None: if self.code is None:
self.generate_code() self.generate_code()
return self.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 TeamNode(DjangoObjectType):
class Meta: class Meta:
@ -107,25 +114,28 @@ class PrivateUserNode(DjangoObjectType):
def resolve_permissions(self, info): def resolve_permissions(self, info):
return self.get_all_permissions() return self.get_all_permissions()
def resolve_selected_class(self, info): @staticmethod
return self.selected_class() def resolve_selected_class(root: User, info):
return root.selected_class
def resolve_expiry_date(self, info): @staticmethod
if not self.hep_id: # concerns users that already have an (old) account 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 return format(datetime(2020, 7, 31), 'U') # just set some expiry date
else: else:
return self.license_expiry_date return root.license_expiry_date
def resolve_is_teacher(self, info): def resolve_is_teacher(root: User, info):
return self.is_teacher() return root.is_teacher()
def resolve_school_classes(self, info): @staticmethod
if self.selected_class() is None: # then we don't have any class to return 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.none()
return SchoolClass.objects.filter( return SchoolClass.objects.filter(
Q(schoolclassmember__active=True, schoolclassmember__user=self) | Q(pk=self.selected_class().pk)).distinct() Q(schoolclassmember__active=True, schoolclassmember__user=root) | Q(pk=root.selected_class.pk)).distinct()
def resolve_old_classes(self, info): def resolve_old_classes(self: User, info):
return SchoolClass.objects.filter(schoolclassmember__active=False, schoolclassmember__user=self) return SchoolClass.objects.filter(schoolclassmember__active=False, schoolclassmember__user=self)
def resolve_recent_modules(self, info, **kwargs): def resolve_recent_modules(self, info, **kwargs):

View File

@ -41,7 +41,7 @@ class JoinSchoolClassTest(TestCase):
self.assertEqual(result['success'], True) self.assertEqual(result['success'], True)
self.assertEqual(result['schoolClass']['name'], 'Klasse 2B') self.assertEqual(result['schoolClass']['name'], 'Klasse 2B')
self.assertEqual(self.user.school_classes.count(), 2) self.assertEqual(self.user.school_classes.count(), 2)
self.assertEqual(self.user.selected_class().code, 'YYYY') self.assertEqual(self.user.selected_class.code, 'YYYY')
def test_class_already_joined(self): def test_class_already_joined(self):
code = 'YYYY' code = 'YYYY'

View File

@ -82,3 +82,5 @@ class MySchoolClasses(TestCase):
self.assertEqual(len(old_classes), 1) self.assertEqual(len(old_classes), 1)

View File

@ -6,6 +6,7 @@ from graphql_relay import to_global_id
from api.schema import schema from api.schema import schema
from api.utils import get_graphql_mutation, get_object from api.utils import get_graphql_mutation, get_object
from core.factories import UserFactory, TeacherFactory from core.factories import UserFactory, TeacherFactory
from core.tests.base_test import SkillboxTestCase
from users.models import SchoolClass, User from users.models import SchoolClass, User
from users.services import create_users from users.services import create_users
@ -38,18 +39,14 @@ class SchoolClassesTest(TestCase):
self.assertEqual(f'{self.prefix} {user.pk}', class_name) self.assertEqual(f'{self.prefix} {user.pk}', class_name)
class ModifySchoolClassTest(TestCase): class ModifySchoolClassTest(SkillboxTestCase):
def setUp(self): def setUp(self):
create_users() create_users()
self.teacher = User.objects.get(username='teacher') self.teacher = User.objects.get(username='teacher')
self.student = User.objects.get(username='student1') self.student = User.objects.get(username='student1')
request = RequestFactory().get('/') self.client = self.get_client(user=self.teacher)
request.user = self.teacher self.student_client = self.get_client(user=self.student)
student_request = RequestFactory().get('/')
student_request.user = self.student
self.client = Client(schema=schema, context_value=request)
self.student_client = Client(schema=schema, context_value=student_request)
def test_update_school_class(self): def test_update_school_class(self):
class_name = 'The Colbert Show' class_name = 'The Colbert Show'
@ -115,7 +112,7 @@ class ModifySchoolClassTest(TestCase):
school_class = get_object(SchoolClass, id) school_class = get_object(SchoolClass, id)
self.assertEqual(school_class.name, class_name) self.assertEqual(school_class.name, class_name)
self.assertEqual(school_class.get_teacher(), self.teacher) self.assertEqual(school_class.get_teacher(), self.teacher)
self.assertEqual(self.teacher.selected_class().name, class_name) self.assertEqual(self.teacher.selected_class.name, class_name)
def test_create_school_class_fail(self): def test_create_school_class_fail(self):
self.assertEqual(SchoolClass.objects.count(), 2) self.assertEqual(SchoolClass.objects.count(), 2)

View File

@ -1,29 +1,18 @@
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory
from graphene.test import Client
from graphql_relay import to_global_id from graphql_relay import to_global_id
from api.schema import schema
from core.factories import UserFactory from core.factories import UserFactory
from core.tests.base_test import SkillboxTestCase
from users.factories import SchoolClassFactory from users.factories import SchoolClassFactory
from users.models import UserSetting from users.models import SchoolClassMember, UserSetting
class UserSettingTests(TestCase): class UserSettingTests(SkillboxTestCase):
def setUp(self): def setUp(self):
self.user = UserFactory(username='aschi') self.user = UserFactory(username='aschi')
self.class1 = SchoolClassFactory(users=[self.user]) self.class1 = SchoolClassFactory(users=[self.user])
self.class2 = SchoolClassFactory(users=[self.user]) self.class2 = SchoolClassFactory(users=[self.user])
self.class3 = SchoolClassFactory(users=[]) self.class3 = SchoolClassFactory(users=[])
request = RequestFactory().get('/')
request.user = self.user
# adding session
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
self.client = Client(schema=schema, context_value=request)
def make_mutation(self, class_id): def make_mutation(self, class_id):
mutation = ''' mutation = '''
@ -37,7 +26,7 @@ class UserSettingTests(TestCase):
} }
''' '''
return self.client.execute(mutation, variables={ return self.get_client(user=self.user).execute(mutation, variables={
'input': { 'input': {
'id': to_global_id('SchoolClassNode', class_id) 'id': to_global_id('SchoolClassNode', class_id)
} }
@ -50,18 +39,20 @@ class UserSettingTests(TestCase):
selectedClass { selectedClass {
name name
id id
readOnly
} }
} }
} }
''' '''
return self.client.execute(query) return self.get_client(user=self.user).execute(query)
def test_selects_first_class_on_first_call(self): def test_selects_first_class_on_first_call(self):
result = self.make_query() result = self.make_query()
first_class = self.user.school_classes.first() first_class = self.user.school_classes.first()
self.assertIsNone(result.get('errors')) self.assertIsNone(result.get('errors'))
self.assertEqual(result.get('data').get('me').get('selectedClass').get('name'), first_class.name) self.assertEqual(result.get('data').get('me').get('selectedClass').get('name'), first_class.name)
self.assertFalse(result.get('data').get('me').get('selectedClass').get('readOnly'))
def test_returns_selected_class(self): def test_returns_selected_class(self):
selected_class = self.user.school_classes.all()[1] selected_class = self.user.school_classes.all()[1]
@ -73,7 +64,6 @@ class UserSettingTests(TestCase):
selected_class.name) selected_class.name)
def test_user_can_select_class(self): def test_user_can_select_class(self):
selected_class = self.user.school_classes.first() selected_class = self.user.school_classes.first()
setting = UserSetting.objects.create(user=self.user, selected_class=selected_class) setting = UserSetting.objects.create(user=self.user, selected_class=selected_class)
setting.save() setting.save()
@ -87,7 +77,6 @@ class UserSettingTests(TestCase):
selected_class.name) selected_class.name)
def test_user_can_select_class_even_no_settings_exist(self): def test_user_can_select_class_even_no_settings_exist(self):
selected_class = self.user.school_classes.all()[1] selected_class = self.user.school_classes.all()[1]
mutation_result = self.make_mutation(selected_class.pk) mutation_result = self.make_mutation(selected_class.pk)
self.assertIsNone(mutation_result.get('errors')) self.assertIsNone(mutation_result.get('errors'))
@ -106,4 +95,17 @@ class UserSettingTests(TestCase):
mutation_result = self.make_mutation(self.class3.pk) mutation_result = self.make_mutation(self.class3.pk)
self.assertIsNotNone(mutation_result.get('errors')) self.assertIsNotNone(mutation_result.get('errors'))
def test_inactive_class_as_selected_class(self):
selected_class = self.class2
membership = SchoolClassMember.objects.get(user=self.user, school_class=selected_class)
self.assertTrue(membership.active)
membership.active = False
membership.save()
setting = UserSetting.objects.create(user=self.user, selected_class=selected_class)
setting.save()
result = self.make_query()
self.assertIsNone(result.get('errors'))
self.assertTrue(result.get('data').get('me').get('selectedClass').get('readOnly'))