summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPalaparthy Adityachandra2020-05-13 16:24:52 +0530
committerGitHub2020-05-13 16:24:52 +0530
commitf7825eade425cb51ba053763db9d9fd051b0f4ce (patch)
treebc15b6cf7b5bbab4d4c8cfac5dd43abaaf778777
parentb8636965de8a86e68fd542754678e6826c7e5eac (diff)
parent833b96ac49499810934ada8ba02750623455d0cd (diff)
downloadonline_test-f7825eade425cb51ba053763db9d9fd051b0f4ce.tar.gz
online_test-f7825eade425cb51ba053763db9d9fd051b0f4ce.tar.bz2
online_test-f7825eade425cb51ba053763db9d9fd051b0f4ce.zip
Merge branch 'master' into merge_monitor_regrade
-rw-r--r--.travis.yml1
-rw-r--r--api/__init__.py0
-rw-r--r--api/apps.py5
-rw-r--r--api/serializers.py79
-rw-r--r--api/tests.py906
-rw-r--r--api/urls.py33
-rw-r--r--api/views.py432
-rw-r--r--grades/urls.py2
-rw-r--r--online_test/settings.py23
-rw-r--r--online_test/urls.py2
-rw-r--r--requirements/requirements-common.txt4
-rw-r--r--requirements/requirements-production.txt (renamed from requirements/requirements-py3.txt)0
-rw-r--r--requirements/requirements-py2.txt2
-rw-r--r--yaksh/models.py14
14 files changed, 1500 insertions, 3 deletions
diff --git a/.travis.yml b/.travis.yml
index 09b262a..fd0746c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -31,6 +31,7 @@ script:
- coverage run -p manage.py test -v 2 yaksh
- coverage run -p manage.py test -v 2 grades
- coverage run -p manage.py test -v 2 yaksh.live_server_tests.load_test
+ - coverage run -p manage.py test -v 2 api
after_success:
- coverage combine
diff --git a/api/__init__.py b/api/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/api/__init__.py
diff --git a/api/apps.py b/api/apps.py
new file mode 100644
index 0000000..d87006d
--- /dev/null
+++ b/api/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class ApiConfig(AppConfig):
+ name = 'api'
diff --git a/api/serializers.py b/api/serializers.py
new file mode 100644
index 0000000..1c1e6a4
--- /dev/null
+++ b/api/serializers.py
@@ -0,0 +1,79 @@
+from rest_framework import serializers
+from yaksh.models import (
+ Question, Quiz, QuestionPaper, AnswerPaper, Course,
+ LearningModule, LearningUnit, Lesson
+)
+
+
+class QuestionSerializer(serializers.ModelSerializer):
+ test_cases = serializers.SerializerMethodField()
+
+ def get_test_cases(self, obj):
+ test_cases = obj.get_test_cases_as_dict()
+ return test_cases
+
+ class Meta:
+ model = Question
+ exclude = ('partial_grading', )
+
+
+class QuizSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Quiz
+ exclude = ('view_answerpaper', )
+
+
+class QuestionPaperSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = QuestionPaper
+ fields = '__all__'
+
+
+class AnswerPaperSerializer(serializers.ModelSerializer):
+
+ questions = QuestionSerializer(many=True)
+
+ class Meta:
+ model = AnswerPaper
+ fields = '__all__'
+
+
+class LessonSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Lesson
+ fields = '__all__'
+
+
+class LearningUnitSerializer(serializers.ModelSerializer):
+
+ quiz = QuizSerializer()
+ lesson = LessonSerializer()
+
+ class Meta:
+ model = LearningUnit
+ fields = '__all__'
+
+
+class LearningModuleSerializer(serializers.ModelSerializer):
+
+ learning_unit = LearningUnitSerializer(many=True)
+
+ class Meta:
+ model = LearningModule
+ fields = '__all__'
+
+
+class CourseSerializer(serializers.ModelSerializer):
+
+ learning_module = LearningModuleSerializer(many=True)
+
+ class Meta:
+ model = Course
+ exclude = (
+ 'teachers',
+ 'rejected',
+ 'requests',
+ 'students',
+ 'grading_system',
+ 'view_grade',
+ )
diff --git a/api/tests.py b/api/tests.py
new file mode 100644
index 0000000..4ef6fa4
--- /dev/null
+++ b/api/tests.py
@@ -0,0 +1,906 @@
+from django.test import TestCase
+from django.urls import reverse
+from django.contrib.auth.models import User
+from rest_framework.test import APIClient
+from rest_framework import status
+from yaksh.models import (
+ Question, Quiz, QuestionPaper, QuestionSet,
+ AnswerPaper, Course, LearningModule, LearningUnit, StandardTestCase,
+ McqTestCase, Profile
+)
+from api.serializers import (
+ QuestionSerializer, QuizSerializer,
+ QuestionPaperSerializer, AnswerPaperSerializer
+)
+from datetime import datetime
+import pytz
+from textwrap import dedent
+from yaksh.settings import SERVER_POOL_PORT
+from yaksh.code_server import ServerPool
+from yaksh import settings
+from threading import Thread
+import time
+import json
+
+
+class QuestionListTestCase(TestCase):
+ """ Test get all questions and create a new question """
+
+ def setUp(self):
+ self.client = APIClient()
+ self.username = 'demo'
+ self.password = 'demo'
+ self.user = User.objects.create_user(username=self.username,
+ password=self.password)
+ Question.objects.create(summary='test question 1', language='python',
+ type='mcq', user=self.user)
+ Question.objects.create(summary='test question 2', language='python',
+ type='mcq', user=self.user)
+
+ def test_get_all_questions_anonymous(self):
+ # When
+ response = self.client.get(reverse('api:questions'))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_get_all_questions(self):
+ # Given
+ questions = Question.objects.filter(user=self.user)
+ serializer = QuestionSerializer(questions, many=True)
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:questions'))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 2)
+ self.assertEqual(response.data, serializer.data)
+
+ def test_create_question_invalid_data(self):
+ # Given
+ data = {'summary': 'Add test question', 'user': self.user.id}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(reverse('api:questions'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(Question.objects.filter(
+ summary='Add test question').exists())
+
+ def test_create_question_valid_data(self):
+ # Given
+ data = {'summary': 'Add test question', 'description': 'test',
+ 'language': 'python', 'type': 'mcq', 'user': self.user.id}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(reverse('api:questions'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertTrue(Question.objects.filter(
+ summary='Add test question').exists())
+
+ def tearDown(self):
+ self.client.logout()
+ User.objects.all().delete()
+ Question.objects.all().delete()
+
+
+class QuestionDetailTestCase(TestCase):
+ """ Test get, update or delete a question """
+
+ def setUp(self):
+ self.client = APIClient()
+ self.username = 'demo'
+ self.password = 'demo'
+ self.otherusername = 'otheruser'
+ self.user = User.objects.create_user(username=self.username,
+ password=self.password)
+ self.otheruser = User.objects.create_user(username=self.otherusername,
+ password=self.password)
+ Question.objects.create(summary='test question', language='python',
+ type='mcq', user=self.user)
+ Question.objects.create(summary='delete question', language='python',
+ type='mcq', user=self.user)
+ Question.objects.create(summary='Created by other user',
+ language='python', type='mcq',
+ user=self.otheruser)
+
+ def test_get_question_anonymous(self):
+ # Given
+ question = Question.objects.get(summary='test question')
+ # When
+ response = self.client.get(reverse('api:question',
+ kwargs={'pk': question.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_get_question_invalid_pk(self):
+ # Given
+ invalid_pk = 3243
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:question',
+ kwargs={'pk': invalid_pk}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_get_question(self):
+ # Given
+ question = Question.objects.get(summary='test question')
+ serializer = QuestionSerializer(question)
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:question',
+ kwargs={'pk': question.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data, serializer.data)
+
+ def test_get_question_not_own(self):
+ # Given
+ question = Question.objects.get(summary='Created by other user')
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:question',
+ kwargs={'pk': question.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_edit_question_anonymous(self):
+ # Given
+ question = Question.objects.get(summary='test question')
+ data = {'summary': 'Edited test question', 'description': 'test',
+ 'language': 'python', 'type': 'mcq', 'user': self.user.id}
+ # When
+ response = self.client.put(reverse('api:question',
+ kwargs={'pk': question.id}), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_edit_question_invalid_data(self):
+ # Given
+ question = Question.objects.get(summary='test question')
+ data = {'summary': 'Edited test question', 'user': self.user.id}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.put(reverse('api:question',
+ kwargs={'pk': question.id}), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_edit_question(self):
+ # Given
+ question = Question.objects.get(summary='test question')
+ data = {'summary': 'Edited test question', 'description': 'test',
+ 'language': 'python', 'type': 'mcq', 'user': self.user.id}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.put(reverse('api:question',
+ kwargs={'pk': question.id}), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ question = Question.objects.get(pk=question.pk)
+ self.assertEqual(question.summary, 'Edited test question')
+
+ def test_delete_question_anonymous(self):
+ # Given
+ question = Question.objects.get(summary='delete question')
+ # When
+ response = self.client.delete(reverse('api:question',
+ kwargs={'pk': question.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_delete_question_not_own(self):
+ # Given
+ question = Question.objects.get(summary='Created by other user')
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.delete(reverse('api:question',
+ kwargs={'pk': question.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+ self.assertTrue(Question.objects.filter(pk=question.id).exists())
+
+ def test_delete_question(self):
+ # Given
+ question = Question.objects.get(summary='delete question')
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.delete(reverse('api:question',
+ kwargs={'pk': question.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
+ self.assertFalse(Question.objects.filter(pk=question.id).exists())
+
+ def tearDown(self):
+ self.client.logout()
+ User.objects.all().delete()
+ Question.objects.all().delete()
+
+
+class QuestionPaperListTestCase(TestCase):
+ """ Test get all question paper and create a new question paper """
+
+ def setUp(self):
+ self.client = APIClient()
+ self.username = 'demo'
+ self.otherusername = 'otheruser'
+ self.password = 'demo'
+ self.user = User.objects.create_user(username=self.username,
+ password=self.password)
+ self.otheruser = User.objects.create_user(username=self.otherusername,
+ password=self.password)
+ self.quiz1 = Quiz.objects.create(description='Quiz1',
+ creator=self.user)
+ self.quiz2 = Quiz.objects.create(description='Quiz2',
+ creator=self.user)
+ self.quiz3 = Quiz.objects.create(description='Quiz3',
+ creator=self.otheruser)
+ self.quiz4 = Quiz.objects.create(description='Quiz4',
+ creator=self.user)
+ self.quiz5 = Quiz.objects.create(description='Quiz5',
+ creator=self.user)
+ self.quiz6 = Quiz.objects.create(description='Quiz6',
+ creator=self.otheruser)
+ self.questionpaper = QuestionPaper.objects.create(quiz=self.quiz1)
+ self.questionpaper2 = QuestionPaper.objects.create(quiz=self.quiz2)
+ QuestionPaper.objects.create(quiz=self.quiz3)
+
+ self.question1 = Question.objects.create(summary='Q1', user=self.user,
+ language='python', type='mcq')
+ self.question2 = Question.objects.create(summary='Q2', user=self.user,
+ language='python', type='mcq')
+ self.question3 = Question.objects.create(summary='Q3', user=self.user,
+ language='python', type='mcq')
+ self.question4 = Question.objects.create(summary='Q4', user=self.user,
+ language='python', type='mcq')
+ self.question5 = Question.objects.create(summary='Q5',
+ user=self.otheruser,
+ language='python', type='mcq')
+ self.questionset = QuestionSet.objects.create(marks=1, num_questions=1)
+ self.questionset.questions.add(self.question3)
+ self.questionset.questions.add(self.question4)
+ self.questionset.save()
+ self.questionpaper.fixed_questions.add(self.question1)
+ self.questionpaper.fixed_questions.add(self.question2)
+ self.questionpaper.random_questions.add(self.questionset)
+ self.questionpaper.save()
+ self.questionpaper.update_total_marks()
+
+ def test_get_all_questionpapers_anonymous(self):
+ # When
+ response = self.client.get(reverse('api:questionpapers'))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_get_all_questionpaper(self):
+ # Given
+ questionpapers = QuestionPaper.objects.filter(
+ quiz__in=[self.quiz1, self.quiz2]
+ )
+ serializer = QuestionPaperSerializer(questionpapers, many=True)
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:questionpapers'))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 2)
+ self.assertEqual(response.data, serializer.data)
+
+ def test_create_questionpaper_invalid_data(self):
+ # Given
+ data = {'fixed_questions': [self.question1.id], 'user': self.user.id}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(reverse('api:questionpapers'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_create_questionpaper_valid_data(self):
+ # Given
+ data = {'quiz': self.quiz4.id,
+ 'fixed_questions': [self.question1.id, self.question2.id],
+ 'random_questions': [self.questionset.id]}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(reverse('api:questionpapers'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertTrue(QuestionPaper.objects.filter(quiz=self.quiz4).exists())
+
+ def test_create_questionpaper_not_own_quiz(self):
+ # Given
+ data = {'quiz': self.quiz5.id, 'fixed_questions': [self.question1.id],
+ 'random_questions': [self.questionset.id]}
+ # When
+ self.client.login(username=self.otherusername, password=self.password)
+ response = self.client.post(reverse('api:questionpapers'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_create_questionpaper_not_own_questions(self):
+ # Given
+ data = {'quiz': self.quiz6.id,
+ 'fixed_questions': [self.question1.id, self.question5.id],
+ 'random_questions': [self.questionset.id]}
+ # When
+ self.client.login(username=self.otherusername, password=self.password)
+ response = self.client.post(reverse('api:questionpapers'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_create_questionpaper_not_own_questionsets(self):
+ # Given
+ data = {'quiz': self.quiz6.id,
+ 'fixed_questions': [self.question5.id],
+ 'random_questions': [self.questionset.id]}
+ # When
+ self.client.login(username=self.otherusername, password=self.password)
+ response = self.client.post(reverse('api:questionpapers'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_create_questionpaper_already_exists(self):
+ # Given
+ data = {'quiz': self.quiz1.id,
+ 'fixed_questions': [self.question1.id],
+ 'random_questions': [self.questionset.id]}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(reverse('api:questionpapers'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
+ self.assertEqual(QuestionPaper.objects.filter(
+ quiz=self.quiz1).count(), 1)
+
+ # QuestionPaper Detail Tests
+ def test_get_questionpaper(self):
+ # Given
+ questionpaper = self.questionpaper
+ serializer = QuestionPaperSerializer(questionpaper)
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:questionpaper',
+ kwargs={'pk': questionpaper.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data, serializer.data)
+
+ def test_get_questionpaper_not_own(self):
+ # Given
+ questionpaper = self.questionpaper
+ # When
+ self.client.login(username=self.otherusername, password=self.password)
+ response = self.client.get(reverse('api:questionpaper',
+ kwargs={'pk': questionpaper.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_edit_questionpaper(self):
+ # Given
+ questionpaper = self.questionpaper2
+ data = {'quiz': self.quiz5.id,
+ 'fixed_questions': [self.question1.id, self.question2.id],
+ 'random_questions': [self.questionset.id]}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.put(reverse('api:questionpaper',
+ kwargs={'pk': questionpaper.id}), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ questionpaper = QuestionPaper.objects.get(pk=questionpaper.id)
+ self.assertEqual(questionpaper.quiz.id, self.quiz5.id)
+
+ def test_delete_questionpaper(self):
+ # Given
+ questionpaper = self.questionpaper2
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.delete(reverse('api:questionpaper',
+ kwargs={'pk': questionpaper.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
+ questionpapers = QuestionPaper.objects.filter(quiz=self.quiz2)
+ self.assertFalse(questionpapers.exists())
+
+ def tearDown(self):
+ self.client.logout()
+ User.objects.all().delete()
+ Question.objects.all().delete()
+ QuestionPaper.objects.all().delete()
+ Quiz.objects.all().delete()
+
+
+class QuizListTestCase(TestCase):
+ """ Test get all quizzes and create a new quiz """
+
+ def setUp(self):
+ self.client = APIClient()
+ self.username = 'demo'
+ self.password = 'demo'
+ self.user = User.objects.create_user(username=self.username,
+ password=self.password)
+ Quiz.objects.create(description='Test Quiz 1', creator=self.user)
+ Quiz.objects.create(description='Test Quiz 2', creator=self.user)
+
+ def test_get_all_quizzes_anonymous(self):
+ # When
+ response = self.client.get(reverse('api:quizzes'))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_get_all_quizzes(self):
+ # Given
+ quizzes = Quiz.objects.filter(creator=self.user)
+ serializer = QuizSerializer(quizzes, many=True)
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:quizzes'))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 2)
+ self.assertEqual(response.data, serializer.data)
+
+ def test_create_quiz_invalid_data(self):
+ # Given
+ data = {'creator': self.user.id}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(reverse('api:quizzes'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_create_quiz_valid_data(self):
+ # Given
+ data = {'description': 'Added quiz', 'creator': self.user.id}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(reverse('api:quizzes'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertTrue(Quiz.objects.filter(description='Added quiz').exists())
+
+ def tearDown(self):
+ self.client.logout()
+ User.objects.all().delete()
+ Quiz.objects.all().delete()
+
+
+class QuizDetailTestCase(TestCase):
+ """ Test get, update or delete a quiz """
+
+ def setUp(self):
+ self.client = APIClient()
+ self.username = 'quizuser'
+ self.password = 'demo'
+ self.otherusername = 'quizuser2'
+ self.user = User.objects.create_user(username=self.username,
+ password=self.password)
+ self.otheruser = User.objects.create_user(username=self.otherusername,
+ password=self.password)
+ Quiz.objects.create(description='Quiz1', creator=self.user)
+ Quiz.objects.create(description='Quiz2', creator=self.user)
+ Quiz.objects.create(description='delete quiz', creator=self.user)
+ Quiz.objects.create(description='Quiz3', creator=self.otheruser)
+
+ def test_get_quiz_anonymous(self):
+ # Given
+ quiz = Quiz.objects.get(description='Quiz1')
+ # When
+ response = self.client.get(reverse('api:quiz', kwargs={'pk': quiz.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_get_quiz_invalid_pk(self):
+ # Given
+ invalid_pk = 3242
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:quiz',
+ kwargs={'pk': invalid_pk}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_get_quiz(self):
+ # Given
+ quiz = Quiz.objects.get(description='Quiz1')
+ serializer = QuizSerializer(quiz)
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:quiz',
+ kwargs={'pk': quiz.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data, serializer.data)
+
+ def test_get_quiz_not_own(self):
+ # Given
+ quiz = Quiz.objects.get(description='Quiz3')
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:quiz', kwargs={'pk': quiz.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+ def test_edit_quiz_anonymous(self):
+ # Given
+ quiz = Quiz.objects.get(description='Quiz1')
+ data = {'description': 'Quiz1 Edited', 'creator': self.user.id}
+ # When
+ response = self.client.put(reverse('api:quiz', kwargs={'pk': quiz.id}),
+ data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_edit_quiz_invalid_data(self):
+ # Given
+ quiz = Quiz.objects.get(description='Quiz1')
+ data = {'creator': self.user.id}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.put(reverse('api:quiz', kwargs={'pk': quiz.id}),
+ data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_edit_quiz(self):
+ # Given
+ quiz = Quiz.objects.get(description='Quiz2')
+ data = {'description': 'Quiz2 edited', 'creator': self.user.id}
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.put(reverse('api:quiz', kwargs={'pk': quiz.id}),
+ data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ quiz = Quiz.objects.get(pk=quiz.id)
+ self.assertEqual(quiz.description, 'Quiz2 edited')
+
+ def test_delete_quiz_anonymous(self):
+ # Given
+ quiz = Quiz.objects.get(description='delete quiz')
+ # When
+ response = self.client.delete(reverse('api:quiz',
+ kwargs={'pk': quiz.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+ def test_delete_quiz_not_own(self):
+ # Given
+ quiz = Quiz.objects.get(description='Quiz3')
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.delete(reverse('api:quiz',
+ kwargs={'pk': quiz.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+ self.assertTrue(Quiz.objects.filter(pk=quiz.id).exists())
+
+ def test_delete_quiz(self):
+ # Given
+ quiz = Quiz.objects.get(description='delete quiz')
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.delete(reverse('api:quiz',
+ kwargs={'pk': quiz.id}))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
+ self.assertFalse(Quiz.objects.filter(pk=quiz.id).exists())
+
+ def tearDown(self):
+ self.client.logout()
+ User.objects.all().delete()
+ Quiz.objects.all().delete()
+
+
+class AnswerPaperListTestCase(TestCase):
+
+ def setUp(self):
+ self.client = APIClient()
+ self.username = 'demo'
+ self.otherusername = 'otheruser'
+ self.studentusername = 'student'
+ self.password = 'demo'
+ self.user = User.objects.create_user(username=self.username,
+ password=self.password)
+ self.otheruser = User.objects.create_user(username=self.otherusername,
+ password=self.password)
+ self.student = User.objects.create_user(username=self.studentusername,
+ password='demo')
+ self.quiz1 = Quiz.objects.create(description='Quiz1',
+ creator=self.user)
+ self.quiz2 = Quiz.objects.create(description='Quiz2',
+ creator=self.otheruser)
+ self.questionpaper1 = QuestionPaper.objects.create(quiz=self.quiz1)
+ self.questionpaper2 = QuestionPaper.objects.create(quiz=self.quiz2)
+ self.question1 = Question.objects.create(summary='Q1', user=self.user,
+ language='python', type='mcq')
+ self.question2 = Question.objects.create(summary='Q5',
+ user=self.otheruser,
+ language='python', type='mcq')
+ self.questionpaper1.fixed_questions.add(self.question1)
+ self.questionpaper2.fixed_questions.add(self.question2)
+ self.questionpaper1.save()
+ self.questionpaper2.save()
+ self.questionpaper1.update_total_marks()
+ self.questionpaper2.update_total_marks()
+ self.answerpaper1 = AnswerPaper.objects.create(
+ user=self.user,
+ question_paper=self.questionpaper1, attempt_number=1,
+ start_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzinfo=pytz.utc),
+ end_time=datetime(2015, 10, 9, 10, 28, 15, 0, tzinfo=pytz.utc)
+ )
+ self.answerpaper2 = AnswerPaper.objects.create(
+ user=self.otheruser,
+ question_paper=self.questionpaper2, attempt_number=1,
+ start_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzinfo=pytz.utc),
+ end_time=datetime(2015, 10, 9, 10, 28, 15, 0, tzinfo=pytz.utc)
+ )
+ self.course = Course.objects.create(name="Python Course",
+ enrollment="Enroll Request",
+ creator=self.user)
+ # Learing module
+ learning_module = LearningModule.objects.create(
+ name='LM1', description='module one', creator=self.user
+ )
+ learning_unit_quiz = LearningUnit.objects.create(quiz=self.quiz1,
+ type='quiz', order=1)
+ learning_module.learning_unit.add(learning_unit_quiz)
+ learning_module.save()
+ self.course.learning_module.add(learning_module)
+ self.course.students.add(self.student)
+ self.course.save()
+
+ def test_get_all_answerpapers(self):
+ # Given
+ answerpapers = [self.answerpaper1]
+ serializer = AnswerPaperSerializer(answerpapers, many=True)
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.get(reverse('api:answerpapers'))
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(len(response.data), 1)
+ self.assertEqual(response.data, serializer.data)
+
+ def test_create_answerpaper_valid_data(self):
+ # Given
+ data = {'question_paper': self.questionpaper1.id,
+ 'attempt_number': 1, 'course': self.course.id}
+ # When
+ self.client.login(username=self.studentusername,
+ password=self.password)
+ response = self.client.post(reverse('api:answerpapers'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ answerpapers = AnswerPaper.objects.filter(
+ user=self.student, attempt_number=1,
+ question_paper=self.questionpaper1, course=self.course
+ )
+ self.assertTrue(answerpapers.exists())
+ self.assertEqual(answerpapers.count(), 1)
+
+ def test_create_answerpaper_invalid_data(self):
+ # Given
+ data = {'question_paper': self.questionpaper1.id}
+ # When
+ self.client.login(username=self.studentusername,
+ password=self.password)
+ response = self.client.post(reverse('api:answerpapers'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+ def test_create_answerpaper_not_enrolled(self):
+ # Given
+ data = {'question_paper': self.questionpaper1.id,
+ 'attempt_number': 1, 'course': self.course.id}
+ # When
+ self.client.login(username=self.otherusername, password=self.password)
+ response = self.client.post(reverse('api:answerpapers'), data)
+ # Then
+ self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+ answerpapers = AnswerPaper.objects.filter(
+ question_paper=self.questionpaper1, user=self.otheruser,
+ attempt_number=1, course=self.course
+ )
+ self.assertFalse(answerpapers.exists())
+ self.assertEqual(answerpapers.count(), 0)
+
+ def tearDown(self):
+ self.client.logout()
+ User.objects.all().delete()
+ Question.objects.all().delete()
+ QuestionPaper.objects.all().delete()
+ Quiz.objects.all().delete()
+ AnswerPaper.objects.all().delete()
+
+
+class AnswerValidatorTestCase(TestCase):
+
+ @classmethod
+ def setUpClass(self):
+ self.client = APIClient()
+ self.username = 'demo'
+ self.password = 'demo'
+ self.user = User.objects.create_user(username=self.username,
+ password=self.password)
+ Profile.objects.create(user=self.user)
+ self.quiz = Quiz.objects.create(description='Quiz', creator=self.user)
+ self.questionpaper = QuestionPaper.objects.create(quiz=self.quiz)
+ self.question1 = Question.objects.create(summary='Q1', user=self.user,
+ points=1.0, language='python',
+ type='code')
+ self.question2 = Question.objects.create(summary='Q2', user=self.user,
+ points=1.0, language='python',
+ type='mcq')
+ self.question3 = Question.objects.create(summary='Q3', user=self.user,
+ points=1.0, language='python',
+ type='mcc')
+ self.question4 = Question.objects.create(summary='Q4', user=self.user,
+ points=1.0, language='python',
+ type='mcq')
+ self.question5 = Question.objects.create(summary='Q5', user=self.user,
+ points=1.0, language='python',
+ type='mcq')
+ self.assertion_testcase = StandardTestCase(
+ question=self.question1,
+ test_case='assert add(1, 3) == 4',
+ type='standardtestcase'
+ )
+ self.assertion_testcase.save()
+ self.mcq_based_testcase1 = McqTestCase(
+ options='a',
+ question=self.question2,
+ correct=True,
+ type='mcqtestcase'
+ )
+ self.mcq_based_testcase1.save()
+ self.mcq_based_testcase2 = McqTestCase(
+ options='b',
+ question=self.question2,
+ correct=False,
+ type='mcqtestcase'
+ )
+ self.mcq_based_testcase2.save()
+ self.mcc_based_testcase = McqTestCase(
+ question=self.question3,
+ options='a',
+ correct=True,
+ type='mcqtestcase'
+ )
+ self.mcc_based_testcase.save()
+ self.questionset = QuestionSet.objects.create(marks=1, num_questions=1)
+ self.questionset.questions.add(self.question3)
+ self.questionset.questions.add(self.question4)
+ self.questionset.save()
+ self.questionpaper.fixed_questions.add(self.question1)
+ self.questionpaper.fixed_questions.add(self.question2)
+ self.questionpaper.random_questions.add(self.questionset)
+ self.questionpaper.save()
+ self.questionpaper.update_total_marks()
+ self.course = Course.objects.create(name="Python Course",
+ enrollment="Enroll Request",
+ creator=self.user)
+ # Learing module
+ learning_module = LearningModule.objects.create(
+ name='LM1', description='module one', creator=self.user
+ )
+ learning_unit_quiz = LearningUnit.objects.create(quiz=self.quiz,
+ type='quiz', order=1)
+ learning_module.learning_unit.add(learning_unit_quiz)
+ learning_module.save()
+ self.course.learning_module.add(learning_module)
+ self.course.students.add(self.user)
+ self.course.save()
+ self.ip = '127.0.0.1'
+ self.answerpaper = self.questionpaper.make_answerpaper(
+ self.user, self.ip, 1, self.course.id
+ )
+
+ settings.code_evaluators['python']['standardtestcase'] = \
+ "yaksh.python_assertion_evaluator.PythonAssertionEvaluator"
+ server_pool = ServerPool(n=1, pool_port=SERVER_POOL_PORT)
+ self.server_pool = server_pool
+ self.server_thread = t = Thread(target=server_pool.run)
+ t.start()
+
+ @classmethod
+ def tearDownClass(self):
+ self.client.logout()
+ User.objects.all().delete()
+ Question.objects.all().delete()
+ QuestionPaper.objects.all().delete()
+ Quiz.objects.all().delete()
+ AnswerPaper.objects.all().delete()
+ self.server_pool.stop()
+ self.server_thread.join()
+ settings.code_evaluators['python']['standardtestcase'] = \
+ "python_assertion_evaluator.PythonAssertionEvaluator"
+
+ def test_correct_mcq(self):
+ # Given
+ data = {'answer': str(self.mcq_based_testcase1.id)}
+ answerpaper_id = self.answerpaper.id
+ question_id = self.question2.id
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(
+ reverse('api:validators', kwargs={'answerpaper_id': answerpaper_id,
+ 'question_id': question_id}), data
+ )
+ # Then
+ self.assertTrue(response.status_code, status.HTTP_200_OK)
+ self.assertTrue(response.data.get('success'))
+ answerpaper = AnswerPaper.objects.get(
+ user=self.user, course=self.course, attempt_number=1,
+ question_paper=self.questionpaper
+ )
+ self.assertTrue(answerpaper.marks_obtained > 0)
+
+ def test_wrong_mcq(self):
+ # Given
+ data = {'answer': str(self.mcq_based_testcase2.id)}
+ answerpaper_id = self.answerpaper.id
+ question_id = self.question2.id
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(
+ reverse('api:validators', kwargs={'answerpaper_id': answerpaper_id,
+ 'question_id': question_id}), data
+ )
+ # Then
+ self.assertTrue(response.status_code, status.HTTP_200_OK)
+ self.assertFalse(response.data.get('success'))
+
+ def test_correct_mcc(self):
+ # Given
+ data = {'answer': str(self.mcc_based_testcase.id)}
+ answerpaper_id = self.answerpaper.id
+ question_id = self.question3.id
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(
+ reverse('api:validators', kwargs={'answerpaper_id': answerpaper_id,
+ 'question_id': question_id}), data
+ )
+ # Then
+ self.assertTrue(response.status_code, status.HTTP_200_OK)
+ self.assertTrue(response.data.get('success'))
+ answerpaper = AnswerPaper.objects.get(
+ user=self.user, course=self.course, attempt_number=1,
+ question_paper=self.questionpaper
+ )
+ self.assertTrue(answerpaper.marks_obtained > 0)
+
+ def test_correct_code(self):
+ # Given
+ answer = dedent("""\
+ def add(a,b):
+ return a+b
+ """)
+ data = {'answer': answer}
+ answerpaper_id = self.answerpaper.id
+ question_id = self.question1.id
+ # When
+ self.client.login(username=self.username, password=self.password)
+ response = self.client.post(
+ reverse('api:validators', kwargs={'answerpaper_id': answerpaper_id,
+ 'question_id': question_id}), data
+ )
+ # Then
+ self.assertTrue(response.status_code, status.HTTP_200_OK)
+ self.assertEqual(response.data.get('status'), 'running')
+ uid = response.data['uid']
+ time.sleep(2)
+ response = self.client.get(reverse('api:validator',
+ kwargs={'uid': uid}))
+ self.assertTrue(response.status_code, status.HTTP_200_OK)
+ answerpaper = AnswerPaper.objects.get(
+ user=self.user, course=self.course, attempt_number=1,
+ question_paper=self.questionpaper
+ )
+ if response.data.get('status') == 'done':
+ result = json.loads(response.data.get('result'))
+ self.assertTrue(result.get('success'))
+ else:
+ self.assertEqual(response.data.get('status'), 'running')
diff --git a/api/urls.py b/api/urls.py
new file mode 100644
index 0000000..f519aea
--- /dev/null
+++ b/api/urls.py
@@ -0,0 +1,33 @@
+from django.conf.urls import url
+from rest_framework.urlpatterns import format_suffix_patterns
+from api import views
+
+app_name = 'api'
+
+urlpatterns = [
+ url(r'questions/$', views.QuestionList.as_view(), name='questions'),
+ url(r'questions/(?P<pk>[0-9]+)/$', views.QuestionDetail.as_view(),
+ name='question'),
+ url(r'get_courses/$', views.CourseList.as_view(), name='get_courses'),
+ url(r'start_quiz/(?P<course_id>[0-9]+)/(?P<quiz_id>[0-9]+)/$', views.StartQuiz.as_view(),
+ name='start_quiz'),
+ url(r'quizzes/$', views.QuizList.as_view(), name='quizzes'),
+ url(r'quizzes/(?P<pk>[0-9]+)/$', views.QuizDetail.as_view(), name='quiz'),
+ url(r'questionpapers/$', views.QuestionPaperList.as_view(),
+ name='questionpapers'),
+ url(r'questionpapers/(?P<pk>[0-9]+)/$',
+ views.QuestionPaperDetail.as_view(), name='questionpaper'),
+ url(r'answerpapers/$', views.AnswerPaperList.as_view(),
+ name='answerpapers'),
+ url(r'validate/(?P<answerpaper_id>[0-9]+)/(?P<question_id>[0-9]+)/$',
+ views.AnswerValidator.as_view(), name='validators'),
+ url(r'validate/(?P<uid>[0-9]+)/$',
+ views.AnswerValidator.as_view(), name='validator'),
+ url(r'course/(?P<pk>[0-9]+)/$',
+ views.GetCourse.as_view(), name='get_course'),
+ url(r'quit/(?P<answerpaper_id>\d+)/$', views.QuitQuiz.as_view(),
+ name="quit_quiz"),
+ url(r'login/$', views.login, name='login')
+]
+
+urlpatterns = format_suffix_patterns(urlpatterns)
diff --git a/api/views.py b/api/views.py
new file mode 100644
index 0000000..8d2da83
--- /dev/null
+++ b/api/views.py
@@ -0,0 +1,432 @@
+from yaksh.models import (
+ Question, Quiz, QuestionPaper, QuestionSet, AnswerPaper, Course, Answer
+)
+from api.serializers import (
+ QuestionSerializer, QuizSerializer, QuestionPaperSerializer,
+ AnswerPaperSerializer, CourseSerializer
+)
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework import permissions
+from rest_framework.authtoken.models import Token
+from rest_framework.decorators import (
+ api_view, authentication_classes, permission_classes
+)
+from django.http import Http404
+from django.contrib.auth import authenticate
+from yaksh.code_server import get_result as get_result_from_code_server
+from yaksh.settings import SERVER_POOL_PORT, SERVER_HOST_NAME
+import json
+
+
+class QuestionList(APIView):
+ """ List all questions or create a new question. """
+
+ def get(self, request, format=None):
+ questions = Question.objects.filter(user=request.user)
+ serializer = QuestionSerializer(questions, many=True)
+ return Response(serializer.data)
+
+ def post(self, request, format=None):
+ serializer = QuestionSerializer(data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+class CourseList(APIView):
+ """ List all courses """
+
+ def get(self, request, format=None):
+ courses = Course.objects.filter(students=request.user)
+ serializer = CourseSerializer(courses, many=True)
+ return Response(serializer.data)
+
+
+class StartQuiz(APIView):
+ """ Retrieve Answerpaper. If does not exists then create one """
+
+ def get_quiz(self, pk, user):
+ try:
+ return Quiz.objects.get(pk=pk)
+ except Quiz.DoesNotExist:
+ raise Http404
+
+ def get(self, request, course_id, quiz_id, format=None):
+ context = {}
+ user = request.user
+ quiz = self.get_quiz(quiz_id, user)
+ questionpaper = quiz.questionpaper_set.first()
+
+ last_attempt = AnswerPaper.objects.get_user_last_attempt(
+ questionpaper, user, course_id)
+ if last_attempt and last_attempt.is_attempt_inprogress():
+ serializer = AnswerPaperSerializer(last_attempt)
+ context["time_left"] = last_attempt.time_left()
+ context["answerpaper"] = serializer.data
+ return Response(context)
+
+ can_attempt, msg = questionpaper.can_attempt_now(user, course_id)
+ if not can_attempt:
+ return Response({'message': msg})
+ if not last_attempt:
+ attempt_number = 1
+ else:
+ attempt_number = last_attempt.attempt_number + 1
+ ip = request.META['REMOTE_ADDR']
+ answerpaper = questionpaper.make_answerpaper(user, ip, attempt_number,
+ course_id)
+ serializer = AnswerPaperSerializer(answerpaper)
+ context["time_left"] = answerpaper.time_left()
+ context["answerpaper"] = serializer.data
+ return Response(context, status=status.HTTP_201_CREATED)
+
+
+class QuestionDetail(APIView):
+ """ Retrieve, update or delete a question """
+
+ def get_question(self, pk, user):
+ try:
+ return Question.objects.get(pk=pk, user=user)
+ except Question.DoesNotExist:
+ raise Http404
+
+ def get(self, request, pk, format=None):
+ question = self.get_question(pk, request.user)
+ serializer = QuestionSerializer(question)
+ return Response(serializer.data)
+
+ def put(self, request, pk, format=None):
+ question = self.get_question(pk, request.user)
+ serializer = QuestionSerializer(question, data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ def delete(self, request, pk, format=None):
+ question = self.get_question(pk, request.user)
+ question.delete()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+class AnswerPaperList(APIView):
+
+ def get_questionpaper(self, pk):
+ try:
+ return QuestionPaper.objects.get(pk=pk)
+ except QuestionPaper.DoesNotExist:
+ raise Http404
+
+ def get_course(self, pk):
+ try:
+ return Course.objects.get(pk=pk)
+ except Course.DoesNotExist:
+ raise Http404
+
+ def get_answerpapers(self, user):
+ return AnswerPaper.objects.filter(question_paper__quiz__creator=user)
+
+ def get(self, request, format=None):
+ user = request.user
+ answerpapers = self.get_answerpapers(user)
+ serializer = AnswerPaperSerializer(answerpapers, many=True)
+ return Response(serializer.data)
+
+ def is_user_allowed(self, user, course):
+ ''' if user is student or teacher or creator then allow '''
+ return user in course.students.all() or user in course.teachers.all() \
+ or user == course.creator
+
+ def post(self, request, format=None):
+ try:
+ questionpaperid = request.data['question_paper']
+ attempt_number = request.data['attempt_number']
+ course_id = request.data['course']
+ except KeyError:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ user = request.user
+ ip = request.META['REMOTE_ADDR']
+ questionpaper = self.get_questionpaper(questionpaperid)
+ course = self.get_course(course_id)
+ if not self.is_user_allowed(user, course):
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ answerpaper = questionpaper.make_answerpaper(user, ip, attempt_number,
+ course_id)
+ serializer = AnswerPaperSerializer(answerpaper)
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+
+
+class AnswerValidator(APIView):
+
+ def get_answerpaper(self, pk, user):
+ try:
+ return AnswerPaper.objects.get(pk=pk, user=user)
+ except AnswerPaper.DoesNotExist:
+ raise Http404
+
+ def get_question(self, pk, answerpaper):
+ try:
+ question = Question.objects.get(pk=pk)
+ if question in answerpaper.questions.all():
+ return question
+ else:
+ raise Http404
+ except AnswerPaper.DoesNotExist:
+ raise Http404
+
+ def get_answer(self, pk):
+ try:
+ return Answer.objects.get(pk=pk)
+ except Answer.DoesNotExist:
+ raise Http404
+
+ def post(self, request, answerpaper_id, question_id, format=None):
+ user = request.user
+ answerpaper = self.get_answerpaper(answerpaper_id, user)
+ question = self.get_question(question_id, answerpaper)
+ try:
+ if question.type == 'mcq' or question.type == 'mcc':
+ user_answer = request.data['answer']
+ elif question.type == 'integer':
+ user_answer = int(request.data['answer'][0])
+ elif question.type == 'float':
+ user_answer = float(request.data['answer'][0])
+ elif question.type == 'string':
+ user_answer = request.data['answer']
+ else:
+ user_answer = request.data['answer']
+ except KeyError:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ # save answer uid
+ answer = Answer.objects.create(question=question, answer=user_answer)
+ answerpaper.answers.add(answer)
+ answerpaper.save()
+ json_data = None
+ if question.type in ['code', 'upload']:
+ json_data = question.consolidate_answer_data(user_answer, user)
+ result = answerpaper.validate_answer(user_answer, question, json_data,
+ answer.id)
+
+ # updaTE RESult
+ if question.type not in ['code', 'upload']:
+ if result.get('success'):
+ answer.correct = True
+ answer.marks = question.points
+ answer.error = json.dumps(result.get('error'))
+ answer.save()
+ answerpaper.update_marks(state='inprogress')
+ return Response(result)
+
+ def get(self, request, uid):
+ answer = self.get_answer(uid)
+ url = '{0}:{1}'.format(SERVER_HOST_NAME, SERVER_POOL_PORT)
+ result = get_result_from_code_server(url, uid)
+ # update result
+ if result['status'] == 'done':
+ final_result = json.loads(result.get('result'))
+ answer.error = json.dumps(final_result.get('error'))
+ if final_result.get('success'):
+ answer.correct = True
+ answer.marks = answer.question.points
+ answer.save()
+ answerpaper = answer.answerpaper_set.get()
+ answerpaper.update_marks(state='inprogress')
+ return Response(result)
+
+
+class QuizList(APIView):
+ """ List all quizzes or create a new quiz """
+
+ def get(self, request, format=None):
+ quizzes = Quiz.objects.filter(creator=request.user)
+ serializer = QuizSerializer(quizzes, many=True)
+ return Response(serializer.data)
+
+ def post(self, request, format=None):
+ serializer = QuizSerializer(data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+class QuizDetail(APIView):
+ """ Retrieve, update or delete a quiz """
+
+ def get_quiz(self, pk, user):
+ try:
+ return Quiz.objects.get(pk=pk, creator=user)
+ except Quiz.DoesNotExist:
+ raise Http404
+
+ def get(self, request, pk, format=None):
+ quiz = self.get_quiz(pk, request.user)
+ serializer = QuizSerializer(quiz)
+ return Response(serializer.data)
+
+ def put(self, request, pk, format=None):
+ quiz = self.get_quiz(pk, request.user)
+ serializer = QuizSerializer(quiz, data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ def delete(self, request, pk, format=None):
+ quiz = self.get_quiz(pk, request.user)
+ quiz.delete()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+class QuestionPaperList(APIView):
+ """ List all question papers or create a new question paper """
+
+ def get_questionpapers(self, user):
+ return QuestionPaper.objects.filter(quiz__creator=user)
+
+ def questionpaper_exists(self, quiz_id):
+ return QuestionPaper.objects.filter(quiz=quiz_id).exists()
+
+ def check_quiz_creator(self, user, quiz_id):
+ try:
+ Quiz.objects.get(pk=quiz_id, creator=user)
+ except Quiz.DoesNotExist:
+ raise Http404
+
+ def check_questions_creator(self, user, question_ids):
+ for question_id in question_ids:
+ try:
+ Question.objects.get(pk=question_id, user=user)
+ except Question.DoesNotExist:
+ raise Http404
+
+ def check_questionsets_creator(self, user, questionset_ids):
+ for question_id in questionset_ids:
+ try:
+ questionset = QuestionSet.objects.get(pk=question_id)
+ for question in questionset.questions.all():
+ Question.objects.get(pk=question.id, user=user)
+ except (QuestionSet.DoesNotExist, Question.DoesNotExist):
+ raise Http404
+
+ def get(self, request, format=None):
+ questionpapers = self.get_questionpapers(request.user)
+ serializer = QuestionPaperSerializer(questionpapers, many=True)
+ return Response(serializer.data)
+
+ def post(self, request, format=None):
+ serializer = QuestionPaperSerializer(data=request.data)
+ if serializer.is_valid():
+ user = request.user
+ quiz_id = request.data.get('quiz')
+ question_ids = request.data.get('fixed_questions', [])
+ questionset_ids = request.data.get('random_questions', [])
+ if self.questionpaper_exists(quiz_id):
+ return Response({'error': 'Already exists'},
+ status=status.HTTP_409_CONFLICT)
+ self.check_quiz_creator(user, quiz_id)
+ self.check_questions_creator(user, question_ids)
+ self.check_questionsets_creator(user, questionset_ids)
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status.HTTP_400_BAD_REQUEST)
+
+
+class QuestionPaperDetail(APIView):
+ """ Retrieve, update or delete a question paper"""
+
+ def get_questionpaper(self, pk, user):
+ try:
+ return QuestionPaper.objects.get(pk=pk, quiz__creator=user)
+ except QuestionPaper.DoesNotExist:
+ raise Http404
+
+ def get(self, request, pk, format=None):
+ questionpaper = self.get_questionpaper(pk, request.user)
+ serializer = QuestionPaperSerializer(questionpaper)
+ return Response(serializer.data)
+
+ def check_quiz_creator(self, user, quiz_id):
+ try:
+ Quiz.objects.get(pk=quiz_id, creator=user)
+ except Quiz.DoesNotExist:
+ raise Http404
+
+ def check_questions_creator(self, user, question_ids):
+ for question_id in question_ids:
+ try:
+ Question.objects.get(pk=question_id, user=user)
+ except Question.DoesNotExist:
+ raise Http404
+
+ def check_questionsets_creator(self, user, questionset_ids):
+ for question_id in questionset_ids:
+ try:
+ questionset = QuestionSet.objects.get(pk=question_id)
+ for question in questionset.questions.all():
+ Question.objects.get(pk=question.id, user=user)
+ except (QuestionSet.DoesNotExist, Question.DoesNotExist):
+ raise Http404
+
+ def put(self, request, pk, format=None):
+ user = request.user
+ questionpaper = self.get_questionpaper(pk, user)
+ serializer = QuestionPaperSerializer(questionpaper, data=request.data)
+ if serializer.is_valid():
+ user = request.user
+ quiz_id = request.data.get('quiz')
+ question_ids = request.data.get('fixed_questions', [])
+ questionset_ids = request.data.get('random_questions', [])
+ self.check_quiz_creator(user, quiz_id)
+ self.check_questions_creator(user, question_ids)
+ self.check_questionsets_creator(user, questionset_ids)
+ serializer.save()
+ return Response(serializer.data)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ def delete(self, request, pk, format=None):
+ questionpaper = self.get_questionpaper(pk, request.user)
+ questionpaper.delete()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+class GetCourse(APIView):
+ def get(self, request, pk, format=None):
+ course = Course.objects.get(id=pk)
+ serializer = CourseSerializer(course)
+ return Response(serializer.data)
+
+
+@api_view(['POST'])
+@authentication_classes(())
+@permission_classes(())
+def login(request):
+ data = {}
+ if request.method == "POST":
+ username = request.data.get('username')
+ password = request.data.get('password')
+ user = authenticate(username=username, password=password)
+ if user is not None and user.is_authenticated:
+ token, created = Token.objects.get_or_create(user=user)
+ data = {
+ 'token': token.key
+ }
+ return Response(data, status=status.HTTP_201_CREATED)
+
+
+class QuitQuiz(APIView):
+ def get_answerpaper(self, answerpaper_id):
+ try:
+ return AnswerPaper.objects.get(id=answerpaper_id)
+ except AnswerPaper.DoesNotExist:
+ raise Http404
+
+ def get(self, request, answerpaper_id, format=None):
+ answerpaper = self.get_answerpaper(answerpaper_id)
+ answerpaper.status = 'completed'
+ answerpaper.save()
+ serializer = AnswerPaperSerializer(answerpaper)
+ return Response(serializer.data) \ No newline at end of file
diff --git a/grades/urls.py b/grades/urls.py
index 32a7e4d..f5616a8 100644
--- a/grades/urls.py
+++ b/grades/urls.py
@@ -1,6 +1,8 @@
from django.conf.urls import url
from grades import views
+app_name = 'grades'
+
urlpatterns = [
url(r'^$', views.grading_systems, name="grading_systems_home"),
url(r'^grading_systems/$', views.grading_systems, name="grading_systems"),
diff --git a/online_test/settings.py b/online_test/settings.py
index ad52804..3b89c28 100644
--- a/online_test/settings.py
+++ b/online_test/settings.py
@@ -48,10 +48,15 @@ INSTALLED_APPS = (
'django_celery_beat',
'django_celery_results',
'notifications_plugin',
+ 'rest_framework',
+ 'api',
+ 'corsheaders',
+ 'rest_framework.authtoken'
)
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -214,6 +219,7 @@ AUTH_PASSWORD_VALIDATORS = [
TAGGIT_CASE_INSENSITIVE = True
+
# Celery parameters
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers.DatabaseScheduler'
CELERY_TASK_SERIALIZER = 'json'
@@ -222,3 +228,20 @@ CELERY_ACCEPT_CONTENT = ['json']
CELERY_TIMEZONE = 'Asia/Kolkata'
CELERY_BROKER_URL = 'redis://localhost'
CELERY_RESULT_BACKEND = 'django-db'
+
+REST_FRAMEWORK = {
+ # Use Django's standard `django.contrib.auth` permissions,
+ # or allow read-only access for unauthenticated users.
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
+ 'rest_framework.authentication.TokenAuthentication',
+ 'rest_framework.authentication.BasicAuthentication',
+ 'rest_framework.authentication.SessionAuthentication',
+ ],
+ 'DEFAULT_PERMISSION_CLASSES': [
+ 'rest_framework.permissions.IsAuthenticated'
+ ],
+ 'TEST_REQUEST_DEFAULT_FORMAT': 'json'
+}
+
+CORS_ORIGIN_ALLOW_ALL = True
+CORS_ALLOW_CREDENTIALS = True
diff --git a/online_test/urls.py b/online_test/urls.py
index 52cbd40..bb5a04a 100644
--- a/online_test/urls.py
+++ b/online_test/urls.py
@@ -16,5 +16,7 @@ urlpatterns = [
url(r'^exam/reset/', include('django.contrib.auth.urls')),
url(r'^', include('social_django.urls', namespace='social')),
url(r'^grades/', include(('grades.urls', 'grades'))),
+ url(r'^api/', include('api.urls', namespace='api')),
+
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/requirements/requirements-common.txt b/requirements/requirements-common.txt
index aae82f3..db5de43 100644
--- a/requirements/requirements-common.txt
+++ b/requirements/requirements-common.txt
@@ -7,7 +7,7 @@ requests-oauthlib>=0.6.1
social-auth-app-django==3.1.0
selenium==2.53.6
coverage
-ruamel.yaml==0.15.23
+ruamel.yaml==0.16.10
markdown==2.6.9
pygments==2.2.0
celery==4.4.2
@@ -15,4 +15,6 @@ redis==3.4.1
notifications-plugin==0.1.2
django-celery-beat==2.0.0
django-celery-results==1.2.1
+djangorestframework==3.11.0
+django-cors-headers==3.1.0
Pillow
diff --git a/requirements/requirements-py3.txt b/requirements/requirements-production.txt
index 3d13335..3d13335 100644
--- a/requirements/requirements-py3.txt
+++ b/requirements/requirements-production.txt
diff --git a/requirements/requirements-py2.txt b/requirements/requirements-py2.txt
deleted file mode 100644
index 38777a1..0000000
--- a/requirements/requirements-py2.txt
+++ /dev/null
@@ -1,2 +0,0 @@
--r requirements-common.txt
-mysql-python==1.2.5
diff --git a/yaksh/models.py b/yaksh/models.py
index 9bcb132..64489b8 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -1452,6 +1452,19 @@ class Question(models.Model):
tc_list.extend(test_case)
return tc_list
+ def get_test_cases_as_dict(self, **kwargs):
+ tc_list = []
+ for tc in self.testcase_set.values_list("type", flat=True).distinct():
+ test_case_ctype = ContentType.objects.get(app_label="yaksh",
+ model=tc)
+ test_case = test_case_ctype.get_all_objects_for_this_type(
+ question=self,
+ **kwargs
+ )
+ for tc in test_case:
+ tc_list.append(model_to_dict(tc))
+ return tc_list
+
def get_test_case(self, **kwargs):
for tc in self.testcase_set.all():
test_case_type = tc.type
@@ -1743,6 +1756,7 @@ class QuestionPaper(models.Model):
for question_set in self.random_questions.all():
marks += question_set.marks * question_set.num_questions
self.total_marks = marks
+ self.save()
def _get_questions_for_answerpaper(self):
""" Returns fixed and random questions for the answer paper"""