diff --git a/core/serializers/tutor.py b/core/serializers/tutor.py index 958081a8066d640cd25ffa8002cf7232c84b30aa..ce893d09968383b407e1ff04519257a1ab9b0fa0 100644 --- a/core/serializers/tutor.py +++ b/core/serializers/tutor.py @@ -57,4 +57,5 @@ class CorrectorSerializer(DynamicFieldsModelSerializer): 'is_active', 'username', 'feedback_created', - 'feedback_validated') + 'feedback_validated', + 'exercise_groups') diff --git a/core/views/common_views.py b/core/views/common_views.py index d7713fe9fd7ccf8f7e5362404eb9018ba1cb6989..324d3350d48f91cdf5ec0136f021b8239ec3ae34 100644 --- a/core/views/common_views.py +++ b/core/views/common_views.py @@ -31,7 +31,8 @@ from core.serializers import (ExamSerializer, StudentInfoSerializer, SubmissionNoTypeSerializer, StudentSubmissionSerializer, SubmissionTypeSerializer, CorrectorSerializer, UserAccountSerializer, SolutionCommentSerializer, - SubmissionNoTypeWithStudentSerializer) + SubmissionNoTypeWithStudentSerializer, + GroupSerializer) log = logging.getLogger(__name__) config = constance.config @@ -328,6 +329,37 @@ class UserAccountViewSet(viewsets.ReadOnlyModelViewSet): user.save() return Response(status.HTTP_200_OK) + @action(detail=True, methods=['patch'], permission_classes=(IsReviewer,)) + def change_groups(self, request, *args, **kwargs): + print("\n data: ") + print(request.data) + print("\n") + print(type("hi")) + # for some reason only the newly added groups come as a group object + groups = [x.get('pk') if type(x) is not str else x for x in request.data] + req_user = request.user + user = self.get_object() + if groups is None: + error_msg = "You need to provide an 'groups' field" + return Response({'Error': error_msg}, status.HTTP_400_BAD_REQUEST) + if req_user.is_student() or req_user.is_tutor(): + return Response(status.HTTP_403_FORBIDDEN) + user.set_groups(groups) + user.save() + return Response(status.HTTP_200_OK) + + @action(detail=True) + def get_groups(self, request, *args, **kwargs): + req_user = request.user + if req_user.is_student() or req_user.is_tutor(): + return Response(status.HTTP_403_FORBIDDEN) + user = self.get_object() + print("\n\n\n Data: \n") + print(type(user.exercise_groups)) + user_groups = [GroupSerializer(group) for group in user.exercise_groups.all()] + return Response(user.exercise_groups, status=status.HTTP_200_OK) + + @action(detail=False) def me(self, request): serializer = self.get_serializer(request.user) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 2197ebe9d8f5bb4f10c1dd74584b85fd5334dd0e..4ed1c8f67a3ca286cd46a786661b42bdaa148bc6 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -153,6 +153,15 @@ export async function fetchGroups(): Promise<Group[]> { return (await ax.get(url)).data } +export async function fetchUserGroups(userPk: string): Promise<Group[]> { + const url = `/api/user/${userPk}/get_groups/` + return (await ax.get(url)).data +} + +export async function setGroups (userPk: string, groups: Group[]): Promise<UserAccount> { + return (await ax.patch(`/api/user/${userPk}/change_groups/`, groups)).data +} + export async function deleteSolutionComment (pk: number): Promise<AxiosResponse<void>> { const url = `/api/solution-comment/${pk}/` return ax.delete(url) @@ -217,6 +226,10 @@ export async function fetchUsers (): Promise<UserAccount[]> { return (await ax.get('api/user/')).data } +export async function fetchUser(userPk: string): Promise<UserAccount> { + return (await ax.get(`/api/user/${userPk}`)).data +} + export async function getLabels (): Promise<FeedbackLabel[]> { return (await ax.get('/api/label/')).data } diff --git a/frontend/src/components/tutor_list/TutorList.vue b/frontend/src/components/tutor_list/TutorList.vue index 1d1e5e9e23338db1be59477c792bd148fb99918b..758278c491b072a262836810f5a5c7ba449c5f2b 100644 --- a/frontend/src/components/tutor_list/TutorList.vue +++ b/frontend/src/components/tutor_list/TutorList.vue @@ -34,6 +34,22 @@ <span>Free locked submissions</span> </v-tooltip> </template> + <template #item.exerciseGroups="{ item }"> + <v-select + v-model="item.exerciseGroups" + item-text="name" + item-value="pk" + :items="groups" + label="Set Groups" + single-line + return-object + flat + class="mr-6" + multiple + chips + @change="setExerciseGroups($event, item)" + /> + </template> <template #item.isActive="{ item }"> <v-btn v-if="canRevokeAccess(item.username)" @@ -69,11 +85,13 @@ <script lang="ts"> import Vue from 'vue' import Component from 'vue-class-component' -import { changeActiveForUser } from '@/api' +import { changeActiveForUser, setGroups, fetchUserGroups, fetchUser } from '@/api' import { actions } from '@/store/actions' import { Authentication } from '@/store/modules/authentication' import { TutorOverview } from '@/store/modules/tutor-overview' -import { Tutor } from '@/models' +import { Group, Tutor } from '@/models' +import { Assignments } from '@/store/modules/assignments' + @Component export default class TutorList extends Vue { @@ -98,6 +116,11 @@ export default class TutorList extends Vue { align: 'right', value: 'reservedSubmissions' }, + { + text: 'Exercise Groups', + align: 'right', + value: 'exerciseGroups' + }, { text: 'Has Access', align: 'right', @@ -106,13 +129,42 @@ export default class TutorList extends Vue { ] get tutors () { - return TutorOverview.state.tutors.map(tutor => { + var tlist = TutorOverview.state.tutors.map(tutor => { + var groups: Group[] + groups = [] + this.userAccountGroups(tutor).then(function(value) { + groups = value // Success! + }, function(reason) { + console.log(reason) // Error! + return [] + }) const reservedSubmissions = TutorOverview.state.activeAssignments[tutor.pk] + //console.log(groups) return { ...tutor, - reservedSubmissions: reservedSubmissions ? reservedSubmissions.length : 0 + reservedSubmissions: reservedSubmissions ? reservedSubmissions.length : 0, } }) + console.log(tlist) + return tlist + } + + get groups () { + return Assignments.state.groups.slice().sort((a, b) => { + const matches_a = a.name.match(/(\d+)/) + const number_a = Number(matches_a === null ? 0 : matches_a[1]) + + const matches_b = b.name.match(/(\d+)/) + const number_b = Number(matches_b === null ? 0 : matches_b[1]) + + return (number_a<number_b?-1:(number_a>number_b?1:0)) + }) + } + + async userAccountGroups(tutor: Tutor) { + const groups = await (await fetchUser(tutor.pk)).exerciseGroups + console.log(groups) + return groups } changeActiveStatus (tutor: Tutor) { @@ -127,6 +179,18 @@ export default class TutorList extends Vue { }) } + setExerciseGroups (groups: Group[], tutor: Tutor){ + setGroups(tutor.pk, groups).then(() => { + TutorOverview.getTutors() + }).catch(() => { + this.$notify({ + title: 'Error', + text: `Unable to change exercise-groups of ${tutor.username}`, + type: 'error' + }) + }) + } + deleteAssignmentsOfTutor (tutor: Tutor) { TutorOverview.deleteActiveAssignmentsOfTutor(tutor) } diff --git a/frontend/src/models.ts b/frontend/src/models.ts index de82f12d4f38f6420dde46c422ed9338f2920760..7215c276f41f9a7641e0daf9889e7214b2c3b69e 100644 --- a/frontend/src/models.ts +++ b/frontend/src/models.ts @@ -783,6 +783,12 @@ export interface Tutor { * @memberof Tutor */ feedbackValidated?: string + /** + * + * @type {Group} + * @memberof Tutor + */ + exerciseGroups: Group[] } /**