diff --git a/core/serializers/student.py b/core/serializers/student.py index 211069c3aeac257ddc3f8a3f73c671c224583366..32f7c3790f64f7a9eae182f120d3ac81e61b0f25 100644 --- a/core/serializers/student.py +++ b/core/serializers/student.py @@ -23,7 +23,7 @@ class StudentInfoSerializer(DynamicFieldsModelSerializer): 'passes_exam') -class StudentInfoSerializerForListView(DynamicFieldsModelSerializer): +class StudentInfoForListViewSerializer(DynamicFieldsModelSerializer): name = serializers.ReadOnlyField(source='user.fullname') user = serializers.ReadOnlyField(source='user.username') user_pk = serializers.ReadOnlyField(source='user.pk') diff --git a/core/urls.py b/core/urls.py index 6772c660a2c57b4babb356f36c9b2f643dc86c50..3e3014d9e5ea1425f870441fc2113bcdf65dd03d 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,11 @@ -from django.urls import path +from django.urls import path, re_path +from drf_yasg.views import get_schema_view +from drf_yasg import openapi from rest_framework.routers import DefaultRouter +from rest_framework.permissions import IsAdminUser, AllowAny from core import views +from core.permissions import IsReviewer # Create a router and register our viewsets with it. router = DefaultRouter() @@ -20,6 +24,17 @@ router.register('assignment', views.AssignmentApiViewSet) router.register('statistics', views.StatisticsEndpoint, base_name='statistics') router.register('user', views.UserAccountViewSet, base_name='user') +schema_view = get_schema_view( + openapi.Info( + title="Grady API", + default_version='v1', + description="Blub", + ), + # validators=['flex', 'ssv'], + public=True, + permission_classes=(AllowAny,), +) + # regular views that are not viewsets regular_views_urlpatterns = [ path('student-page/', @@ -33,6 +48,12 @@ regular_views_urlpatterns = [ views.get_jwt_expiration_delta, name='jwt-time-delta'), path('export/csv/', views.StudentCSVExport.as_view(), name='export-csv'), + re_path(r'swagger(?P<format>\.json|\.yaml)$', + schema_view.without_ui(cache_timeout=0), name='schema-json'), + re_path(r'swagger/$', schema_view.with_ui('swagger', cache_timeout=0), + name='schema-swagger-ui'), + re_path(r'redoc/$', schema_view.with_ui('redoc', cache_timeout=0), + name='schema-redoc'), ] urlpatterns = [ diff --git a/core/views/common_views.py b/core/views/common_views.py index 231910e4e09ef7419691769bd9337954ebca1074..ea2fdccbb19751fe267997ece3670c01257f8fbf 100644 --- a/core/views/common_views.py +++ b/core/views/common_views.py @@ -21,7 +21,7 @@ from core import models from core.models import ExamType, StudentInfo, SubmissionType from core.permissions import IsReviewer, IsStudent, IsTutorOrReviewer from core.serializers import (ExamSerializer, StudentInfoSerializer, - StudentInfoSerializerForListView, + StudentInfoForListViewSerializer, SubmissionNoTypeSerializer, SubmissionSerializer, SubmissionTypeSerializer, TutorSerializer, UserAccountSerializer) @@ -70,7 +70,7 @@ class StudentReviewerApiViewSet(viewsets.ReadOnlyModelViewSet): .prefetch_related('submissions__feedback')\ .prefetch_related('submissions__type')\ .all() - serializer_class = StudentInfoSerializerForListView + serializer_class = StudentInfoForListViewSerializer def _set_students_active(self, active): for student in self.get_queryset(): @@ -143,13 +143,14 @@ class StatisticsEndpoint(viewsets.ViewSet): models.Feedback.objects.aggregate(avg=Avg('score')).get('avg', 0), 'submission_type_progress': - models.SubmissionType.get_annotated_feedback_count().values( + # Queryset is explicitly evaluated so camelizer plugin camelizes it + list(models.SubmissionType.get_annotated_feedback_count().values( 'pk', 'name', 'submission_count', 'feedback_final', 'feedback_in_validation', - 'feedback_in_conflict') + 'feedback_in_conflict')) }) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 670854cab4becc9b07050862cbe91601ceb228b8..57bf23bfce5c065d0bf811eb078cc3b1b322fcfb 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1,11 +1,23 @@ -import axios from 'axios' +import axios, {AxiosInstance, AxiosPromise, AxiosResponse} from 'axios' import {Credentials} from '@/store/modules/authentication' - -function addFieldsToUrl ({url, fields = []}: {url: string, fields?: string[]}) { +import { + Assignment, + Exam, + Feedback, FeedbackComment, + JSONWebToken, Statistics, + StudentInfo, + StudentInfoForListView, + Submission, + SubmissionNoType, SubmissionType, + Subscription, + Tutor, UserAccount +} from "@/models"; + +function addFieldsToUrl ({url, fields = []}: {url: string, fields?: string[]}): string { return fields.length > 0 ? url + '?fields=pk,' + fields : url } -function getInstanceBaseUrl () { +function getInstanceBaseUrl (): string { if (process.env.NODE_ENV === 'production') { return `https://${window.location.host}${window.location.pathname}` } else { @@ -13,7 +25,7 @@ function getInstanceBaseUrl () { } } -let ax = axios.create({ +let ax: AxiosInstance = axios.create({ baseURL: getInstanceBaseUrl() }) { @@ -23,43 +35,39 @@ let ax = axios.create({ } } -export async function registerTutor (credentials: Credentials) { - return ax.post('/api/tutor/register/', credentials) +export async function registerTutor (credentials: Credentials): Promise<AxiosResponse<Tutor>> { + return ax.post<Tutor>('/api/tutor/register/', credentials) } -export async function fetchJWT (credentials: Credentials): Promise<string> { +export async function fetchJWT (credentials: Credentials): Promise<JSONWebToken> { const token: string = (await ax.post('/api/get-token/', credentials)).data.token ax.defaults.headers['Authorization'] = `JWT ${token}` - return token + return {token} } -export async function refreshJWT (token: string): Promise<string> { - const newToken: string = (await ax.post('/api/refresh-token/', {token})).data.token - ax.defaults.headers['Authorization'] = `JWT ${newToken}` - return newToken +export async function refreshJWT (oldToken: string): Promise<JSONWebToken> { + const token: string = (await ax.post('/api/refresh-oldToken/', {oldToken})).data.token + ax.defaults.headers['Authorization'] = `JWT ${token}` + return {token} } export async function fetchJWTTimeDelta (): Promise<number> { return (await ax.get('/api/jwt-time-delta/')).data.timeDelta } -export async function fetchUserRole (): Promise<string> { - return (await ax.get('/api/user-role/')).data.role -} - -export async function fetchStudentSelfData () { +export async function fetchStudentSelfData (): Promise<StudentInfo> { return (await ax.get('/api/student-page/')).data } -export async function fetchStudentSubmissions () { +export async function fetchStudentSubmissions (): Promise<Array<Submission>> { return (await ax.get('/api/student-submissions/')).data } -export async function fetchSubmissionFeedbackTests ({pk}: {pk: string}) { +export async function fetchSubmissionFeedbackTests ({pk}: {pk: string}): Promise<SubmissionNoType> { return (await ax.get(`/api/submission/${pk}/`)).data } -export async function fetchAllStudents (fields: string[] = []) { +export async function fetchAllStudents (fields: string[] = []): Promise<Array<StudentInfoForListView>> { const url = addFieldsToUrl({ url: '/api/student/', fields @@ -68,7 +76,7 @@ export async function fetchAllStudents (fields: string[] = []) { } export async function fetchStudent ({pk, fields = []}: -{pk: string, fields?: string[]}) { +{pk: string, fields?: string[]}): Promise<StudentInfoForListView> { const url = addFieldsToUrl({ url: `/api/student/${pk}/`, fields @@ -76,7 +84,7 @@ export async function fetchStudent ({pk, fields = []}: return (await ax.get(url)).data } -export async function fetchAllTutors (fields: string[] = []) { +export async function fetchAllTutors (fields: string[] = []): Promise<Array<Tutor>> { const url = addFieldsToUrl({ url: '/api/tutor/', fields @@ -84,20 +92,20 @@ export async function fetchAllTutors (fields: string[] = []) { return (await ax.get(url)).data } -export async function fetchSubscriptions () { +export async function fetchSubscriptions (): Promise<Array<Subscription>> { return (await ax.get('/api/subscription/')).data } -export async function deactivateSubscription ({pk}: {pk: string}) { +export async function deactivateSubscription ({pk}: {pk: string}): Promise<AxiosResponse<void>> { const url = `/api/subscription/${pk}/` - return (await ax.delete(url)).data + return ax.delete(url) } -export async function fetchSubscription (subscriptionPk: string) { +export async function fetchSubscription (subscriptionPk: string): Promise<Subscription> { return (await ax.get(`/api/subscription/${subscriptionPk}/`)).data } -export async function fetchAllFeedback (fields: string[] = []) { +export async function fetchAllFeedback (fields: string[] = []): Promise<Array<Feedback>> { const url = addFieldsToUrl({ url: '/api/feedback/', fields @@ -105,20 +113,20 @@ export async function fetchAllFeedback (fields: string[] = []) { return (await ax.get(url)).data } -export async function fetchFeedback ({ofSubmission}) { +export async function fetchFeedback ({ofSubmission}: {ofSubmission: string}): Promise<Feedback> { const url = `/api/feedback/${ofSubmission}/` return (await ax.get(url)).data } export async function fetchExamType ({examPk, fields = []}: -{examPk?: string, fields?: string[]}) { +{examPk?: string, fields?: string[]}): Promise<Exam | Array<Exam>> { const url = addFieldsToUrl({ url: `/api/examtype/${examPk !== undefined ? examPk + '/' : ''}`, fields}) return (await ax.get(url)).data } -export async function fetchStatistics (opt = {fields: []}) { +export async function fetchStatistics (opt = {fields: []}): Promise<Statistics> { const url = addFieldsToUrl({ url: '/api/statistics/', fields: opt.fields @@ -126,45 +134,41 @@ export async function fetchStatistics (opt = {fields: []}) { return (await ax.get(url)).data } -interface SubscriptionPayload { - /* eslint-disable */ - query_type: string, - query_key?: string, - feedback_stage?: string - /* eslint-enable */ -} - -export async function subscribeTo (type: string, key: string, stage: string) { - let data: SubscriptionPayload = { - query_type: type +export async function subscribeTo (type: Subscription.QueryTypeEnum, + key: string, + stage: Subscription.FeedbackStageEnum): Promise<Subscription> { + let data: Subscription = { + queryType: type } if (key) { - data.query_key = key + data.queryKey = key } if (stage) { - data.feedback_stage = stage + data.feedbackStage = stage } return (await ax.post('/api/subscription/', data)).data } -export async function createAssignment ({subscription = null, subscriptionPk = ''}) { +export async function createAssignment ( + {subscription = undefined, subscriptionPk = ''}: + {subscription?: Subscription, subscriptionPk?: string}): Promise<Assignment> { const data = { subscription: subscription ? subscription.pk : subscriptionPk } return (await ax.post(`/api/assignment/`, data)).data } -export async function submitFeedbackForAssignment ({feedback}) { +export async function submitFeedbackForAssignment ({feedback}: {feedback: Feedback}): Promise<Feedback> { return (await ax.post('/api/feedback/', feedback)).data } -export async function submitUpdatedFeedback ({feedback}) { - return (await ax.patch(`/api/feedback/${feedback.of_submission}/`, feedback)).data +export async function submitUpdatedFeedback ({feedback}: {feedback: Feedback}): Promise<Feedback> { + return (await ax.patch(`/api/feedback/${feedback.ofSubmission}/`, feedback)).data } -export async function fetchSubmissionTypes (fields = []) { +export async function fetchSubmissionTypes (fields = []): Promise<Array<SubmissionType>> { let url = '/api/submissiontype/' if (fields.length > 0) { url += '?fields=pk,' + fields @@ -172,43 +176,43 @@ export async function fetchSubmissionTypes (fields = []) { return (await ax.get(url)).data } -export async function fetchAllAssignments (fields = []) { +export async function fetchAllAssignments (fields = []): Promise<Array<Assignment>> { const url = addFieldsToUrl({url: '/api/assignment/', fields}) return (await ax.get(url)).data } -export async function deleteAssignment ({assignment}) { +export async function deleteAssignment ({assignment}: {assignment: Assignment}): Promise<AxiosResponse<void>> { const url = `/api/assignment/${assignment.pk}/` - return (await ax.delete(url)).data + return ax.delete(url) } -export async function deleteComment (comment = {pk: undefined}) { +export async function deleteComment (comment = {pk: undefined}): Promise<AxiosResponse<void>> { const url = `/api/feedback-comment/${comment.pk}/` - return (await ax.delete(url)).data + return await ax.delete(url) } -export async function patchComment (comment = {pk: undefined}) { +export async function patchComment (comment = {pk: undefined}): Promise<FeedbackComment> { const url = `/api/feedback-comment/${comment.pk}/` return (await ax.patch(url, comment)).data } -export async function activateAllStudentAccess () { +export async function activateAllStudentAccess (): Promise<AxiosResponse<void>> { return ax.post('/api/student/activate/') } -export async function deactivateAllStudentAccess () { +export async function deactivateAllStudentAccess (): Promise<AxiosResponse<void>> { return ax.post('/api/student/deactivate/') } -export async function changePassword (userPk: string, data) { - return ax.patch(`/api/user/${userPk}/change_password/`, data) +export async function changePassword (userPk: string, data: {password: string}): Promise<UserAccount> { + return (await ax.patch(`/api/user/${userPk}/change_password/`, data)).data } -export async function getOwnUser () { +export async function getOwnUser (): Promise<UserAccount> { return (await ax.get('/api/user/me/')).data } -export async function changeActiveForUser (userPk: string, active: boolean) { +export async function changeActiveForUser (userPk: string, active: boolean): Promise<UserAccount> { return (await ax.patch(`/api/user/${userPk}/change_active/`, {'is_active': active})).data } diff --git a/frontend/src/models.ts b/frontend/src/models.ts new file mode 100644 index 0000000000000000000000000000000000000000..485884b20dd559c7321004ed39ca16bc2daebeec --- /dev/null +++ b/frontend/src/models.ts @@ -0,0 +1,827 @@ +/** + * + * @export + * @interface Assignment + */ +export interface Assignment { + /** + * + * @type {string} + * @memberof Assignment + */ + pk?: string; + /** + * + * @type {string} + * @memberof Assignment + */ + submission?: string; + /** + * + * @type {boolean} + * @memberof Assignment + */ + isDone?: boolean; + /** + * + * @type {string} + * @memberof Assignment + */ + owner?: string; + /** + * + * @type {string} + * @memberof Assignment + */ + stage?: string; +} + +/** + * + * @export + * @interface Exam + */ +export interface Exam { + /** + * + * @type {string} + * @memberof Exam + */ + pk?: string; + /** + * + * @type {string} + * @memberof Exam + */ + moduleReference: string; + /** + * + * @type {number} + * @memberof Exam + */ + totalScore: number; + /** + * + * @type {number} + * @memberof Exam + */ + passScore: number; + /** + * + * @type {boolean} + * @memberof Exam + */ + passOnly?: boolean; +} + +/** + * + * @export + * @interface Feedback + */ +export interface Feedback { + /** + * + * @type {number} + * @memberof Feedback + */ + pk?: number; + /** + * + * @type {string} + * @memberof Feedback + */ + ofSubmission: string; + /** + * + * @type {boolean} + * @memberof Feedback + */ + isFinal?: boolean; + /** + * + * @type {number} + * @memberof Feedback + */ + score?: number; + /** + * + * @type {Array<FeedbackComment>} + * @memberof Feedback + */ + feedbackLines?: {[lineNo: number]: FeedbackComment[]}; + /** + * + * @type {Date} + * @memberof Feedback + */ + created?: string; + /** + * + * @type {string} + * @memberof Feedback + */ + ofSubmissionType?: string; + /** + * + * @type {string} + * @memberof Feedback + */ + feedbackStageForUser?: string; +} + +/** + * + * @export + * @interface FeedbackComment + */ +export interface FeedbackComment { + /** + * + * @type {string} + * @memberof FeedbackComment + */ + pk?: string; + /** + * + * @type {string} + * @memberof FeedbackComment + */ + text: string; + /** + * + * @type {Date} + * @memberof FeedbackComment + */ + created?: string; + /** + * + * @type {string} + * @memberof FeedbackComment + */ + ofTutor?: string; + /** + * + * @type {number} + * @memberof FeedbackComment + */ + ofLine?: number; + /** + * + * @type {boolean} + * @memberof FeedbackComment + */ + visibleToStudent?: boolean; +} + +/** + * + * @export + * @interface Credentials + */ +export interface Credentials { + /** + * + * @type {string} + * @memberof JSONWebToken + */ + username: string; + /** + * + * @type {string} + * @memberof JSONWebToken + */ + password: string; +} + + +export interface JSONWebToken { + token: string +} + +export interface Statistics { + submissionsPerType: number, + submissionsPerStudent: number, + currentMeanScore: number, + submissionTypeProgress: Array<SubmissionTypeProgress> +} + +/** + * + * @export + * @interface StudentInfo + */ +export interface StudentInfo { + /** + * + * @type {string} + * @memberof StudentInfo + */ + pk?: string; + /** + * + * @type {string} + * @memberof StudentInfo + */ + name?: string; + /** + * + * @type {string} + * @memberof StudentInfo + */ + user: string; + /** + * + * @type {string} + * @memberof StudentInfo + */ + matrikelNo?: string; + /** + * + * @type {Exam} + * @memberof StudentInfo + */ + exam: Exam; + /** + * + * @type {Array<SubmissionList>} + * @memberof StudentInfo + */ + submissions: Array<SubmissionList>; + /** + * + * @type {boolean} + * @memberof StudentInfo + */ + passesExam?: boolean; +} + +/** + * + * @export + * @interface StudentInfoForListView + */ +export interface StudentInfoForListView { + /** + * + * @type {string} + * @memberof StudentInfoForListView + */ + pk?: string; + /** + * + * @type {string} + * @memberof StudentInfoForListView + */ + name?: string; + /** + * + * @type {string} + * @memberof StudentInfoForListView + */ + user?: string; + /** + * + * @type {string} + * @memberof StudentInfoForListView + */ + userPk?: string; + /** + * + * @type {string} + * @memberof StudentInfoForListView + */ + exam?: string; + /** + * + * @type {Array<SubmissionNoTextFields>} + * @memberof StudentInfoForListView + */ + submissions: Array<SubmissionNoTextFields>; + /** + * + * @type {string} + * @memberof StudentInfoForListView + */ + matrikelNo?: string; + /** + * + * @type {boolean} + * @memberof StudentInfoForListView + */ + passesExam?: boolean; + /** + * + * @type {boolean} + * @memberof StudentInfoForListView + */ + isActive: boolean; +} + +/** + * + * @export + * @interface Submission + */ +export interface Submission { + /** + * + * @type {string} + * @memberof Submission + */ + pk?: string; + /** + * + * @type {SubmissionType} + * @memberof Submission + */ + type: SubmissionType; + /** + * + * @type {string} + * @memberof Submission + */ + text?: string; + /** + * + * @type {VisibleCommentFeedback} + * @memberof Submission + */ + feedback: VisibleCommentFeedback; + /** + * + * @type {Array<Test>} + * @memberof Submission + */ + tests: Array<Test>; +} + +/** + * + * @export + * @interface SubmissionList + */ +export interface SubmissionList { + /** + * + * @type {string} + * @memberof SubmissionList + */ + pk?: string; + /** + * + * @type {SubmissionTypeList} + * @memberof SubmissionList + */ + type: SubmissionTypeList; + /** + * + * @type {Feedback} + * @memberof SubmissionList + */ + feedback: Feedback; +} + +/** + * + * @export + * @interface SubmissionNoTextFields + */ +export interface SubmissionNoTextFields { + /** + * + * @type {string} + * @memberof SubmissionNoTextFields + */ + pk?: string; + /** + * + * @type {string} + * @memberof SubmissionNoTextFields + */ + type: string; + /** + * + * @type {string} + * @memberof SubmissionNoTextFields + */ + score?: string; + /** + * + * @type {string} + * @memberof SubmissionNoTextFields + */ + _final?: string; + /** + * + * @type {string} + * @memberof SubmissionNoTextFields + */ + fullScore?: string; +} + +/** + * + * @export + * @interface SubmissionNoType + */ +export interface SubmissionNoType { + /** + * + * @type {string} + * @memberof SubmissionNoType + */ + pk?: string; + /** + * + * @type {string} + * @memberof SubmissionNoType + */ + type: string; + /** + * + * @type {string} + * @memberof SubmissionNoType + */ + fullScore?: string; + /** + * + * @type {string} + * @memberof SubmissionNoType + */ + text?: string; + /** + * + * @type {Feedback} + * @memberof SubmissionNoType + */ + feedback: Feedback; + /** + * + * @type {Array<Test>} + * @memberof SubmissionNoType + */ + tests: Array<Test>; +} + +/** + * + * @export + * @interface SubmissionType + */ +export interface SubmissionType { + /** + * + * @type {string} + * @memberof SubmissionType + */ + pk?: string; + /** + * + * @type {string} + * @memberof SubmissionType + */ + name: string; + /** + * + * @type {number} + * @memberof SubmissionType + */ + fullScore?: number; + /** + * + * @type {string} + * @memberof SubmissionType + */ + description: string; + /** + * + * @type {string} + * @memberof SubmissionType + */ + solution: string; + /** + * + * @type {string} + * @memberof SubmissionType + */ + programmingLanguage?: SubmissionType.ProgrammingLanguageEnum; +} + +/** + * @export + * @namespace SubmissionType + */ +export namespace SubmissionType { + /** + * @export + * @enum {string} + */ + export enum ProgrammingLanguageEnum { + C = <any> 'c', + Java = <any> 'java' + } +} + +/** + * + * @export + * @interface SubmissionTypeList + */ +export interface SubmissionTypeList { + /** + * + * @type {string} + * @memberof SubmissionTypeList + */ + pk?: string; + /** + * + * @type {string} + * @memberof SubmissionTypeList + */ + name: string; + /** + * + * @type {number} + * @memberof SubmissionTypeList + */ + fullScore?: number; +} + +export interface SubmissionTypeProgress { + pk?: string, + name: string, + feedbackFinal: number, + feedbackInValidation: number, + feedbackInConflict: number, + submissionCount: number +} + +/** + * + * @export + * @interface Subscription + */ +export interface Subscription { + /** + * + * @type {string} + * @memberof Subscription + */ + pk?: string; + /** + * + * @type {string} + * @memberof Subscription + */ + owner?: string; + /** + * + * @type {string} + * @memberof Subscription + */ + queryType?: Subscription.QueryTypeEnum; + /** + * + * @type {string} + * @memberof Subscription + */ + queryKey?: string; + /** + * + * @type {string} + * @memberof Subscription + */ + feedbackStage?: Subscription.FeedbackStageEnum; + /** + * + * @type {boolean} + * @memberof Subscription + */ + deactivated?: boolean; + /** + * + * @type {string} + * @memberof Subscription + */ + assignments?: string; + /** + * + * @type {string} + * @memberof Subscription + */ + remaining?: string; + /** + * + * @type {string} + * @memberof Subscription + */ + available?: string; +} + +/** + * @export + * @namespace Subscription + */ +export namespace Subscription { + /** + * @export + * @enum {string} + */ + export enum QueryTypeEnum { + Random = <any> 'random', + Student = <any> 'student', + Exam = <any> 'exam', + SubmissionType = <any> 'submission_type' + } + /** + * @export + * @enum {string} + */ + export enum FeedbackStageEnum { + Creation = <any> 'feedback-creation', + Validation = <any> 'feedback-validation', + ConflictResolution = <any> 'feedback-conflict-resolution' + } +} + +/** + * + * @export + * @interface Test + */ +export interface Test { + /** + * + * @type {string} + * @memberof Test + */ + pk?: string; + /** + * + * @type {string} + * @memberof Test + */ + name: string; + /** + * + * @type {string} + * @memberof Test + */ + label: string; + /** + * + * @type {string} + * @memberof Test + */ + annotation: string; +} + +/** + * + * @export + * @interface Tutor + */ +export interface Tutor { + /** + * + * @type {string} + * @memberof Tutor + */ + pk?: string; + /** + * + * @type {string} + * @memberof Tutor + */ + password?: string; + /** + * Designates whether this user should be treated as active. Unselect this instead of deleting accounts. + * @type {boolean} + * @memberof Tutor + */ + isActive?: boolean; + /** + * Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only. + * @type {string} + * @memberof Tutor + */ + username: string; + /** + * + * @type {string} + * @memberof Tutor + */ + feedbackCreated?: string; + /** + * + * @type {string} + * @memberof Tutor + */ + feedbackValidated?: string; +} + +/** + * + * @export + * @interface UserAccount + */ +export interface UserAccount { + /** + * + * @type {string} + * @memberof UserAccount + */ + pk?: string; + /** + * Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only. + * @type {string} + * @memberof UserAccount + */ + username?: string; + /** + * + * @type {string} + * @memberof UserAccount + */ + role?: UserAccount.RoleEnum; + /** + * + * @type {boolean} + * @memberof UserAccount + */ + isAdmin?: boolean; + /** + * + * @type {string} + * @memberof UserAccount + */ + password?: string; +} + +/** + * @export + * @namespace UserAccount + */ +export namespace UserAccount { + /** + * @export + * @enum {string} + */ + export enum RoleEnum { + Student = <any> 'Student', + Tutor = <any> 'Tutor', + Reviewer = <any> 'Reviewer' + } +} + +/** + * + * @export + * @interface VisibleCommentFeedback + */ +export interface VisibleCommentFeedback { + /** + * + * @type {number} + * @memberof VisibleCommentFeedback + */ + pk?: number; + /** + * + * @type {string} + * @memberof VisibleCommentFeedback + */ + ofSubmission: string; + /** + * + * @type {boolean} + * @memberof VisibleCommentFeedback + */ + isFinal?: boolean; + /** + * + * @type {number} + * @memberof VisibleCommentFeedback + */ + score?: number; + /** + * + * @type {string} + * @memberof VisibleCommentFeedback + */ + feedbackLines?: string; + /** + * + * @type {Date} + * @memberof VisibleCommentFeedback + */ + created?: Date; + /** + * + * @type {string} + * @memberof VisibleCommentFeedback + */ + ofSubmissionType?: string; +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c800f3ea8e347d2dbec47be5b999bd78f2e119d3..59a075aa131f80c4080a3a6c8df151001c6a7bdd 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "esnext", "module": "esnext", - "strict": false, + "strict": true, "jsx": "preserve", "importHelpers": true, "moduleResolution": "node", diff --git a/grady/settings/default.py b/grady/settings/default.py index 1ff127492376e82778d51c91b8d69f44c49e9467..a9a9fbeeae99e1f11d47ff737e508964cc3bab0a 100644 --- a/grady/settings/default.py +++ b/grady/settings/default.py @@ -43,6 +43,7 @@ INSTALLED_APPS = [ 'rest_framework', 'corsheaders', 'drf_dynamic_fields', + 'drf_yasg', 'core', ] @@ -138,7 +139,17 @@ REST_FRAMEWORK = { 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', - ) + ), + 'DEFAULT_RENDERER_CLASSES': ( + 'djangorestframework_camel_case.render.CamelCaseJSONRenderer', + ), + + 'DEFAULT_PARSER_CLASSES': ( + 'djangorestframework_camel_case.parser.CamelCaseJSONParser', + ), + # 'JSON_UNDERSCOREIZE': { + # 'no_underscore_before_number': True, + # }, } JWT_AUTH = { diff --git a/swagger-api-specification.json b/swagger-api-specification.json new file mode 100644 index 0000000000000000000000000000000000000000..29c9f7d385dcf94e0408908be37510818539bcc1 --- /dev/null +++ b/swagger-api-specification.json @@ -0,0 +1 @@ +{"swagger": "2.0", "info": {"title": "Grady API", "description": "Blub", "version": "v1"}, "host": "localhost:8000", "schemes": ["http"], "basePath": "/api", "consumes": ["application/json"], "produces": ["application/json"], "securityDefinitions": {"basic": {"type": "basic"}}, "security": [{"basic": []}], "paths": {"/assignment/": {"get": {"operationId": "assignment_list", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/Assignment"}}}}, "tags": ["assignment"]}, "post": {"operationId": "assignment_create", "description": "", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/Assignment"}}], "responses": {"201": {"description": "", "schema": {"$ref": "#/definitions/Assignment"}}}, "tags": ["assignment"]}, "parameters": []}, "/assignment/{assignment_id}/": {"get": {"operationId": "assignment_read", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/Assignment"}}}, "tags": ["assignment"]}, "delete": {"operationId": "assignment_delete", "description": "Stop working on the assignment before it is finished", "parameters": [], "responses": {"204": {"description": ""}}, "tags": ["assignment"]}, "parameters": [{"name": "assignment_id", "in": "path", "description": "A UUID string identifying this tutor submission assignment.", "required": true, "type": "string", "format": "uuid"}]}, "/examtype/": {"get": {"operationId": "examtype_list", "description": "Gets a list of an individual exam by Id if provided", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/Exam"}}}}, "tags": ["examtype"]}, "parameters": []}, "/examtype/{exam_type_id}/": {"get": {"operationId": "examtype_read", "description": "Gets a list of an individual exam by Id if provided", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/Exam"}}}, "tags": ["examtype"]}, "parameters": [{"name": "exam_type_id", "in": "path", "description": "A UUID string identifying this ExamType.", "required": true, "type": "string", "format": "uuid"}]}, "/export/csv/": {"get": {"operationId": "export_csv_list", "description": "", "parameters": [], "responses": {"200": {"description": ""}}, "produces": ["text/csv"], "tags": ["export"]}, "parameters": []}, "/feedback-comment/{comment_id}/": {"patch": {"operationId": "feedback-comment_partial_update", "description": "Gets a list of an individual exam by Id if provided", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/FeedbackComment"}}], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/FeedbackComment"}}}, "tags": ["feedback-comment"]}, "delete": {"operationId": "feedback-comment_delete", "description": "Gets a list of an individual exam by Id if provided", "parameters": [], "responses": {"204": {"description": ""}}, "tags": ["feedback-comment"]}, "parameters": [{"name": "comment_id", "in": "path", "description": "A UUID string identifying this Feedback Comment.", "required": true, "type": "string", "format": "uuid"}]}, "/feedback/": {"get": {"operationId": "feedback_list", "description": "Gets a list of an individual exam by Id if provided", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/Feedback"}}}}, "tags": ["feedback"]}, "post": {"operationId": "feedback_create", "description": "Gets a list of an individual exam by Id if provided", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/Feedback"}}], "responses": {"201": {"description": "", "schema": {"$ref": "#/definitions/Feedback"}}}, "tags": ["feedback"]}, "parameters": []}, "/feedback/{submission_pk}/": {"get": {"operationId": "feedback_read", "description": "Gets a list of an individual exam by Id if provided", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/Feedback"}}}, "tags": ["feedback"]}, "patch": {"operationId": "feedback_partial_update", "description": "Gets a list of an individual exam by Id if provided", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/Feedback"}}], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/Feedback"}}}, "tags": ["feedback"]}, "parameters": [{"name": "submission_pk", "in": "path", "required": true, "type": "string"}]}, "/get-token/": {"post": {"operationId": "get-token_create", "description": "API View that receives a POST with a user's username and password.\n\nReturns a JSON Web Token that can be used for authenticated requests.", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/JSONWebToken"}}], "responses": {"201": {"description": "", "schema": {"$ref": "#/definitions/JSONWebToken"}}}, "tags": ["get-token"]}, "parameters": []}, "/jwt-time-delta/": {"get": {"operationId": "jwt-time-delta_list", "description": "", "parameters": [], "responses": {"200": {"description": ""}}, "tags": ["jwt-time-delta"]}, "parameters": []}, "/refresh-token/": {"post": {"operationId": "refresh-token_create", "description": "API View that returns a refreshed token (with new expiration) based on\nexisting token\n\nIf 'orig_iat' field (original issued-at-time) is found, will first check\nif it's within expiration window, then copy it to the new token", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/RefreshJSONWebToken"}}], "responses": {"201": {"description": "", "schema": {"$ref": "#/definitions/RefreshJSONWebToken"}}}, "tags": ["refresh-token"]}, "parameters": []}, "/statistics/": {"get": {"operationId": "statistics_list", "description": "", "parameters": [], "responses": {"200": {"description": ""}}, "tags": ["statistics"]}, "parameters": []}, "/student-page/": {"get": {"operationId": "student-page_read", "description": "Gets all data that belongs to one student", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/StudentInfo"}}}, "tags": ["student-page"]}, "parameters": []}, "/student-submissions/": {"get": {"operationId": "student-submissions_list", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/Submission"}}}}, "tags": ["student-submissions"]}, "parameters": []}, "/student/": {"get": {"operationId": "student_list", "description": "Gets a list of all students without individual submissions", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/StudentInfoForListView"}}}}, "tags": ["student"]}, "parameters": []}, "/student/activate/": {"post": {"operationId": "student_activate", "description": "Gets a list of all students without individual submissions", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/StudentInfoForListView"}}], "responses": {"201": {"description": "", "schema": {"$ref": "#/definitions/StudentInfoForListView"}}}, "tags": ["student"]}, "parameters": []}, "/student/deactivate/": {"post": {"operationId": "student_deactivate", "description": "Gets a list of all students without individual submissions", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/StudentInfoForListView"}}], "responses": {"201": {"description": "", "schema": {"$ref": "#/definitions/StudentInfoForListView"}}}, "tags": ["student"]}, "parameters": []}, "/student/{student_id}/": {"get": {"operationId": "student_read", "description": "Gets a list of all students without individual submissions", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/StudentInfoForListView"}}}, "tags": ["student"]}, "parameters": [{"name": "student_id", "in": "path", "description": "A UUID string identifying this Student.", "required": true, "type": "string", "format": "uuid"}]}, "/submission/": {"get": {"operationId": "submission_list", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/SubmissionNoType"}}}}, "tags": ["submission"]}, "parameters": []}, "/submission/{submission_id}/": {"get": {"operationId": "submission_read", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/SubmissionNoType"}}}, "tags": ["submission"]}, "parameters": [{"name": "submission_id", "in": "path", "description": "A UUID string identifying this Submission.", "required": true, "type": "string", "format": "uuid"}]}, "/submissiontype/": {"get": {"operationId": "submissiontype_list", "description": "Gets a list or a detail view of a single SubmissionType", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/SubmissionType"}}}}, "tags": ["submissiontype"]}, "parameters": []}, "/submissiontype/{submission_type_id}/": {"get": {"operationId": "submissiontype_read", "description": "Gets a list or a detail view of a single SubmissionType", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/SubmissionType"}}}, "tags": ["submissiontype"]}, "parameters": [{"name": "submission_type_id", "in": "path", "description": "A UUID string identifying this SubmissionType.", "required": true, "type": "string", "format": "uuid"}]}, "/subscription/": {"get": {"operationId": "subscription_list", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/Subscription"}}}}, "tags": ["subscription"]}, "post": {"operationId": "subscription_create", "description": "", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/Subscription"}}], "responses": {"201": {"description": "", "schema": {"$ref": "#/definitions/Subscription"}}}, "tags": ["subscription"]}, "parameters": []}, "/subscription/{subscription_id}/": {"get": {"operationId": "subscription_read", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/Subscription"}}}, "tags": ["subscription"]}, "delete": {"operationId": "subscription_delete", "description": "", "parameters": [], "responses": {"204": {"description": ""}}, "tags": ["subscription"]}, "parameters": [{"name": "subscription_id", "in": "path", "description": "A UUID string identifying this submission subscription.", "required": true, "type": "string", "format": "uuid"}]}, "/tutor/": {"get": {"operationId": "tutor_list", "description": "Api endpoint for creating, listing, viewing or deleting tutors", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/Tutor"}}}}, "tags": ["tutor"]}, "post": {"operationId": "tutor_create", "description": "Api endpoint for creating, listing, viewing or deleting tutors", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/Tutor"}}], "responses": {"201": {"description": "", "schema": {"$ref": "#/definitions/Tutor"}}}, "tags": ["tutor"]}, "parameters": []}, "/tutor/register/": {"post": {"operationId": "tutor_register", "description": "Api endpoint for creating, listing, viewing or deleting tutors", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/Tutor"}}], "responses": {"201": {"description": "", "schema": {"$ref": "#/definitions/Tutor"}}}, "tags": ["tutor"]}, "parameters": []}, "/tutor/{user_id}/": {"get": {"operationId": "tutor_read", "description": "Api endpoint for creating, listing, viewing or deleting tutors", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/Tutor"}}}, "tags": ["tutor"]}, "put": {"operationId": "tutor_update", "description": "Api endpoint for creating, listing, viewing or deleting tutors", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/Tutor"}}], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/Tutor"}}}, "tags": ["tutor"]}, "patch": {"operationId": "tutor_partial_update", "description": "Api endpoint for creating, listing, viewing or deleting tutors", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/Tutor"}}], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/Tutor"}}}, "tags": ["tutor"]}, "delete": {"operationId": "tutor_delete", "description": "Api endpoint for creating, listing, viewing or deleting tutors", "parameters": [], "responses": {"204": {"description": ""}}, "tags": ["tutor"]}, "parameters": [{"name": "user_id", "in": "path", "description": "A UUID string identifying this user.", "required": true, "type": "string", "format": "uuid"}]}, "/user-role/": {"get": {"operationId": "user-role_list", "description": "", "parameters": [], "responses": {"200": {"description": ""}}, "tags": ["user-role"]}, "parameters": []}, "/user/": {"get": {"operationId": "user_list", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/UserAccount"}}}}, "tags": ["user"]}, "parameters": []}, "/user/me/": {"get": {"operationId": "user_me", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"type": "array", "items": {"$ref": "#/definitions/UserAccount"}}}}, "tags": ["user"]}, "parameters": []}, "/user/{user_id}/": {"get": {"operationId": "user_read", "description": "", "parameters": [], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/UserAccount"}}}, "tags": ["user"]}, "parameters": [{"name": "user_id", "in": "path", "description": "A UUID string identifying this user.", "required": true, "type": "string", "format": "uuid"}]}, "/user/{user_id}/change_active/": {"patch": {"operationId": "user_change_active", "description": "", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/UserAccount"}}], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/UserAccount"}}}, "tags": ["user"]}, "parameters": [{"name": "user_id", "in": "path", "description": "A UUID string identifying this user.", "required": true, "type": "string", "format": "uuid"}]}, "/user/{user_id}/change_password/": {"patch": {"operationId": "user_change_password", "description": "", "parameters": [{"name": "data", "in": "body", "required": true, "schema": {"$ref": "#/definitions/UserAccount"}}], "responses": {"200": {"description": "", "schema": {"$ref": "#/definitions/UserAccount"}}}, "tags": ["user"]}, "parameters": [{"name": "user_id", "in": "path", "description": "A UUID string identifying this user.", "required": true, "type": "string", "format": "uuid"}]}}, "definitions": {"Assignment": {"type": "object", "properties": {"pk": {"title": "Assignment id", "type": "string", "format": "uuid", "readOnly": true}, "submission": {"title": "Submission", "type": "string", "format": "uuid", "readOnly": true}, "isDone": {"title": "Is done", "type": "boolean", "readOnly": true}, "owner": {"title": "Owner", "type": "string", "readOnly": true}, "stage": {"title": "Stage", "type": "string", "readOnly": true}}}, "Exam": {"required": ["moduleReference", "totalScore", "passScore"], "type": "object", "properties": {"pk": {"title": "Exam type id", "type": "string", "format": "uuid", "readOnly": true}, "moduleReference": {"title": "Module reference", "type": "string", "maxLength": 50, "minLength": 1}, "totalScore": {"title": "Total score", "type": "integer", "maximum": 2147483647, "minimum": 0}, "passScore": {"title": "Pass score", "type": "integer", "maximum": 2147483647, "minimum": 0}, "passOnly": {"title": "Pass only", "type": "boolean"}}}, "FeedbackComment": {"required": ["text"], "type": "object", "properties": {"pk": {"title": "Comment id", "type": "string", "format": "uuid", "readOnly": true}, "text": {"title": "Text", "type": "string", "minLength": 1}, "created": {"title": "Created", "type": "string", "format": "date-time", "readOnly": true}, "ofTutor": {"title": "Of tutor", "type": "string", "readOnly": true}, "ofLine": {"title": "Of line", "type": "integer", "maximum": 2147483647, "minimum": 0}, "visibleToStudent": {"title": "Visible to student", "type": "boolean"}}}, "Feedback": {"required": ["ofSubmission"], "type": "object", "properties": {"pk": {"title": "ID", "type": "integer", "readOnly": true}, "ofSubmission": {"title": "Of submission", "type": "string", "format": "uuid"}, "isFinal": {"title": "Is final", "type": "boolean"}, "score": {"title": "Score", "type": "integer", "maximum": 2147483647, "minimum": 0}, "feedbackLines": {"type": "array", "items": {"$ref": "#/definitions/FeedbackComment"}}, "created": {"title": "Created", "type": "string", "format": "date-time", "readOnly": true}, "ofSubmissionType": {"title": "Of submission type", "type": "string", "readOnly": true}, "feedbackStageForUser": {"title": "Feedback stage for user", "type": "string", "readOnly": true}}}, "JSONWebToken": {"required": ["username", "password"], "type": "object", "properties": {"username": {"title": "Username", "type": "string", "minLength": 1}, "password": {"title": "Password", "type": "string", "minLength": 1}}}, "RefreshJSONWebToken": {"required": ["token"], "type": "object", "properties": {"token": {"title": "Token", "type": "string", "minLength": 1}}}, "SubmissionTypeList": {"title": "Type", "required": ["name"], "type": "object", "properties": {"pk": {"title": "Submission type id", "type": "string", "format": "uuid", "readOnly": true}, "name": {"title": "Name", "type": "string", "maxLength": 100, "minLength": 1}, "fullScore": {"title": "Full score", "type": "integer", "maximum": 2147483647, "minimum": 0}}}, "SubmissionList": {"required": ["type", "feedback"], "type": "object", "properties": {"pk": {"title": "Submission id", "type": "string", "format": "uuid", "readOnly": true}, "type": {"$ref": "#/definitions/SubmissionTypeList"}, "feedback": {"$ref": "#/definitions/Feedback"}}}, "StudentInfo": {"required": ["user", "exam", "submissions"], "type": "object", "properties": {"pk": {"title": "Student id", "type": "string", "format": "uuid", "readOnly": true}, "name": {"title": "Name", "type": "string", "readOnly": true}, "user": {"title": "User", "type": "string", "format": "uuid"}, "matrikelNo": {"title": "Matrikel no", "type": "string", "readOnly": true}, "exam": {"$ref": "#/definitions/Exam"}, "submissions": {"type": "array", "items": {"$ref": "#/definitions/SubmissionList"}}, "passesExam": {"title": "Passes exam", "type": "boolean"}}}, "SubmissionType": {"title": "Type", "required": ["name", "description", "solution"], "type": "object", "properties": {"pk": {"title": "Submission type id", "type": "string", "format": "uuid", "readOnly": true}, "name": {"title": "Name", "type": "string", "maxLength": 100, "minLength": 1}, "fullScore": {"title": "Full score", "type": "integer", "maximum": 2147483647, "minimum": 0}, "description": {"title": "Description", "type": "string", "minLength": 1}, "solution": {"title": "Solution", "type": "string", "minLength": 1}, "programmingLanguage": {"title": "Programming language", "type": "string", "enum": ["c", "java"]}}}, "VisibleCommentFeedback": {"title": "Feedback", "required": ["ofSubmission"], "type": "object", "properties": {"pk": {"title": "ID", "type": "integer", "readOnly": true}, "ofSubmission": {"title": "Of submission", "type": "string", "format": "uuid"}, "isFinal": {"title": "Is final", "type": "boolean"}, "score": {"title": "Score", "type": "integer", "maximum": 2147483647, "minimum": 0}, "feedbackLines": {"title": "Feedback lines", "type": "string", "readOnly": true}, "created": {"title": "Created", "type": "string", "format": "date-time", "readOnly": true}, "ofSubmissionType": {"title": "Of submission type", "type": "string", "readOnly": true}}}, "Test": {"required": ["name", "label", "annotation"], "type": "object", "properties": {"pk": {"title": "Test id", "type": "string", "format": "uuid", "readOnly": true}, "name": {"title": "Name", "type": "string", "maxLength": 30, "minLength": 1}, "label": {"title": "Label", "type": "string", "maxLength": 50, "minLength": 1}, "annotation": {"title": "Annotation", "type": "string", "minLength": 1}}}, "Submission": {"required": ["type", "feedback", "tests"], "type": "object", "properties": {"pk": {"title": "Submission id", "type": "string", "format": "uuid", "readOnly": true}, "type": {"$ref": "#/definitions/SubmissionType"}, "text": {"title": "Text", "type": "string"}, "feedback": {"$ref": "#/definitions/VisibleCommentFeedback"}, "tests": {"type": "array", "items": {"$ref": "#/definitions/Test"}}}}, "SubmissionNoTextFields": {"required": ["type"], "type": "object", "properties": {"pk": {"title": "Submission id", "type": "string", "format": "uuid", "readOnly": true}, "type": {"title": "Type", "type": "string", "format": "uuid"}, "score": {"title": "Score", "type": "string", "readOnly": true}, "final": {"title": "Final", "type": "string", "readOnly": true}, "fullScore": {"title": "Full score", "type": "string", "readOnly": true}}}, "StudentInfoForListView": {"required": ["submissions", "isActive"], "type": "object", "properties": {"pk": {"title": "Student id", "type": "string", "format": "uuid", "readOnly": true}, "name": {"title": "Name", "type": "string", "readOnly": true}, "user": {"title": "User", "type": "string", "readOnly": true}, "userPk": {"title": "User pk", "type": "string", "readOnly": true}, "exam": {"title": "Exam", "type": "string", "readOnly": true}, "submissions": {"type": "array", "items": {"$ref": "#/definitions/SubmissionNoTextFields"}}, "matrikelNo": {"title": "Matrikel no", "type": "string", "maxLength": 30, "minLength": 1}, "passesExam": {"title": "Passes exam", "type": "boolean"}, "isActive": {"title": "Is active", "type": "boolean"}}}, "SubmissionNoType": {"required": ["type", "feedback", "tests"], "type": "object", "properties": {"pk": {"title": "Submission id", "type": "string", "format": "uuid", "readOnly": true}, "type": {"title": "Type", "type": "string", "format": "uuid"}, "fullScore": {"title": "Full score", "type": "string", "readOnly": true}, "text": {"title": "Text", "type": "string"}, "feedback": {"$ref": "#/definitions/Feedback"}, "tests": {"type": "array", "items": {"$ref": "#/definitions/Test"}}}}, "Subscription": {"type": "object", "properties": {"pk": {"title": "Subscription id", "type": "string", "format": "uuid", "readOnly": true}, "owner": {"title": "Owner", "type": "string", "readOnly": true}, "queryType": {"title": "Query type", "type": "string", "enum": ["random", "student", "exam", "submission_type"], "default": "random"}, "queryKey": {"title": "Query key", "type": "string", "format": "uuid"}, "feedbackStage": {"title": "Feedback stage", "type": "string", "enum": ["feedback-creation", "feedback-validation", "feedback-conflict-resolution"], "default": "feedback-creation"}, "deactivated": {"title": "Deactivated", "type": "boolean", "readOnly": true}, "assignments": {"title": "Assignments", "type": "string", "readOnly": true}, "remaining": {"title": "Remaining", "type": "string", "readOnly": true}, "available": {"title": "Available", "type": "string", "readOnly": true}}}, "Tutor": {"required": ["username"], "type": "object", "properties": {"pk": {"title": "User id", "type": "string", "format": "uuid", "readOnly": true}, "password": {"title": "Password", "type": "string", "minLength": 1}, "isActive": {"title": "Active", "description": "Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", "type": "boolean"}, "username": {"title": "Username", "description": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", "type": "string", "pattern": "^[\\w.@+-]+$", "maxLength": 150, "minLength": 1}, "feedbackCreated": {"title": "Feedback created", "type": "string", "readOnly": true}, "feedbackValidated": {"title": "Feedback validated", "type": "string", "readOnly": true}}}, "UserAccount": {"required": ["password"], "type": "object", "properties": {"pk": {"title": "User id", "type": "string", "format": "uuid", "readOnly": true}, "username": {"title": "Username", "description": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", "type": "string", "readOnly": true, "minLength": 1}, "role": {"title": "Role", "type": "string", "enum": ["Student", "Tutor", "Reviewer"], "readOnly": true}, "isAdmin": {"title": "Is admin", "type": "boolean", "readOnly": true}, "password": {"title": "Password", "type": "string", "maxLength": 128, "minLength": 1}}}}} \ No newline at end of file