From 746572b0b285dec81580253a863adf6ad2f299aa Mon Sep 17 00:00:00 2001 From: Dominik Seeger <dominik.seeger@gmx.net> Date: Tue, 8 Oct 2019 18:49:05 +0200 Subject: [PATCH] added import endpoint --- core/tests/test_import_views.py | 80 +++++++++++++++++++++++++++++++++ core/urls.py | 1 + core/views/__init__.py | 1 + core/views/importer.py | 25 +++++++++++ util/importer.py | 13 ++++-- 5 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 core/tests/test_import_views.py create mode 100644 core/views/importer.py diff --git a/core/tests/test_import_views.py b/core/tests/test_import_views.py new file mode 100644 index 00000000..3d78b199 --- /dev/null +++ b/core/tests/test_import_views.py @@ -0,0 +1,80 @@ +from rest_framework import status +from rest_framework.test import APIClient, APITestCase +from core.models import UserAccount, SubmissionType + +from util.factories import GradyUserFactory + +test_data = { + "meta": { + "version": "3.0.0" + }, + "module": { + "module_reference": "test", + "pass_only": True, + "pass_score": 1, + "total_score": 99 + }, + "students": [ + { + "fullname": "test", + "identifier": "test-test", + "submissions": [ + { + "code": "some messy, perhaps incorrect stuff", + "tests": {}, + "type": "[a0] coding stuff" + }, + { + "code": "i don't know man", + "tests": {}, + "type": "[a1] improvise" + } + ], + } + ], + "submission_types": [ + { + "description": "code some 1337 stuff", + "full_score": 99, + "name": "[a0] coding stuff", + "programming_language": "c", + "solution": "how dare u" + }, + { + "description": "now this one's hard", + "full_score": 1, + "name": "[a1] improvise", + "programming_language": "haskell", + "solution": "nope" + }, + ] +} + + +class ImportViewTest(APITestCase): + + factory = GradyUserFactory() + + def setUp(self): + self.url = '/api/import/' + self.client = APIClient() + self.client.force_login(user=self.factory.make_reviewer()) + + def test_can_not_submit_nothing(self): + res = self.client.post(self.url) + self.assertEqual(status.HTTP_400_BAD_REQUEST, res.status_code) + + def test_will_fail_on_wrong_importer_version(self): + data = {"meta": {"version": "0.0.0"}} + res = self.client.post(self.url, data) + self.assertEqual(status.HTTP_409_CONFLICT, res.status_code) + + def test_data_is_imported_correctly(self): + res = self.client.post(self.url, test_data) + + sub_types = SubmissionType.objects.all() + students = UserAccount.objects.all().filter(role='Student') + + self.assertEqual(2, len(sub_types)) + self.assertEqual(1, len(students)) + self.assertEqual(status.HTTP_201_CREATED, res.status_code) diff --git a/core/urls.py b/core/urls.py index 25fad50d..66dea3ff 100644 --- a/core/urls.py +++ b/core/urls.py @@ -52,6 +52,7 @@ regular_views_urlpatterns = [ name='jwt-time-delta'), path('instance/export/', views.InstanceExport.as_view(), name="instance-export"), path('export/json/', views.StudentJSONExport.as_view(), name='export-json'), + path('import/', views.ImportApiViewSet.as_view(), name='import-json'), 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), diff --git a/core/views/__init__.py b/core/views/__init__.py index 64f568df..c455fbd9 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -3,3 +3,4 @@ from .subscription import SubscriptionApiViewSet, AssignmentApiViewSet # noqa from .common_views import * # noqa from .export import StudentJSONExport, InstanceExport # noqa from .label import LabelApiViewSet, LabelStatistics # noqa +from .importer import ImportApiViewSet # noqa diff --git a/core/views/importer.py b/core/views/importer.py new file mode 100644 index 00000000..ee5c1975 --- /dev/null +++ b/core/views/importer.py @@ -0,0 +1,25 @@ +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.exceptions import ValidationError +from core.permissions import IsReviewer +from util.importer import parse_and_import_hektor_json + + +class ImportApiViewSet(APIView): + permission_classes = (IsReviewer, ) + + def post(self, request): + exam_data = request.data + + if not exam_data: + return Response({"Error": "You need to submit the exam data to be imported"}, + status.HTTP_400_BAD_REQUEST) + + try: + parse_and_import_hektor_json(exam_data) + except ValidationError as err: + return Response({"ValidationError": err.detail}, + status.HTTP_409_CONFLICT) + + return Response({}, status.HTTP_201_CREATED) diff --git a/util/importer.py b/util/importer.py index 6441ba52..70b8a810 100644 --- a/util/importer.py +++ b/util/importer.py @@ -3,6 +3,7 @@ import os import readline import logging +from rest_framework.exceptions import ValidationError from util.messages import warn from core.models import ExamType, Feedback, Submission, SubmissionType, Test, FeedbackLabel from core.models import UserAccount as User @@ -119,11 +120,17 @@ def load_hektor_json(): with open(file, 'r') as f: exam_data = json.JSONDecoder().decode(f.read()) + parse_and_import_hektor_json(exam_data) + + +def parse_and_import_hektor_json(exam_data): hektor_version = exam_data['meta']['version'] if not (semver.match(hektor_version, RUSTY_HEKTOR_MIN_VER) and semver.match(hektor_version, RUSTY_HEKTOR_MAX_VER)): - warn(f'The data you\'re trying to import has the wrong version {hektor_version}\n' - f'Requirements: {RUSTY_HEKTOR_MIN_VER}, {RUSTY_HEKTOR_MAX_VER}') + raise ValidationError( + f'The data you\'re trying to import has the wrong version {hektor_version}\n' + f'Requirements: {RUSTY_HEKTOR_MIN_VER}, {RUSTY_HEKTOR_MAX_VER}' + ) exam, _ = ExamType.objects.get_or_create(**exam_data['module']) @@ -131,7 +138,7 @@ def load_hektor_json(): _, created = SubmissionType.objects.update_or_create( name=submission_type['name'], defaults=submission_type) if not created: - log.warning(f"Updated submission type {submission_type}") + raise ValidationError(f"Updated submission type: {submission_type['name']}") for student in exam_data['students']: student_obj = user_factory.make_student(exam=exam, is_active=False, -- GitLab