diff --git a/backend/pigeonhole/apps/projects/permissions.py b/backend/pigeonhole/apps/projects/permissions.py
index 5e03ef11..e05c47a4 100644
--- a/backend/pigeonhole/apps/projects/permissions.py
+++ b/backend/pigeonhole/apps/projects/permissions.py
@@ -24,7 +24,9 @@ def has_permission(self, request, view):
return False
elif view.action in ['get_group_submissions']:
return True
- elif view.action in ['download_submissions', 'download_testfiles']:
+ elif view.action in ['download_submissions', 'download_testfiles',
+ 'get_last_group_submissions',
+ 'get_submissions']:
return user.is_teacher or user.is_admin or user.is_superuser
elif view.action in ['get_group']:
return True
diff --git a/backend/pigeonhole/apps/projects/views.py b/backend/pigeonhole/apps/projects/views.py
index dae5b424..8f606cab 100644
--- a/backend/pigeonhole/apps/projects/views.py
+++ b/backend/pigeonhole/apps/projects/views.py
@@ -222,6 +222,28 @@ def get_last_submission(self, request, *args, **kwargs):
SubmissionsSerializer(submissions.first()).data, status=status.HTTP_200_OK
)
+ @action(detail=True, methods=["get"])
+ def get_last_group_submissions(self, request, *args, **kwargs):
+ project = self.get_object()
+ groups = Group.objects.filter(project_id=project)
+ latest_submission_ids = []
+
+ for group in groups:
+ latest_submission = Submissions.objects.filter(group_id=group).order_by('-timestamp').first()
+ if latest_submission:
+ latest_submission_ids.append(latest_submission.submission_id)
+
+ queryset = Submissions.objects.filter(submission_id__in=latest_submission_ids)
+ submissions_filter = SubmissionFilter(request.GET, queryset=queryset)
+ filtered_submissions = submissions_filter.qs
+ paginator = CustomPageNumberPagination()
+ paginated_submissions = paginator.paginate_queryset(
+ filtered_submissions, request
+ )
+
+ serializer = SubmissionsSerializer(paginated_submissions, many=True)
+ return paginator.get_paginated_response(serializer.data)
+
@action(detail=True, methods=["get"])
def download_submissions(self, request, *args, **kwargs):
project = self.get_object()
diff --git a/backend/pigeonhole/tests/test_views/test_user/test_teacher.py b/backend/pigeonhole/tests/test_views/test_user/test_teacher.py
index e44e80c5..326f1926 100644
--- a/backend/pigeonhole/tests/test_views/test_user/test_teacher.py
+++ b/backend/pigeonhole/tests/test_views/test_user/test_teacher.py
@@ -60,7 +60,7 @@ def test_update_self(self):
'course': [self.course.course_id]
}
response = self.client.put(f'{API_ENDPOINT}{self.teacher.id}/', updated_data, format='json')
- self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_delete_self(self):
response = self.client.delete(f'{API_ENDPOINT}{self.teacher.id}/')
diff --git a/frontend/app/[locale]/components/LatestSubmissionList.tsx b/frontend/app/[locale]/components/LatestSubmissionList.tsx
new file mode 100644
index 00000000..01644829
--- /dev/null
+++ b/frontend/app/[locale]/components/LatestSubmissionList.tsx
@@ -0,0 +1,42 @@
+"use client"
+
+import ListView from "@app/[locale]/components/ListView";
+import React from "react";
+import {useTranslation} from "react-i18next";
+import GroupsIcon from '@mui/icons-material/Groups';
+import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
+import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
+
+
+const LatestSubmissionList = ({project_id, page_size = 5, search}: {
+ project_id: number,
+ page_size: number,
+ search: string
+}) => {
+ const {t} = useTranslation()
+ const headers = [
+ {" " + t('group_number')}
+ ,
+ {" " + t('submission_date')}
+ ,
+ {" " + t('Status')}
+ , ""]
+ const headers_backend = ["group_nr", "submission_date", "status", ""]
+ const sortable = [true, true, false]
+
+ return (
+
+ )
+}
+
+export default LatestSubmissionList;
\ No newline at end of file
diff --git a/frontend/app/[locale]/components/ListView.tsx b/frontend/app/[locale]/components/ListView.tsx
index d2b40b73..84be9180 100644
--- a/frontend/app/[locale]/components/ListView.tsx
+++ b/frontend/app/[locale]/components/ListView.tsx
@@ -46,7 +46,7 @@ import {
getUsers,
postData,
getOpenCourses,
- fetchUserData
+ fetchUserData, getLatestSubmissions
} from '@lib/api';
import baseTheme from "../../../styles/theme";
import {useTranslation} from "react-i18next";
@@ -191,6 +191,7 @@ const ListView: NextPage = ({
},
'submissions': (data) => [data.submission_id, data.group_id, convertDate(t, data.timestamp), data?.output_simple_test && data?.eval_result],
'submissions_group': (data) => [data.submission_id, data.group_id, convertDate(t, data.timestamp), data?.output_simple_test && data?.eval_result],
+ 'submissions_latest': (data) => [data.submission_id, data.group_id, convertDate(t, data.timestamp), data?.output_simple_test && data?.eval_result],
'archived_courses': (data) => [data.course_id, data.name, data.description, data.open_course],
};
@@ -219,6 +220,9 @@ const ListView: NextPage = ({
'submissions_group': async () => {
return parse_pages(await getGroupSubmissions(get_id, currentPage, page_size, searchTerm, sortConfig.key.toLowerCase(), sortConfig.direction === 'asc' ? 'asc' : 'desc'));
},
+ 'submissions_latest': async () => {
+ return parse_pages(await getLatestSubmissions(get_id, currentPage, page_size, searchTerm, sortConfig.key.toLowerCase(), sortConfig.direction === 'asc' ? 'asc' : 'desc'));
+ },
'archived_courses': async () => {
return parse_pages(await getArchivedCourses(currentPage, page_size, searchTerm));
}
@@ -591,7 +595,7 @@ const ListView: NextPage = ({
)}
- {(get == 'submissions' || get == 'submissions_group') && (
+ {(get == 'submissions' || get == 'submissions_group' || get == 'submissions_latest') && (