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