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 | 79 | ||||
-rw-r--r-- | api/tests.py | 906 | ||||
-rw-r--r-- | api/urls.py | 33 | ||||
-rw-r--r-- | api/views.py | 432 | ||||
-rw-r--r-- | grades/urls.py | 2 | ||||
-rw-r--r-- | online_test/settings.py | 23 | ||||
-rw-r--r-- | online_test/urls.py | 2 | ||||
-rw-r--r-- | requirements/requirements-common.txt | 4 | ||||
-rw-r--r-- | requirements/requirements-production.txt (renamed from requirements/requirements-py3.txt) | 0 | ||||
-rw-r--r-- | requirements/requirements-py2.txt | 2 | ||||
-rw-r--r-- | yaksh/models.py | 14 |
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""" |