diff options
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | api/__init__.py | 0 | ||||
-rw-r--r-- | api/apps.py | 5 | ||||
-rw-r--r-- | api/serializers.py | 25 | ||||
-rw-r--r-- | api/tests.py | 888 | ||||
-rw-r--r-- | api/urls.py | 23 | ||||
-rw-r--r-- | api/views.py | 330 | ||||
-rw-r--r-- | online_test/settings.py | 15 | ||||
-rw-r--r-- | online_test/urls.py | 2 | ||||
-rw-r--r-- | yaksh/models.py | 1 |
10 files changed, 1290 insertions, 0 deletions
diff --git a/.travis.yml b/.travis.yml index 59eaa66..e1e7dc8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ script: - coverage erase - coverage run -p manage.py test -v 2 --settings online_test.test_settings yaksh - coverage run -p manage.py test -v 2 --settings online_test.test_settings grades + - coverage run -p manage.py test -v 2 --settings online_test.test_settings api - coverage run -p manage.py test -v 2 --settings online_test.test_settings yaksh.live_server_tests.load_test after_success: 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..919e429 --- /dev/null +++ b/api/serializers.py @@ -0,0 +1,25 @@ +from rest_framework import serializers +from yaksh.models import Question, Quiz, QuestionPaper, AnswerPaper + + +class QuestionSerializer(serializers.ModelSerializer): + class Meta: + model = Question + fields = '__all__' + + +class QuizSerializer(serializers.ModelSerializer): + class Meta: + model = Quiz + fields = '__all__' + + +class QuestionPaperSerializer(serializers.ModelSerializer): + class Meta: + model = QuestionPaper + fields = '__all__' + +class AnswerPaperSerializer(serializers.ModelSerializer): + class Meta: + model = AnswerPaper + fields = '__all__' diff --git a/api/tests.py b/api/tests.py new file mode 100644 index 0000000..0930685 --- /dev/null +++ b/api/tests.py @@ -0,0 +1,888 @@ +from django.test import TestCase +from django.core.urlresolvers 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) + self.assertFalse(QuestionPaper.objects.filter(quiz=self.quiz2).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(question_paper=self.questionpaper1, + user=self.student, attempt_number=1, + 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['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['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['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['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['status'] == 'done': + result = json.loads(response.data['result']) + self.assertTrue(result['success']) + else: + self.assertEqual(response.data['status'], 'running') + diff --git a/api/urls.py b/api/urls.py new file mode 100644 index 0000000..be276da --- /dev/null +++ b/api/urls.py @@ -0,0 +1,23 @@ +from django.conf.urls import url +from rest_framework.urlpatterns import format_suffix_patterns +from api import views + + +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'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'), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/api/views.py b/api/views.py new file mode 100644 index 0000000..14e04f0 --- /dev/null +++ b/api/views.py @@ -0,0 +1,330 @@ +from yaksh.models import ( + Question, Quiz, QuestionPaper, QuestionSet, AnswerPaper, Course, Answer +) +from api.serializers import ( + QuestionSerializer, QuizSerializer, QuestionPaperSerializer, + AnswerPaperSerializer +) +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from rest_framework import permissions +from django.http import Http404 +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 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): + try: + user_answer = request.data['answer'] + except KeyError: + return Response(status=status.HTTP_400_BAD_REQUEST) + user = request.user + answerpaper = self.get_answerpaper(answerpaper_id, user) + question = self.get_question(question_id, answerpaper) + # 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(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) + diff --git a/online_test/settings.py b/online_test/settings.py index b9a7a2c..1df8a4a 100644 --- a/online_test/settings.py +++ b/online_test/settings.py @@ -45,6 +45,8 @@ INSTALLED_APPS = ( 'taggit', 'social.apps.django_app.default', 'grades', + 'rest_framework', + 'api', ) MIDDLEWARE_CLASSES = ( @@ -187,3 +189,16 @@ SOCIAL_AUTH_FACEBOOK_SCOPE = ['email'] SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'fields': 'id, name, email' } + +REST_FRAMEWORK = { + # Use Django's standard `django.contrib.auth` permissions, + # or allow read-only access for unauthenticated users. + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated' + ], + 'TEST_REQUEST_DEFAULT_FORMAT': 'json' +} diff --git a/online_test/urls.py b/online_test/urls.py index 28c2a26..5517732 100644 --- a/online_test/urls.py +++ b/online_test/urls.py @@ -15,5 +15,7 @@ urlpatterns = [ url(r'^', include('social.apps.django_app.urls', namespace='social')), url(r'^grades/', include('grades.urls', namespace='grades', app_name='grades')), + url(r'^api/', include('api.urls', namespace='api', app_name='api')), + ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/yaksh/models.py b/yaksh/models.py index 60b09c5..e3212fd 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1509,6 +1509,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""" |