Update types and schema

This commit is contained in:
Ramon Wenger 2023-05-24 20:28:01 +02:00
parent d1664592ce
commit 266356b510
5 changed files with 108 additions and 61 deletions

View File

@ -1,5 +1,13 @@
mutation Coupon($input: CouponInput!) {
coupon(input: $input) {
success
result {
__typename
... on Success {
message
}
... on InvalidCoupon {
reason
}
}
}
}

View File

@ -109,7 +109,9 @@ class CouponTests(TestCase):
school_class = SchoolClass.objects.get(users__in=[self.user])
self.assertIsNotNone(school_class)
self.assertTrue(result.get("data").get("coupon").get("success"))
self.assertEqual(
result.get("data").get("coupon").get("result").get("__typename"), "Success"
)
self.assertTrue(self.user.is_authenticated)
@patch.object(

View File

@ -354,7 +354,7 @@ input CouponInput {
}
type CouponPayload {
success: Boolean
result: RedeemCouponResult
clientMutationId: String
}
@ -508,7 +508,7 @@ type DuplicateContentBlockPayload {
clientMutationId: String
}
type DuplicateName {
type DuplicateName implements FailureNode {
reason: String
}
@ -578,6 +578,10 @@ type InstrumentTypeNode implements Node {
type: String!
}
type InvalidCoupon implements FailureNode {
reason: String
}
scalar JSONString
input JoinClassInput {
@ -938,6 +942,8 @@ type Query {
_debug: DjangoDebug
}
union RedeemCouponResult = Success | InvalidCoupon
type RoomEntryNode implements Node {
id: ID!
title: String!

View File

@ -5,17 +5,27 @@ from django.db import IntegrityError
from django.db.models import Q
from graphene import relay
from graphql_relay import from_global_id
from api.types import FailureNode
from api.utils import get_object
from core.logger import get_logger
from users.inputs import PasswordUpdateInput
from users.models import SchoolClass, SchoolClassMember, Team
from users.schema import CreateSchoolClassResult, CreateTeamResult, FieldError, SchoolClassNode, TeamNode, UpdateError
from users.schema import (
CreateSchoolClassResult,
CreateTeamResult,
FieldError,
SchoolClassNode,
TeamNode,
UpdateError,
DuplicateName,
)
from users.serializers import AvatarUrlSerializer, PasswordSerialzer
logger = get_logger(__name__)
DUPLICATE_REASON = {"reason": "Dieser Name wird bereits verwendet."}
DuplicateFailure = DuplicateName(reason="Dieser Name wird bereits verwendet.")
class CodeNotFoundException(Exception):
@ -29,8 +39,8 @@ class TeacherOnlyMutation(relay.ClientIDMutation):
@classmethod
def mutate(cls, root, info, input):
user = info.context.user
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
raise PermissionError('Permission denied')
if "users.can_manage_school_class_content" not in user.get_role_permissions():
raise PermissionError("Permission denied")
return super().mutate(root, info, input)
@ -44,12 +54,12 @@ class UpdatePassword(relay.ClientIDMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
user = info.context.user
password_data = kwargs.get('password_input')
password_data = kwargs.get("password_input")
serializer = PasswordSerialzer(data=password_data, context=user)
if serializer.is_valid():
user.set_password(password_data['new_password'])
user.set_password(password_data["new_password"])
user.save()
update_session_auth_hash(info.context, user)
return cls(success=True)
@ -75,9 +85,9 @@ class UpdateAvatar(relay.ClientIDMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
user = info.context.user
avatar_data = kwargs.get('avatar_url')
avatar_data = kwargs.get("avatar_url")
serializer = AvatarUrlSerializer(data={'avatar_url': avatar_data})
serializer = AvatarUrlSerializer(data={"avatar_url": avatar_data})
if serializer.is_valid():
user.avatar_url = avatar_data
user.save()
@ -85,7 +95,6 @@ class UpdateAvatar(relay.ClientIDMutation):
errors = []
for key, value in serializer.errors.items():
error = UpdateError(field=key, errors=[])
for field_error in serializer.errors[key]:
@ -102,12 +111,12 @@ class UpdateSetting(relay.ClientIDMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
class_id = kwargs.get('id')
class_id = kwargs.get("id")
school_class = get_object(SchoolClass, class_id)
user = info.context.user
if school_class and school_class not in user.school_classes.all():
raise PermissionDenied('Permission denied: Incorrect school class')
raise PermissionDenied("Permission denied: Incorrect school class")
user.set_selected_class(school_class)
return cls(success=True)
@ -126,22 +135,23 @@ class JoinClass(relay.ClientIDMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
user = info.context.user
code = kwargs.get('code')
code = kwargs.get("code")
try:
school_class = SchoolClass.objects.get(Q(code__iexact=code))
if user not in list(school_class.users.all()):
SchoolClassMember.objects.create(
user=user,
school_class=school_class
)
SchoolClassMember.objects.create(user=user, school_class=school_class)
user.set_selected_class(school_class)
else:
raise CodeNotFoundException('[CAJ] Schüler ist bereits in Klasse') # CAJ = Class Already Joined
raise CodeNotFoundException(
"[CAJ] Schüler ist bereits in Klasse"
) # CAJ = Class Already Joined
return cls(success=True, school_class=school_class)
except SchoolClass.DoesNotExist:
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CNV = Code Not Valid
raise CodeNotFoundException(
"[CNV] Code ist nicht gültig"
) # CNV = Code Not Valid
class AddRemoveMember(relay.ClientIDMutation):
@ -154,18 +164,20 @@ class AddRemoveMember(relay.ClientIDMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
member_id = kwargs.get('member')
school_class_id = kwargs.get('school_class')
active = kwargs.get('active')
member_id = kwargs.get("member")
school_class_id = kwargs.get("school_class")
active = kwargs.get("active")
user = info.context.user
member_pk = from_global_id(member_id)[1]
school_class = get_object(SchoolClass, school_class_id)
if not user.is_teacher() or not school_class.users.filter(pk=user.pk).exists():
raise PermissionError('Permission denied')
raise PermissionError("Permission denied")
school_class_member = SchoolClassMember.objects.get(user__pk=member_pk, school_class=school_class)
school_class_member = SchoolClassMember.objects.get(
user__pk=member_pk, school_class=school_class
)
school_class_member.active = active
school_class_member.save()
@ -182,8 +194,8 @@ class UpdateSchoolClass(TeacherOnlyMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
id = kwargs.get('id')
name = kwargs.get('name')
id = kwargs.get("id")
name = kwargs.get("name")
user = info.context.user
# todo: only allow to edit your own school class
@ -202,7 +214,7 @@ class CreateSchoolClass(TeacherOnlyMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
name = kwargs.get('name')
name = kwargs.get("name")
user = info.context.user
try:
@ -222,7 +234,7 @@ class CreateTeam(TeacherOnlyMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
name = kwargs.get('name')
name = kwargs.get("name")
user = info.context.user
try:
@ -232,7 +244,7 @@ class CreateTeam(TeacherOnlyMutation):
user.save()
return cls(result=team)
except IntegrityError:
return cls(result=DUPLICATE_REASON)
return cls(result=DuplicateFailure)
class UpdateTeam(TeacherOnlyMutation):
@ -245,14 +257,14 @@ class UpdateTeam(TeacherOnlyMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
id = kwargs.get('id')
name = kwargs.get('name')
id = kwargs.get("id")
name = kwargs.get("name")
user = info.context.user
team = get_object(Team, id)
if user not in team.members.all():
logger.info('User not part of this team')
raise PermissionError('Permission denied')
logger.info("User not part of this team")
raise PermissionError("Permission denied")
team.name = name
team.save()
@ -269,7 +281,7 @@ class JoinTeam(TeacherOnlyMutation):
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
user = info.context.user
code = kwargs.get('code')
code = kwargs.get("code")
try:
team = Team.objects.get(Q(code__iexact=code))
user.team = team
@ -277,7 +289,9 @@ class JoinTeam(TeacherOnlyMutation):
return cls(success=True, team=team)
except Team.DoesNotExist:
raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CNV = Code Not Valid
raise CodeNotFoundException(
"[CNV] Code ist nicht gültig"
) # CNV = Code Not Valid
class LeaveTeam(graphene.Mutation):

View File

@ -8,6 +8,7 @@ from graphene import ObjectType, relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from graphql_relay import to_global_id
from api.types import FailureNode
from books.models import Module
from books.schema.queries import ModuleNode
@ -17,25 +18,21 @@ from users.models import SchoolClass, SchoolClassMember, Team, User
class RecentModuleFilter(FilterSet):
class Meta:
model = Module
fields = ('recent_modules',)
fields = ("recent_modules",)
order_by = OrderingFilter(
fields=(
('recent_modules__visited', 'visited'),
)
)
order_by = OrderingFilter(fields=(("recent_modules__visited", "visited"),))
class SchoolClassNode(DjangoObjectType):
pk = graphene.Int()
members = graphene.List('users.schema.ClassMemberNode')
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']
only_fields = ["name", "code", "members", "pk", "read_only"]
filter_fields = ["name"]
interfaces = (relay.Node,)
def resolve_pk(self, *args, **kwargs):
@ -61,11 +58,11 @@ class SchoolClassNode(DjangoObjectType):
class TeamNode(DjangoObjectType):
class Meta:
model = Team
filter_fields = ['name']
filter_fields = ["name"]
interfaces = (relay.Node,)
pk = graphene.Int()
members = graphene.List('users.schema.PublicUserNode')
members = graphene.List("users.schema.PublicUserNode")
def resolve_pk(self, *args, **kwargs):
return self.id
@ -80,7 +77,7 @@ class PublicUserNode(DjangoObjectType):
class Meta:
model = User
only_fields = ['full_name', 'first_name', 'last_name', 'avatar_url']
only_fields = ["full_name", "first_name", "last_name", "avatar_url"]
interfaces = (relay.Node,)
@staticmethod
@ -91,10 +88,22 @@ class PublicUserNode(DjangoObjectType):
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']
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()
@ -104,7 +113,9 @@ class PrivateUserNode(DjangoObjectType):
is_teacher = graphene.Boolean()
old_classes = graphene.List(SchoolClassNode)
school_classes = graphene.List(SchoolClassNode)
recent_modules = DjangoFilterConnectionField(ModuleNode, filterset_class=RecentModuleFilter)
recent_modules = DjangoFilterConnectionField(
ModuleNode, filterset_class=RecentModuleFilter
)
team = graphene.Field(TeamNode)
read_only = graphene.Boolean()
@ -121,7 +132,7 @@ class PrivateUserNode(DjangoObjectType):
@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
return format(datetime(2020, 7, 31), "U") # just set some expiry date
else:
return root.license_expiry_date
@ -133,10 +144,14 @@ class PrivateUserNode(DjangoObjectType):
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()
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)
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/
@ -151,7 +166,8 @@ 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')
user = graphene.Field("users.schema.PublicUserNode")
active = graphene.Boolean()
first_name = graphene.String()
last_name = graphene.String()
@ -160,7 +176,7 @@ class ClassMemberNode(ObjectType):
is_me = graphene.Boolean()
def resolve_id(self, *args):
return to_global_id('PublicUserNode', self.user.pk)
return to_global_id("PublicUserNode", self.user.pk)
def resolve_active(self, *args):
return self.active
@ -183,7 +199,8 @@ class ClassMemberNode(ObjectType):
class DuplicateName(graphene.ObjectType):
reason = graphene.String()
class Meta:
interfaces = (FailureNode,)
class CreateTeamResult(graphene.Union):