diff --git a/common_lib/schema/enums.py b/common_lib/schema/enums.py index 76b374e933bc421a72c81930ddc995df8155a70c..4131ed284ef49be68c0d063e04a5e44bc46209bf 100644 --- a/common_lib/schema/enums.py +++ b/common_lib/schema/enums.py @@ -1,4 +1,5 @@ import graphene +from django.db import models from exercise.models import TeamQuestionnaireState from running_exercise.models import LogType @@ -7,3 +8,21 @@ from user.models import User QuestionnaireStateEnum = graphene.Enum.from_enum(TeamQuestionnaireState.Status) GroupEnum = graphene.Enum.from_enum(User.AuthGroup) LogTypeEnum = graphene.Enum.from_enum(LogType) + + +class ExerciseEventTypeEnum(models.TextChoices): + CREATE = "create" + MODIFY = "modify" + DELETE = "delete" + + @staticmethod + def create() -> "ExerciseEventTypeEnum": + return ExerciseEventTypeEnum(ExerciseEventTypeEnum.CREATE) + + @staticmethod + def modify() -> "ExerciseEventTypeEnum": + return ExerciseEventTypeEnum(ExerciseEventTypeEnum.MODIFY) + + @staticmethod + def delete() -> "ExerciseEventTypeEnum": + return ExerciseEventTypeEnum(ExerciseEventTypeEnum.DELETE) diff --git a/common_lib/schema/types.py b/common_lib/schema/types.py index 0c5dec793b8ca764611433f65a2e5fac79da89be..df7b7c657916f5946856a0abda0591fff144160c 100644 --- a/common_lib/schema/types.py +++ b/common_lib/schema/types.py @@ -1,15 +1,11 @@ -from typing import Any - import graphene -from django.db import models from graphene_django import DjangoObjectType from common_lib.schema.enums import ( GroupEnum, - LogTypeEnum, QuestionnaireStateEnum, + LogTypeEnum, ) -from common_lib.schema.simple_types import Simple from exercise.models import ( Exercise, Team, @@ -28,6 +24,7 @@ from exercise_definition.models import ( Inject, Response, FileInfo, + Role, Content, Control, Channel, @@ -41,78 +38,75 @@ from running_exercise.models import ( ActionLog, EmailThread, Email, + ThreadParticipant, ToolDetails, InjectDetails, CustomInjectDetails, QuestionnaireAnswer, - ThreadParticipant, ) -from user.models import InstructorOfExercise, User, Tag +from user.models import User, Tag, InstructorOfExercise -def select_by_group(user: User, all: Any, some: Any) -> Any: - if user.group >= User.AuthGroup.INSTRUCTOR: - return all - return some +class RestrictedUser(DjangoObjectType): + class Meta: + model = User + exclude = ("definitions", "exercises", "teams", "password") + + group = graphene.Field(GroupEnum) + + +class RestrictedExercise(DjangoObjectType): + id = graphene.ID(source="id") + uuid = graphene.UUID(source="uuid") + + class Meta: + model = Exercise + fields = () + + +class RestrictedTeam(DjangoObjectType): + class Meta: + model = Team + fields = ("id", "exercise", "name", "role", "email_address") + + +class ExerciseDefinitionType(graphene.ObjectType): + id = graphene.Int() + name = graphene.String() class ToolType(DjangoObjectType): class Meta: model = Tool + exclude = ["responses"] - definition = graphene.Field(Simple.DefinitionType, required=True) + definition = graphene.Field(ExerciseDefinitionType) class DefinitionType(DjangoObjectType): class Meta: model = Definition + exclude = ["milestones"] - files = graphene.List(graphene.NonNull(Simple.FileInfoType), required=True) - learning_objectives = graphene.List( - graphene.NonNull(Simple.LearningObjectiveType), - required=True, - ) - addresses = graphene.List( - graphene.NonNull(Simple.EmailAddressType), required=True - ) - injects = graphene.List( - graphene.NonNull(Simple.DefinitionInjectType), required=True - ) - tools = graphene.List(graphene.NonNull(Simple.ToolType), required=True) - roles = graphene.List( - graphene.NonNull(Simple.DefinitionRoleType), required=True - ) - channels = graphene.List( - graphene.NonNull(Simple.DefinitionChannelType), required=True - ) - questionnaires = graphene.List( - graphene.NonNull(Simple.QuestionnaireType), required=True - ) - exercises = graphene.List( - graphene.NonNull(Simple.ExerciseType), required=True - ) - user_set = graphene.List(graphene.NonNull(Simple.UserType), required=True) + tools = graphene.List(graphene.NonNull(ToolType), required=True) + user_set = graphene.List(RestrictedUser) def resolve_user_set(self, info): return self.user_set.all() +class DefinitionRoleType(DjangoObjectType): + class Meta: + model = Role + + class ExerciseType(DjangoObjectType): class Meta: model = Exercise - definition = graphene.Field(Simple.DefinitionType) - teams = graphene.List(graphene.NonNull(Simple.TeamType), required=True) - email_participants = graphene.List( - graphene.NonNull(Simple.EmailParticipantType), required=True - ) - user_set = graphene.List(graphene.NonNull(Simple.UserType), required=True) - threads = graphene.List( - graphene.NonNull(Simple.EmailThreadType), required=True - ) - - def resolve_definition(self, info): - return select_by_group(info.context.user, self.definition, None) + # change the definition field type to hide some definition fields + definition = graphene.Field(ExerciseDefinitionType) + user_set = graphene.List(RestrictedUser) def resolve_teams(self, info): user = info.context.user @@ -128,43 +122,21 @@ class ExerciseType(DjangoObjectType): # trainee or instructor assigned to specific team return self.teams.filter(users__user=user) - def resolve_email_participants(self, info): - return select_by_group( - info.context.user, - self.email_participants.all(), - self.email_participants.filter( - team__in=info.context.user.teams.all() - ), - ) - def resolve_user_set(self, info): - return select_by_group( - info.context.user, self.user_set.all(), User.objects.none() - ) - - def resolve_threads(self, info): - return select_by_group( - info.context.user, - self.threads.all(), - self.threads.filter( - participants__team__in=info.context.user.teams.all() - ), - ) + if info.context.user.group == User.AuthGroup.TRAINEE: + return User.objects.none() + return self.user_set.all() class TeamType(DjangoObjectType): class Meta: model = Team - exercise = graphene.Field(Simple.ExerciseType, required=True) - email_address = graphene.Field(Simple.EmailAddressType, required=True) - team_questionnaire_states = graphene.List( - graphene.NonNull(Simple.TeamQuestionnaireStateType), required=True - ) - logs = graphene.List(graphene.NonNull(Simple.ActionLogType), required=True) - user_set = graphene.List(graphene.NonNull(Simple.UserType), required=True) + user_set = graphene.List(RestrictedUser) def resolve_user_set(self, info): + if info.context.user.group == User.AuthGroup.TRAINEE: + return User.objects.none() return self.user_set.all() @@ -172,8 +144,6 @@ class ContentType(DjangoObjectType): class Meta: model = Content - file_info = graphene.Field(Simple.FileInfoType, required=True) - class ControlType(DjangoObjectType): class Meta: @@ -189,53 +159,45 @@ class ToolDetailsType(DjangoObjectType): class Meta: model = ToolDetails - tool = graphene.Field(Simple.ToolType, required=True) - content = graphene.Field(ContentType, required=True) - user = graphene.Field(Simple.UserType) + user = graphene.Field(RestrictedUser) + tool = graphene.Field(ToolType, required=True) class InjectDetailsType(DjangoObjectType): class Meta: model = InjectDetails - inject = graphene.Field(Simple.DefinitionInjectType, required=True) - class CustomInjectDetailsType(DjangoObjectType): class Meta: model = CustomInjectDetails - user = graphene.Field(Simple.UserType) + user = graphene.Field(RestrictedUser) class EmailType(DjangoObjectType): class Meta: model = Email - thread = graphene.Field(Simple.EmailThreadType, required=True) - sender = graphene.Field(Simple.EmailParticipantType, required=True) - user = graphene.Field(Simple.UserType) + user = graphene.Field(RestrictedUser) class QuestionType(DjangoObjectType): class Meta: model = Question - questionnaire = graphene.Field(Simple.QuestionnaireType, required=True) - def resolve_correct(self, info): - return select_by_group(info.context.user, self.correct, 0) + user = info.context.user + + if user.group == User.AuthGroup.TRAINEE: + return 0 + return self.correct class QuestionnaireType(DjangoObjectType): class Meta: model = Questionnaire - definition = graphene.Field(Simple.DefinitionType, required=True) - team_questionnaire_states = graphene.List( - graphene.NonNull(Simple.TeamQuestionnaireStateType), required=True - ) - class ActionLogDetails(graphene.Union): class Meta: @@ -253,112 +215,88 @@ class ActionLogType(DjangoObjectType): model = ActionLog exclude = ["details_id"] - team = graphene.Field(Simple.TeamType, required=True) - channel = graphene.Field(Simple.DefinitionChannelType, required=True) - type = graphene.Field(LogTypeEnum, required=True) details = graphene.Field(ActionLogDetails, required=True) + type = graphene.Field(LogTypeEnum, required=True) class MilestoneType(DjangoObjectType): class Meta: model = Milestone - - definition = graphene.Field(Simple.DefinitionType, required=True) - milestone_states = graphene.List( - graphene.NonNull(Simple.MilestoneStateType), required=True - ) - activity = graphene.Field(Simple.LearningActivityType, required=True) + exclude = ["milestone_states", "definition"] class MilestoneStateType(DjangoObjectType): class Meta: model = MilestoneState - milestone = graphene.Field(MilestoneType, required=True) - activity = graphene.Field(Simple.TeamLearningActivityType, required=True) - team_ids = graphene.List(graphene.NonNull(graphene.ID), required=True) + team_ids = graphene.List(graphene.ID) def resolve_team_ids(self, info): return [team.id for team in self.team_state.teams.all()] -class ThreadParticipantType(DjangoObjectType): - class Meta: - model = ThreadParticipant - - class EmailThreadType(DjangoObjectType): class Meta: model = EmailThread - participants = graphene.List( - graphene.NonNull(Simple.EmailParticipantType), required=True - ) - exercise = graphene.Field(Simple.ExerciseType, required=True) - emails = graphene.List(graphene.NonNull(EmailType), required=True) last_email = graphene.Field(EmailType, required=False) - def resolve_participants(self, info): - return self.participants.all() - def resolve_last_email(self, info): return self.emails.order_by("timestamp").last() +class ExtendedToolType(DjangoObjectType): + class Meta: + model = Tool + + definition = graphene.Field(ExerciseDefinitionType) + + class ToolResponseType(DjangoObjectType): class Meta: model = Response - tool = graphene.Field(Simple.ToolType, required=True) - class EmailAddressType(DjangoObjectType): class Meta: model = EmailAddress - definition = graphene.Field(Simple.DefinitionType, required=True) + definition = graphene.Field(ExerciseDefinitionType) class EmailTemplateType(DjangoObjectType): class Meta: model = EmailTemplate + exclude = ["email_address"] sender = graphene.String() - email_address = graphene.Field(Simple.EmailAddressType, required=True) def resolve_sender(self, info): return self.email_address.address +class ThreadParticipantType(DjangoObjectType): + class Meta: + model = ThreadParticipant + + class EmailParticipantType(DjangoObjectType): class Meta: model = EmailParticipant - exercise = graphene.Field(Simple.ExerciseType, required=True) - definition_address = graphene.Field(Simple.EmailAddressType) - team = graphene.Field(Simple.TeamType) - threads = graphene.List( - graphene.NonNull(Simple.EmailThreadType), required=True - ) - emails = graphene.List(graphene.NonNull(Simple.EmailType), required=True) - class DefinitionInjectType(DjangoObjectType): class Meta: model = Inject - exclude = ["alternatives"] - - definition = graphene.Field(Simple.DefinitionType, required=True) + exclude = ["definition", "alternatives"] class FileInfoType(DjangoObjectType): class Meta: model = FileInfo - definition = graphene.Field(Simple.DefinitionType, required=True) - -class ExerciseConfigType(graphene.ObjectType): +class GrapheneConfig(graphene.ObjectType): exercise_duration = graphene.Int() email_between_teams = graphene.Boolean() show_exercise_time = graphene.Boolean() @@ -367,19 +305,15 @@ class ExerciseConfigType(graphene.ObjectType): class UserType(DjangoObjectType): + definitions = graphene.List(ExerciseDefinitionType) + exercises = graphene.List(RestrictedExercise) + teams = graphene.List(RestrictedTeam) + group = graphene.Field(GroupEnum) + class Meta: model = User exclude = ["password"] - definitions = graphene.List( - graphene.NonNull(Simple.DefinitionType), required=True - ) - exercises = graphene.List( - graphene.NonNull(Simple.ExerciseType), required=True - ) - teams = graphene.List(graphene.NonNull(Simple.TeamType), required=True) - group = graphene.Field(GroupEnum, required=True) - def resolve_definitions(self, info): return self.definitions.all() @@ -393,95 +327,60 @@ class UserType(DjangoObjectType): class TagType(DjangoObjectType): class Meta: model = Tag - exclude_fields = ["user_set"] - - -class ExerciseEventTypeEnum(models.TextChoices): - CREATE = "create" - MODIFY = "modify" - DELETE = "delete" - - @staticmethod - def create() -> "ExerciseEventTypeEnum": - return ExerciseEventTypeEnum(ExerciseEventTypeEnum.CREATE) - - @staticmethod - def modify() -> "ExerciseEventTypeEnum": - return ExerciseEventTypeEnum(ExerciseEventTypeEnum.MODIFY) - - @staticmethod - def delete() -> "ExerciseEventTypeEnum": - return ExerciseEventTypeEnum(ExerciseEventTypeEnum.DELETE) + exclude_fields = ("user_set",) class DefinitionChannelType(DjangoObjectType): class Meta: model = Channel - definition = graphene.Field(Simple.DefinitionType, required=True) + +class TeamQuestionnaireStateType(DjangoObjectType): + class Meta: + model = TeamQuestionnaireState + + status = graphene.Field(QuestionnaireStateEnum) class QuestionnaireAnswerType(DjangoObjectType): class Meta: model = QuestionnaireAnswer - team_questionnaire_state = graphene.Field( - Simple.TeamQuestionnaireStateType, required=True - ) - def resolve_is_correct(self, info): - return select_by_group(info.context.user, self.is_correct, None) - - -class TeamQuestionnaireStateType(DjangoObjectType): - class Meta: - model = TeamQuestionnaireState + user = info.context.user + if user.is_anonymous: + return self.is_correct - status = graphene.Field(QuestionnaireStateEnum, required=True) - team = graphene.Field(Simple.TeamType, required=True) - questionnaire = graphene.Field(Simple.QuestionnaireType, required=True) - answers = graphene.List( - graphene.NonNull(QuestionnaireAnswerType), required=True - ) + if user.group == User.AuthGroup.TRAINEE: + return None + return self.is_correct class LearningObjectiveType(DjangoObjectType): class Meta: model = LearningObjective - definition = graphene.Field(Simple.DefinitionType, required=True) - class LearningActivityType(DjangoObjectType): class Meta: model = LearningActivity - objective = graphene.Field(Simple.LearningObjectiveType, required=True) - class TeamLearningObjectiveType(DjangoObjectType): + reached = graphene.Boolean() + class Meta: model = TeamLearningObjective - objective = graphene.Field(Simple.LearningObjectiveType, required=True) - reached = graphene.Boolean() - def resolve_reached(self, info): return self.reached() class TeamLearningActivityType(DjangoObjectType): + reached = graphene.Boolean() + class Meta: model = TeamLearningActivity - team_objective = graphene.Field( - Simple.TeamLearningObjectiveType, required=True - ) - activity = graphene.Field(Simple.LearningActivityType, required=True) - milestone_states = graphene.List( - graphene.NonNull(Simple.MilestoneStateType), required=True - ) - reached = graphene.Boolean() - def resolve_reached(self, info): return self.reached() diff --git a/common_lib/schema/types_new.py b/common_lib/schema/types_new.py new file mode 100644 index 0000000000000000000000000000000000000000..0c5dec793b8ca764611433f65a2e5fac79da89be --- /dev/null +++ b/common_lib/schema/types_new.py @@ -0,0 +1,487 @@ +from typing import Any + +import graphene +from django.db import models +from graphene_django import DjangoObjectType + +from common_lib.schema.enums import ( + GroupEnum, + LogTypeEnum, + QuestionnaireStateEnum, +) +from common_lib.schema.simple_types import Simple +from exercise.models import ( + Exercise, + Team, + MilestoneState, + EmailParticipant, + TeamQuestionnaireState, + TeamLearningObjective, + TeamLearningActivity, +) +from exercise_definition.models import ( + Milestone, + Definition, + Tool, + EmailAddress, + EmailTemplate, + Inject, + Response, + FileInfo, + Content, + Control, + Channel, + Overlay, + Questionnaire, + Question, + LearningObjective, + LearningActivity, +) +from running_exercise.models import ( + ActionLog, + EmailThread, + Email, + ToolDetails, + InjectDetails, + CustomInjectDetails, + QuestionnaireAnswer, + ThreadParticipant, +) +from user.models import InstructorOfExercise, User, Tag + + +def select_by_group(user: User, all: Any, some: Any) -> Any: + if user.group >= User.AuthGroup.INSTRUCTOR: + return all + return some + + +class ToolType(DjangoObjectType): + class Meta: + model = Tool + + definition = graphene.Field(Simple.DefinitionType, required=True) + + +class DefinitionType(DjangoObjectType): + class Meta: + model = Definition + + files = graphene.List(graphene.NonNull(Simple.FileInfoType), required=True) + learning_objectives = graphene.List( + graphene.NonNull(Simple.LearningObjectiveType), + required=True, + ) + addresses = graphene.List( + graphene.NonNull(Simple.EmailAddressType), required=True + ) + injects = graphene.List( + graphene.NonNull(Simple.DefinitionInjectType), required=True + ) + tools = graphene.List(graphene.NonNull(Simple.ToolType), required=True) + roles = graphene.List( + graphene.NonNull(Simple.DefinitionRoleType), required=True + ) + channels = graphene.List( + graphene.NonNull(Simple.DefinitionChannelType), required=True + ) + questionnaires = graphene.List( + graphene.NonNull(Simple.QuestionnaireType), required=True + ) + exercises = graphene.List( + graphene.NonNull(Simple.ExerciseType), required=True + ) + user_set = graphene.List(graphene.NonNull(Simple.UserType), required=True) + + def resolve_user_set(self, info): + return self.user_set.all() + + +class ExerciseType(DjangoObjectType): + class Meta: + model = Exercise + + definition = graphene.Field(Simple.DefinitionType) + teams = graphene.List(graphene.NonNull(Simple.TeamType), required=True) + email_participants = graphene.List( + graphene.NonNull(Simple.EmailParticipantType), required=True + ) + user_set = graphene.List(graphene.NonNull(Simple.UserType), required=True) + threads = graphene.List( + graphene.NonNull(Simple.EmailThreadType), required=True + ) + + def resolve_definition(self, info): + return select_by_group(info.context.user, self.definition, None) + + def resolve_teams(self, info): + user = info.context.user + if user.group == User.AuthGroup.ADMIN: + return self.teams.all() + if ( + user.group == User.AuthGroup.INSTRUCTOR + and InstructorOfExercise.objects.filter( + exercise=self, user=user + ).exists() + ): # Instructor is assigned as an instructor of the exercise + return self.teams.all() + # trainee or instructor assigned to specific team + return self.teams.filter(users__user=user) + + def resolve_email_participants(self, info): + return select_by_group( + info.context.user, + self.email_participants.all(), + self.email_participants.filter( + team__in=info.context.user.teams.all() + ), + ) + + def resolve_user_set(self, info): + return select_by_group( + info.context.user, self.user_set.all(), User.objects.none() + ) + + def resolve_threads(self, info): + return select_by_group( + info.context.user, + self.threads.all(), + self.threads.filter( + participants__team__in=info.context.user.teams.all() + ), + ) + + +class TeamType(DjangoObjectType): + class Meta: + model = Team + + exercise = graphene.Field(Simple.ExerciseType, required=True) + email_address = graphene.Field(Simple.EmailAddressType, required=True) + team_questionnaire_states = graphene.List( + graphene.NonNull(Simple.TeamQuestionnaireStateType), required=True + ) + logs = graphene.List(graphene.NonNull(Simple.ActionLogType), required=True) + user_set = graphene.List(graphene.NonNull(Simple.UserType), required=True) + + def resolve_user_set(self, info): + return self.user_set.all() + + +class ContentType(DjangoObjectType): + class Meta: + model = Content + + file_info = graphene.Field(Simple.FileInfoType, required=True) + + +class ControlType(DjangoObjectType): + class Meta: + model = Control + + +class OverlayType(DjangoObjectType): + class Meta: + model = Overlay + + +class ToolDetailsType(DjangoObjectType): + class Meta: + model = ToolDetails + + tool = graphene.Field(Simple.ToolType, required=True) + content = graphene.Field(ContentType, required=True) + user = graphene.Field(Simple.UserType) + + +class InjectDetailsType(DjangoObjectType): + class Meta: + model = InjectDetails + + inject = graphene.Field(Simple.DefinitionInjectType, required=True) + + +class CustomInjectDetailsType(DjangoObjectType): + class Meta: + model = CustomInjectDetails + + user = graphene.Field(Simple.UserType) + + +class EmailType(DjangoObjectType): + class Meta: + model = Email + + thread = graphene.Field(Simple.EmailThreadType, required=True) + sender = graphene.Field(Simple.EmailParticipantType, required=True) + user = graphene.Field(Simple.UserType) + + +class QuestionType(DjangoObjectType): + class Meta: + model = Question + + questionnaire = graphene.Field(Simple.QuestionnaireType, required=True) + + def resolve_correct(self, info): + return select_by_group(info.context.user, self.correct, 0) + + +class QuestionnaireType(DjangoObjectType): + class Meta: + model = Questionnaire + + definition = graphene.Field(Simple.DefinitionType, required=True) + team_questionnaire_states = graphene.List( + graphene.NonNull(Simple.TeamQuestionnaireStateType), required=True + ) + + +class ActionLogDetails(graphene.Union): + class Meta: + types = [ + ToolDetailsType, + InjectDetailsType, + CustomInjectDetailsType, + EmailType, + QuestionnaireType, + ] + + +class ActionLogType(DjangoObjectType): + class Meta: + model = ActionLog + exclude = ["details_id"] + + team = graphene.Field(Simple.TeamType, required=True) + channel = graphene.Field(Simple.DefinitionChannelType, required=True) + type = graphene.Field(LogTypeEnum, required=True) + details = graphene.Field(ActionLogDetails, required=True) + + +class MilestoneType(DjangoObjectType): + class Meta: + model = Milestone + + definition = graphene.Field(Simple.DefinitionType, required=True) + milestone_states = graphene.List( + graphene.NonNull(Simple.MilestoneStateType), required=True + ) + activity = graphene.Field(Simple.LearningActivityType, required=True) + + +class MilestoneStateType(DjangoObjectType): + class Meta: + model = MilestoneState + + milestone = graphene.Field(MilestoneType, required=True) + activity = graphene.Field(Simple.TeamLearningActivityType, required=True) + team_ids = graphene.List(graphene.NonNull(graphene.ID), required=True) + + def resolve_team_ids(self, info): + return [team.id for team in self.team_state.teams.all()] + + +class ThreadParticipantType(DjangoObjectType): + class Meta: + model = ThreadParticipant + + +class EmailThreadType(DjangoObjectType): + class Meta: + model = EmailThread + + participants = graphene.List( + graphene.NonNull(Simple.EmailParticipantType), required=True + ) + exercise = graphene.Field(Simple.ExerciseType, required=True) + emails = graphene.List(graphene.NonNull(EmailType), required=True) + last_email = graphene.Field(EmailType, required=False) + + def resolve_participants(self, info): + return self.participants.all() + + def resolve_last_email(self, info): + return self.emails.order_by("timestamp").last() + + +class ToolResponseType(DjangoObjectType): + class Meta: + model = Response + + tool = graphene.Field(Simple.ToolType, required=True) + + +class EmailAddressType(DjangoObjectType): + class Meta: + model = EmailAddress + + definition = graphene.Field(Simple.DefinitionType, required=True) + + +class EmailTemplateType(DjangoObjectType): + class Meta: + model = EmailTemplate + + sender = graphene.String() + email_address = graphene.Field(Simple.EmailAddressType, required=True) + + def resolve_sender(self, info): + return self.email_address.address + + +class EmailParticipantType(DjangoObjectType): + class Meta: + model = EmailParticipant + + exercise = graphene.Field(Simple.ExerciseType, required=True) + definition_address = graphene.Field(Simple.EmailAddressType) + team = graphene.Field(Simple.TeamType) + threads = graphene.List( + graphene.NonNull(Simple.EmailThreadType), required=True + ) + emails = graphene.List(graphene.NonNull(Simple.EmailType), required=True) + + +class DefinitionInjectType(DjangoObjectType): + class Meta: + model = Inject + exclude = ["alternatives"] + + definition = graphene.Field(Simple.DefinitionType, required=True) + + +class FileInfoType(DjangoObjectType): + class Meta: + model = FileInfo + + definition = graphene.Field(Simple.DefinitionType, required=True) + + +class ExerciseConfigType(graphene.ObjectType): + exercise_duration = graphene.Int() + email_between_teams = graphene.Boolean() + show_exercise_time = graphene.Boolean() + enable_roles = graphene.Boolean() + custom_email_suffix = graphene.String() + + +class UserType(DjangoObjectType): + class Meta: + model = User + exclude = ["password"] + + definitions = graphene.List( + graphene.NonNull(Simple.DefinitionType), required=True + ) + exercises = graphene.List( + graphene.NonNull(Simple.ExerciseType), required=True + ) + teams = graphene.List(graphene.NonNull(Simple.TeamType), required=True) + group = graphene.Field(GroupEnum, required=True) + + def resolve_definitions(self, info): + return self.definitions.all() + + def resolve_exercises(self, info): + return self.exercises.all() + + def resolve_teams(self, info): + return self.teams.all() + + +class TagType(DjangoObjectType): + class Meta: + model = Tag + exclude_fields = ["user_set"] + + +class ExerciseEventTypeEnum(models.TextChoices): + CREATE = "create" + MODIFY = "modify" + DELETE = "delete" + + @staticmethod + def create() -> "ExerciseEventTypeEnum": + return ExerciseEventTypeEnum(ExerciseEventTypeEnum.CREATE) + + @staticmethod + def modify() -> "ExerciseEventTypeEnum": + return ExerciseEventTypeEnum(ExerciseEventTypeEnum.MODIFY) + + @staticmethod + def delete() -> "ExerciseEventTypeEnum": + return ExerciseEventTypeEnum(ExerciseEventTypeEnum.DELETE) + + +class DefinitionChannelType(DjangoObjectType): + class Meta: + model = Channel + + definition = graphene.Field(Simple.DefinitionType, required=True) + + +class QuestionnaireAnswerType(DjangoObjectType): + class Meta: + model = QuestionnaireAnswer + + team_questionnaire_state = graphene.Field( + Simple.TeamQuestionnaireStateType, required=True + ) + + def resolve_is_correct(self, info): + return select_by_group(info.context.user, self.is_correct, None) + + +class TeamQuestionnaireStateType(DjangoObjectType): + class Meta: + model = TeamQuestionnaireState + + status = graphene.Field(QuestionnaireStateEnum, required=True) + team = graphene.Field(Simple.TeamType, required=True) + questionnaire = graphene.Field(Simple.QuestionnaireType, required=True) + answers = graphene.List( + graphene.NonNull(QuestionnaireAnswerType), required=True + ) + + +class LearningObjectiveType(DjangoObjectType): + class Meta: + model = LearningObjective + + definition = graphene.Field(Simple.DefinitionType, required=True) + + +class LearningActivityType(DjangoObjectType): + class Meta: + model = LearningActivity + + objective = graphene.Field(Simple.LearningObjectiveType, required=True) + + +class TeamLearningObjectiveType(DjangoObjectType): + class Meta: + model = TeamLearningObjective + + objective = graphene.Field(Simple.LearningObjectiveType, required=True) + reached = graphene.Boolean() + + def resolve_reached(self, info): + return self.reached() + + +class TeamLearningActivityType(DjangoObjectType): + class Meta: + model = TeamLearningActivity + + team_objective = graphene.Field( + Simple.TeamLearningObjectiveType, required=True + ) + activity = graphene.Field(Simple.LearningActivityType, required=True) + milestone_states = graphene.List( + graphene.NonNull(Simple.MilestoneStateType), required=True + ) + reached = graphene.Boolean() + + def resolve_reached(self, info): + return self.reached() diff --git a/common_lib/subscription_handler.py b/common_lib/subscription_handler.py index cad2ac310c5c7f12b6ae14c00480d34db0a0a86a..fe919424d187b471c5da56dcbde285d137a23137 100644 --- a/common_lib/subscription_handler.py +++ b/common_lib/subscription_handler.py @@ -1,6 +1,6 @@ from typing import List -from common_lib.schema.types import ExerciseEventTypeEnum +from common_lib.schema.enums import ExerciseEventTypeEnum from common_lib.utils import get_subscription_group from exercise.models import ( Team, diff --git a/exercise/lib/exercise_manager.py b/exercise/lib/exercise_manager.py index e2a5b1fff36c5c7a6d795f8abd908834bf2064df..e3062b1269f1b42a9c6384cc78e59f8dd6970506 100644 --- a/exercise/lib/exercise_manager.py +++ b/exercise/lib/exercise_manager.py @@ -8,7 +8,7 @@ from rest_framework.request import Request from common_lib.exceptions import ExerciseOperationException from common_lib.logger import logger, log_user_msg -from common_lib.schema.types import ExerciseEventTypeEnum +from common_lib.schema.enums import ExerciseEventTypeEnum from common_lib.subscription_handler import SubscriptionHandler from common_lib.utils import get_model from exercise.graphql_inputs import CreateExerciseInput, ConfigOverrideInput diff --git a/exercise/schema/query.py b/exercise/schema/query.py index ae2285b4f47f4ebce42de03c67e677feba884f72..8f104b607b7455f29e9da1154f97fd2480cf36fe 100644 --- a/exercise/schema/query.py +++ b/exercise/schema/query.py @@ -11,6 +11,8 @@ from common_lib.schema.types import ( MilestoneType, FileInfoType, DefinitionChannelType, + LearningObjectiveType, + QuestionnaireType, ) from common_lib.utils import get_model from exercise.models import Exercise @@ -20,6 +22,8 @@ from exercise_definition.models import ( Milestone, FileInfo, Channel, + LearningObjective, + Questionnaire, ) from user.models import User @@ -94,6 +98,17 @@ class Query(graphene.ObjectType): description="Retrieve all channels for an exercise", ) + learning_objectives = graphene.List( + LearningObjectiveType, + exercise_id=graphene.ID(required=True), + description="Retrieve all learning objectives for an exercise", + ) + questionnaires = graphene.List( + QuestionnaireType, + exercise_id=graphene.ID(required=True), + description="Retrieve all questionnaires for an exercise", + ) + @protected(User.AuthGroup.TRAINEE) def resolve_exercises( self, @@ -186,3 +201,21 @@ class Query(graphene.ObjectType): ) -> List[Channel]: exercise_access(info.context, int(exercise_id)) return Channel.objects.filter(definition__exercises__in=[exercise_id]) + + @protected(User.AuthGroup.INSTRUCTOR) + def resolve_learning_objectives( + self, info, exercise_id: str + ) -> List[LearningObjective]: + exercise_access(info.context, int(exercise_id)) + return LearningObjective.objects.filter( + definition__exercises__in=[exercise_id] + ) + + @protected(User.AuthGroup.INSTRUCTOR) + def resolve_questionnaires( + self, info, exercise_id: str + ) -> List[Questionnaire]: + exercise_access(info.context, int(exercise_id)) + return Questionnaire.objects.filter( + definition__exercises__in=[exercise_id] + ) diff --git a/exercise/subscription.py b/exercise/subscription.py index 390c4a461671f1258b84f4604c265560de8f56e7..61fe9db39867fe76054624d2600b098520d0c24c 100644 --- a/exercise/subscription.py +++ b/exercise/subscription.py @@ -2,7 +2,7 @@ import channels_graphql_ws import graphene from aai.decorators import protected -from common_lib.schema.types import ExerciseEventTypeEnum +from common_lib.schema.enums import ExerciseEventTypeEnum from common_lib.schema.types import ExerciseType from user.models import User diff --git a/running_exercise/lib/loop_thread.py b/running_exercise/lib/loop_thread.py index 542f8ecdfc9b5d053d9869332fef88ae281845eb..fdf6bc4fe658fc92eaffe2b914558f10aa2faff2 100644 --- a/running_exercise/lib/loop_thread.py +++ b/running_exercise/lib/loop_thread.py @@ -2,7 +2,7 @@ import time from threading import Thread from common_lib.logger import logger -from common_lib.schema.types import ExerciseEventTypeEnum +from common_lib.schema.enums import ExerciseEventTypeEnum from common_lib.subscription_handler import SubscriptionHandler from common_lib.utils import get_model from exercise.models import Exercise diff --git a/running_exercise/schema/query.py b/running_exercise/schema/query.py index ee248c4b29979490d8eb059f420baf7123962ce5..5b7ea3b5eb803ec46362124c4b34edfcc04f16ac 100644 --- a/running_exercise/schema/query.py +++ b/running_exercise/schema/query.py @@ -16,10 +16,10 @@ from common_lib.schema.types import ( EmailParticipantType, EmailTemplateType, TeamType, - ExerciseConfigType, + GrapheneConfig, TeamQuestionnaireStateType, + ExtendedToolType, ) -from common_lib.schema.simple_types import Simple from common_lib.utils import get_model, has_role from exercise.models import ( Team, @@ -54,7 +54,7 @@ class Query(graphene.ObjectType): description="Retrieve all team roles for the running exercise", ) team_tools = graphene.List( - Simple.ToolType, + ExtendedToolType, team_id=graphene.ID(required=True), description="Retrieve all tools available to the specific team", ) @@ -79,6 +79,11 @@ class Query(graphene.ObjectType): channel_id=graphene.ID(required=True), description="Retrieve all team action logs for the specific channel", ) + team_milestone = graphene.Field( + MilestoneStateType, + milestone_state_id=graphene.ID(required=True), + description="Retrieve a specific milestone state", + ) team_milestones = graphene.List( MilestoneStateType, team_id=graphene.ID(required=True), @@ -150,7 +155,7 @@ class Query(graphene.ObjectType): description="Retrieve the amount of time left in the running exercise in seconds" ) exercise_config = graphene.Field( - ExerciseConfigType, + GrapheneConfig, exercise_id=graphene.ID(required=True), description="Retrieve the config for the current running exercise", ) @@ -259,6 +264,14 @@ class Query(graphene.ObjectType): team_access(info.context, int(team_id)) return ActionLog.objects.filter(team_id=team_id, channel_id=channel_id) + @protected(User.AuthGroup.INSTRUCTOR) + def resolve_team_milestone( + self, info, milestone_state_id: str + ) -> MilestoneState: + milestone_state = get_model(MilestoneState, id=int(milestone_state_id)) + exercise_access(info.context, milestone_state.team_state.exercise_id) + return milestone_state + @protected(User.AuthGroup.INSTRUCTOR) def resolve_team_milestones( self, info, team_id: str, visible_only: bool = True @@ -371,14 +384,12 @@ class Query(graphene.ObjectType): return max(0, exercise_duration_s - int(running_exercise.elapsed_s)) @protected(User.AuthGroup.TRAINEE) - def resolve_exercise_config( - self, info, exercise_id: str - ) -> ExerciseConfigType: + def resolve_exercise_config(self, info, exercise_id: str) -> GrapheneConfig: exercise_access(info.context, int(exercise_id)) exercise = get_model(Exercise, id=int(exercise_id)) config: Config = exercise.config - return ExerciseConfigType( + return GrapheneConfig( exercise_duration=config.exercise_duration, email_between_teams=config.has_enabled( Feature.email_between_teams()