diff --git a/backend/api/serializers/group_serializer.py b/backend/api/serializers/group_serializer.py index 3496bcbc..3865f7a2 100644 --- a/backend/api/serializers/group_serializer.py +++ b/backend/api/serializers/group_serializer.py @@ -1,6 +1,8 @@ from api.models.group import Group from api.models.student import Student -from api.permissions.role_permissions import is_student +from api.models.assistant import Assistant +from api.models.teacher import Teacher +from api.permissions.role_permissions import is_student, is_assistant, is_teacher from api.serializers.project_serializer import ProjectSerializer from api.serializers.student_serializer import StudentIDSerializer from django.utils.translation import gettext @@ -30,12 +32,17 @@ class Meta: def to_representation(self, instance): data = super().to_representation(instance) - # If you are not a student, you can always see the score - if is_student(self.context["request"].user): - # Student can not see the score if they are not part of the group, or it is not visible yet - if not instance.students.filter(id=self.context["request"].user.student.id).exists() or\ - not instance.project.score_visible: + user = self.context["request"].user + course_id = instance.project.course.id + # If you are not a student, you can always see the score + if is_student(user): + student_in_course = user.student.courses.filter(id=course_id).exists() + # Student can not see the score if they are not part of the course associated with group and + # neither an assistant or a teacher, + # or it is not visible yet when they are part of the course associated with the group + if not student_in_course and not is_assistant(user) and not is_teacher(user) or \ + not instance.project.score_visible and student_in_course: data.pop("score") return data diff --git a/frontend/src/assets/lang/app/en.json b/frontend/src/assets/lang/app/en.json index d2f951a7..6478779f 100644 --- a/frontend/src/assets/lang/app/en.json +++ b/frontend/src/assets/lang/app/en.json @@ -167,7 +167,8 @@ "searchCourse": "Search a course", "createCourse": "Create a new course", "public": "Public", - "protected": "Protected" + "protected": "Protected", + "csv": "Download grades as a .csv file" }, "card": { "open": "Details", diff --git a/frontend/src/assets/lang/app/nl.json b/frontend/src/assets/lang/app/nl.json index 92456aa0..2cca7874 100644 --- a/frontend/src/assets/lang/app/nl.json +++ b/frontend/src/assets/lang/app/nl.json @@ -164,7 +164,8 @@ "searchCourse": "Zoek een vak", "createCourse": "Maak een vak", "public": "Publiek", - "protected": "Besloten" + "protected": "Besloten", + "csv": "Download punten als een .csv bestand" }, "card": { "open": "Details", diff --git a/frontend/src/components/projects/DownloadCSVButton.vue b/frontend/src/components/projects/DownloadCSVButton.vue new file mode 100644 index 00000000..4f6915c3 --- /dev/null +++ b/frontend/src/components/projects/DownloadCSVButton.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/frontend/src/composables/services/student.service.ts b/frontend/src/composables/services/student.service.ts index 8c1e62b2..c70c67bd 100644 --- a/frontend/src/composables/services/student.service.ts +++ b/frontend/src/composables/services/student.service.ts @@ -73,7 +73,7 @@ export function useStudents(): StudentsState { endpoint, { user: studentData.id, - student_id: studentData.studentId, + student_id: studentData.student_id, }, student, Student.fromJSON, diff --git a/frontend/src/test/unit/services/setup/data.ts b/frontend/src/test/unit/services/setup/data.ts index 1ad12327..f25084c9 100644 --- a/frontend/src/test/unit/services/setup/data.ts +++ b/frontend/src/test/unit/services/setup/data.ts @@ -158,7 +158,7 @@ export const students = [ last_name: 'Doe', last_enrolled: 2023, create_time: new Date('July 21, 2024 01:15:00'), - studentId: null, + student_id: null, roles: ['student'], courses: ['1', '2', '3'], groups: ['0'], @@ -174,7 +174,7 @@ export const students = [ last_name: 'Verhaege', last_enrolled: 2023, create_time: new Date('July 21, 2024 01:15:00'), - studentId: null, + student_id: null, roles: ['student'], courses: [], groups: [], @@ -190,7 +190,7 @@ export const students = [ last_name: 'Verslype', last_enrolled: 2023, create_time: new Date('July 21, 2024 01:15:00'), - studentId: '02012470', + student_id: '02012470', roles: ['student'], courses: [], groups: [], @@ -206,7 +206,7 @@ export const students = [ last_name: 'somtin', last_enrolled: 2023, create_time: new Date('July 21, 2024 01:15:00'), - studentId: null, + student_id: null, roles: ['student'], courses: [], groups: [], diff --git a/frontend/src/test/unit/services/setup/post_handlers.ts b/frontend/src/test/unit/services/setup/post_handlers.ts index 6ae01af8..5409c5df 100644 --- a/frontend/src/test/unit/services/setup/post_handlers.ts +++ b/frontend/src/test/unit/services/setup/post_handlers.ts @@ -37,7 +37,7 @@ export const postHandlers = [ const buffer = await request.arrayBuffer(); const requestBody = new TextDecoder().decode(buffer); const newStudent = JSON.parse(requestBody); - students.push({ id: newStudent.user, studentId: newStudent.student_id, ...newStudent }); + students.push({ id: newStudent.user, student_id: newStudent.student_id, ...newStudent }); return HttpResponse.json(students); }), http.post(baseUrl + endpoints.courses.index, async ({ request }) => { diff --git a/frontend/src/test/unit/services/student_service.test.ts b/frontend/src/test/unit/services/student_service.test.ts index d8c18467..6b8ddc12 100644 --- a/frontend/src/test/unit/services/student_service.test.ts +++ b/frontend/src/test/unit/services/student_service.test.ts @@ -38,7 +38,7 @@ describe('students', (): void => { expect(student.value?.first_name).toBe('John'); expect(student.value?.last_name).toBe('Doe'); expect(student.value?.last_enrolled).toBe(2023); - expect(student.value?.studentId).toBeNull(); + expect(student.value?.student_id).toBeNull(); expect(student.value?.last_login).toBeNull(); expect(student.value?.create_time.toISOString()).toEqual('2024-07-20T23:15:00.000Z'); expect(student.value?.courses).toEqual([]); @@ -60,7 +60,7 @@ describe('students', (): void => { expect(students.value?.[0]?.first_name).toBe('John'); expect(students.value?.[0]?.last_name).toBe('Doe'); expect(students.value?.[0]?.last_enrolled).toBe(2023); - expect(students.value?.[0]?.studentId).toBeNull(); + expect(students.value?.[0]?.student_id).toBeNull(); expect(students.value?.[0]?.last_login).toBeNull(); expect(students.value?.[0]?.create_time).toEqual(new Date('July 21, 2024 01:15:00')); expect(students.value?.[0]?.courses).toEqual([]); @@ -73,7 +73,7 @@ describe('students', (): void => { expect(students.value?.[1]?.first_name).toBe('Bartje'); expect(students.value?.[1]?.last_name).toBe('Verhaege'); expect(students.value?.[1]?.last_enrolled).toBe(2023); - expect(students.value?.[1]?.studentId).toBeNull(); + expect(students.value?.[1]?.student_id).toBeNull(); expect(students.value?.[1]?.last_login).toBeNull(); expect(students.value?.[1]?.create_time).toEqual(new Date('July 21, 2024 01:15:00')); expect(students.value?.[1]?.courses).toEqual([]); @@ -86,7 +86,7 @@ describe('students', (): void => { expect(students.value?.[2]?.first_name).toBe('Tybo'); expect(students.value?.[2]?.last_name).toBe('Verslype'); expect(students.value?.[2]?.last_enrolled).toBe(2023); - expect(students.value?.[2]?.studentId).toBe('02012470'); + expect(students.value?.[2]?.student_id).toBe('02012470'); expect(students.value?.[2]?.last_login).toEqual(new Date('July 30, 2024 01:15:00')); expect(students.value?.[2]?.create_time).toEqual(new Date('July 21, 2024 01:15:00')); expect(students.value?.[2]?.courses).toEqual([]); @@ -99,7 +99,7 @@ describe('students', (): void => { expect(students.value?.[3]?.first_name).toBe('somtin'); expect(students.value?.[3]?.last_name).toBe('somtin'); expect(students.value?.[3]?.last_enrolled).toBe(2023); - expect(students.value?.[3]?.studentId).toBeNull(); + expect(students.value?.[3]?.student_id).toBeNull(); expect(students.value?.[3]?.last_login).toBeNull(); expect(students.value?.[3]?.create_time).toEqual(new Date('July 21, 2024 01:15:00')); expect(students.value?.[3]?.courses).toEqual([]); @@ -121,7 +121,7 @@ describe('students', (): void => { expect(students.value?.[0]?.first_name).toBe('John'); expect(students.value?.[0]?.last_name).toBe('Doe'); expect(students.value?.[0]?.last_enrolled).toBe(2023); - expect(students.value?.[0]?.studentId).toBeNull(); + expect(students.value?.[0]?.student_id).toBeNull(); expect(students.value?.[0]?.last_login).toBeNull(); expect(students.value?.[0]?.create_time).toEqual(new Date('July 21, 2024 01:15:00')); expect(students.value?.[0]?.courses).toEqual([]); @@ -134,7 +134,7 @@ describe('students', (): void => { expect(students.value?.[1]?.first_name).toBe('Bartje'); expect(students.value?.[1]?.last_name).toBe('Verhaege'); expect(students.value?.[1]?.last_enrolled).toBe(2023); - expect(students.value?.[1]?.studentId).toBeNull(); + expect(students.value?.[1]?.student_id).toBeNull(); expect(students.value?.[1]?.last_login).toBeNull(); expect(students.value?.[1]?.create_time).toEqual(new Date('July 21, 2024 01:15:00')); expect(students.value?.[1]?.courses).toEqual([]); @@ -147,7 +147,7 @@ describe('students', (): void => { expect(students.value?.[2]?.first_name).toBe('Tybo'); expect(students.value?.[2]?.last_name).toBe('Verslype'); expect(students.value?.[2]?.last_enrolled).toBe(2023); - expect(students.value?.[2]?.studentId).toBe('02012470'); + expect(students.value?.[2]?.student_id).toBe('02012470'); expect(students.value?.[2]?.last_login).toEqual(new Date('July 30, 2024 01:15:00')); expect(students.value?.[2]?.create_time).toEqual(new Date('July 21, 2024 01:15:00')); expect(students.value?.[2]?.courses).toEqual([]); @@ -160,7 +160,7 @@ describe('students', (): void => { expect(students.value?.[3]?.first_name).toBe('somtin'); expect(students.value?.[3]?.last_name).toBe('somtin'); expect(students.value?.[3]?.last_enrolled).toBe(2023); - expect(students.value?.[3]?.studentId).toBeNull(); + expect(students.value?.[3]?.student_id).toBeNull(); expect(students.value?.[3]?.last_login).toBeNull(); expect(students.value?.[3]?.create_time).toEqual(new Date('July 21, 2024 01:15:00')); expect(students.value?.[3]?.courses).toEqual([]); @@ -181,7 +181,7 @@ describe('students', (): void => { 2024, // last_enrolled new Date(), // create_time null, // last_login - 'studentId', // studentId + 'student_id', // student_id [], [], [], @@ -202,6 +202,6 @@ describe('students', (): void => { // Only check for fields that are sent to the backend expect(students.value?.[prevLength]?.id).toBe('id'); - expect(students.value?.[prevLength]?.studentId).toBe('studentId'); + expect(students.value?.[prevLength]?.student_id).toBe('student_id'); }); }); diff --git a/frontend/src/test/unit/types/data.ts b/frontend/src/test/unit/types/data.ts index 307e73bf..9c004c27 100644 --- a/frontend/src/test/unit/types/data.ts +++ b/frontend/src/test/unit/types/data.ts @@ -26,7 +26,7 @@ export const studentData = { courses: [], create_time: new Date(), last_login: null, - studentId: '1', + student_id: '1', groups: [], }; diff --git a/frontend/src/test/unit/types/helper.ts b/frontend/src/test/unit/types/helper.ts index 97604c47..60c32d6b 100644 --- a/frontend/src/test/unit/types/helper.ts +++ b/frontend/src/test/unit/types/helper.ts @@ -25,7 +25,7 @@ export function createStudent(studentData: any): Student { studentData.last_enrolled, studentData.create_time, studentData.last_login, - studentData.studentId, + studentData.student_id, studentData.roles.slice(), studentData.courses.slice(), studentData.groups.slice(), diff --git a/frontend/src/test/unit/types/student.test.ts b/frontend/src/test/unit/types/student.test.ts index 07f7856c..a1a1978e 100644 --- a/frontend/src/test/unit/types/student.test.ts +++ b/frontend/src/test/unit/types/student.test.ts @@ -18,7 +18,7 @@ describe('student type', () => { expect(student.last_enrolled).toBe(studentData.last_enrolled); expect(student.create_time).toStrictEqual(studentData.create_time); expect(student.last_login).toStrictEqual(studentData.last_login); - expect(student.studentId).toBe(studentData.studentId); + expect(student.student_id).toBe(studentData.student_id); expect(student.roles).toStrictEqual(studentData.roles); expect(student.courses).toStrictEqual(studentData.courses); expect(student.groups).toStrictEqual(studentData.groups); @@ -39,7 +39,7 @@ describe('student type', () => { expect(student.last_enrolled).toBe(studentData.last_enrolled); expect(student.create_time).toStrictEqual(studentData.create_time); expect(student.last_login).toStrictEqual(studentData.last_login); - expect(student.studentId).toBe(studentData.studentId); + expect(student.student_id).toBe(studentData.student_id); expect(student.roles).toStrictEqual(studentData.roles); expect(student.courses).toStrictEqual(studentData.courses); expect(student.groups).toStrictEqual(studentData.groups); diff --git a/frontend/src/types/users/Student.ts b/frontend/src/types/users/Student.ts index 8ba17f8c..34285a98 100644 --- a/frontend/src/types/users/Student.ts +++ b/frontend/src/types/users/Student.ts @@ -14,7 +14,7 @@ export class Student extends User { public last_enrolled: number, public create_time: Date, public last_login: Date | null, - public studentId: string, + public student_id: string, public roles: Role[] = [], public courses: Course[] = [], public groups: Group[] = [], @@ -51,7 +51,7 @@ export class Student extends User { student.last_enrolled, new Date(student.create_time), student.last_login !== null ? new Date(student.last_login) : null, - student.studentId, + student.student_id, ); } diff --git a/frontend/src/views/admin/UsersView.vue b/frontend/src/views/admin/UsersView.vue index 42c329ac..c073e34b 100644 --- a/frontend/src/views/admin/UsersView.vue +++ b/frontend/src/views/admin/UsersView.vue @@ -104,7 +104,7 @@ const saveItem = async (): Promise => { if (role === 'student') { const data: Record = { ...editItem.value, - studentId: editItem.value.id, + student_id: editItem.value.id, }; await func(data); } else { diff --git a/frontend/src/views/projects/roles/TeacherProjectView.vue b/frontend/src/views/projects/roles/TeacherProjectView.vue index b83406b8..328b22d6 100644 --- a/frontend/src/views/projects/roles/TeacherProjectView.vue +++ b/frontend/src/views/projects/roles/TeacherProjectView.vue @@ -5,6 +5,7 @@ import { type Teacher } from '@/types/users/Teacher.ts'; import { type Project } from '@/types/Project.ts'; import ProjectInfo from '@/components/projects/ProjectInfo.vue'; import ProjectMeter from '@/components/projects/ProjectMeter.vue'; +import DownloadCSVButton from '@/components/projects/DownloadCSVButton.vue'; /* Props */ defineProps<{ @@ -36,6 +37,11 @@ defineProps<{
+
+ +