Update types and schema
This commit is contained in:
parent
d1664592ce
commit
266356b510
|
|
@ -1,5 +1,13 @@
|
|||
mutation Coupon($input: CouponInput!) {
|
||||
coupon(input: $input) {
|
||||
success
|
||||
result {
|
||||
__typename
|
||||
... on Success {
|
||||
message
|
||||
}
|
||||
... on InvalidCoupon {
|
||||
reason
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in New Issue