From 0e3353d489fd54a2c84c712ae04e44a9f8d8cd90 Mon Sep 17 00:00:00 2001 From: prathamesh Date: Wed, 19 Dec 2018 13:46:40 +0530 Subject: Provide API version 1 for Yaksh - Serializers for Question, Quiz, QuestionPaper, AnswerPaper - Can create questions, quizzes, question and answer papers - Can check mcq, mcc and code questions - Tests for the API --- .travis.yml | 1 + api/__init__.py | 0 api/apps.py | 5 + api/serializers.py | 25 ++ api/tests.py | 888 ++++++++++++++++++++++++++++++++++++++++++++++++ api/urls.py | 23 ++ api/views.py | 330 ++++++++++++++++++ online_test/settings.py | 15 + online_test/urls.py | 2 + yaksh/models.py | 1 + 10 files changed, 1290 insertions(+) create mode 100644 api/__init__.py create mode 100644 api/apps.py create mode 100644 api/serializers.py create mode 100644 api/tests.py create mode 100644 api/urls.py create mode 100644 api/views.py 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 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[0-9]+)/$', views.QuestionDetail.as_view(), + name='question'), + url(r'quizzes/$', views.QuizList.as_view(), name='quizzes'), + url(r'quizzes/(?P[0-9]+)/$', views.QuizDetail.as_view(), name='quiz'), + url(r'questionpapers/$', views.QuestionPaperList.as_view(), + name='questionpapers'), + url(r'questionpapers/(?P[0-9]+)/$', views.QuestionPaperDetail.as_view(), + name='questionpaper'), + url(r'answerpapers/$', views.AnswerPaperList.as_view(), name='answerpapers'), + url(r'validate/(?P[0-9]+)/(?P[0-9]+)/$', + views.AnswerValidator.as_view(), name='validators'), + url(r'validate/(?P[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""" -- cgit From bcff98d9fe7ac8fa50b1e8ab384a853ee6800c73 Mon Sep 17 00:00:00 2001 From: prathamesh Date: Wed, 19 Dec 2018 13:57:57 +0530 Subject: Add rest framework in requirements --- api/tests.py | 2 +- requirements/requirements-common.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/api/tests.py b/api/tests.py index 0930685..06f45d6 100644 --- a/api/tests.py +++ b/api/tests.py @@ -9,7 +9,7 @@ from yaksh.models import ( McqTestCase, Profile ) from api.serializers import ( - QuestionSerializer, QuizSerializer + QuestionSerializer, QuizSerializer, QuestionPaperSerializer, AnswerPaperSerializer ) from datetime import datetime diff --git a/requirements/requirements-common.txt b/requirements/requirements-common.txt index 484111e..7af41ee 100644 --- a/requirements/requirements-common.txt +++ b/requirements/requirements-common.txt @@ -1,6 +1,6 @@ -r requirements-codeserver.txt invoke==0.21.0 -django==1.10 +django==1.11.17 django-taggit==0.18.1 pytz==2016.4 python-social-auth==0.2.19 @@ -8,3 +8,4 @@ selenium==2.53.6 coverage ruamel.yaml==0.15.23 markdown==2.6.9 +djangorestframework==3.9.0 -- cgit From 81d1e4e4ff4406f7f3823c40a82cf21096f2ccec Mon Sep 17 00:00:00 2001 From: prathamesh Date: Thu, 20 Dec 2018 10:53:23 +0530 Subject: Resolve PEP8 issues --- api/serializers.py | 1 + api/tests.py | 244 ++++++++++++++++++++++++++++------------------------- api/urls.py | 7 +- api/views.py | 17 ++-- 4 files changed, 145 insertions(+), 124 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 919e429..d34f269 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -19,6 +19,7 @@ class QuestionPaperSerializer(serializers.ModelSerializer): model = QuestionPaper fields = '__all__' + class AnswerPaperSerializer(serializers.ModelSerializer): class Meta: model = AnswerPaper diff --git a/api/tests.py b/api/tests.py index 06f45d6..feffcc0 100644 --- a/api/tests.py +++ b/api/tests.py @@ -37,7 +37,6 @@ class QuestionListTestCase(TestCase): 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')) @@ -55,7 +54,7 @@ class QuestionListTestCase(TestCase): 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} @@ -65,7 +64,7 @@ class QuestionListTestCase(TestCase): # Then self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertFalse(Question.objects.filter( - summary='Add test question').exists()) + summary='Add test question').exists()) def test_create_question_valid_data(self): # Given @@ -96,13 +95,14 @@ class QuestionDetailTestCase(TestCase): self.user = User.objects.create_user(username=self.username, password=self.password) self.otheruser = User.objects.create_user(username=self.otherusername, - password=self.password) + 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) + Question.objects.create(summary='Created by other user', + language='python', type='mcq', + user=self.otheruser) def test_get_question_anonymous(self): # Given @@ -152,7 +152,7 @@ class QuestionDetailTestCase(TestCase): 'language': 'python', 'type': 'mcq', 'user': self.user.id} # When response = self.client.put(reverse('api:question', - kwargs={'pk': question.id}), data) + kwargs={'pk': question.id}), data) # Then self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) @@ -163,7 +163,7 @@ class QuestionDetailTestCase(TestCase): # When self.client.login(username=self.username, password=self.password) response = self.client.put(reverse('api:question', - kwargs={'pk': question.id}), data) + kwargs={'pk': question.id}), data) # Then self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -175,7 +175,7 @@ class QuestionDetailTestCase(TestCase): # When self.client.login(username=self.username, password=self.password) response = self.client.put(reverse('api:question', - kwargs={'pk': question.id}), data) + kwargs={'pk': question.id}), data) # Then self.assertEqual(response.status_code, status.HTTP_200_OK) question = Question.objects.get(pk=question.pk) @@ -186,28 +186,28 @@ class QuestionDetailTestCase(TestCase): question = Question.objects.get(summary='delete question') # When response = self.client.delete(reverse('api:question', - kwargs={'pk':question.id})) + 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})) + 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})) + kwargs={'pk': question.id})) # Then self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(Question.objects.filter(pk=question.id).exists()) @@ -229,14 +229,17 @@ class QuestionPaperListTestCase(TestCase): 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) + 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.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) @@ -251,7 +254,8 @@ class QuestionPaperListTestCase(TestCase): 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, + 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) @@ -268,11 +272,12 @@ class QuestionPaperListTestCase(TestCase): 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]) + quiz__in=[self.quiz1, self.quiz2] + ) serializer = QuestionPaperSerializer(questionpapers, many=True) # When self.client.login(username=self.username, password=self.password) @@ -295,8 +300,7 @@ class QuestionPaperListTestCase(TestCase): # Given data = {'quiz': self.quiz4.id, 'fixed_questions': [self.question1.id, self.question2.id], - 'random_questions': [self.questionset.id] - } + 'random_questions': [self.questionset.id]} # When self.client.login(username=self.username, password=self.password) response = self.client.post(reverse('api:questionpapers'), data) @@ -306,7 +310,7 @@ class QuestionPaperListTestCase(TestCase): def test_create_questionpaper_not_own_quiz(self): # Given - data = {'quiz': self.quiz5.id, 'fixed_questions':[self.question1.id], + 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) @@ -317,21 +321,19 @@ class QuestionPaperListTestCase(TestCase): 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] - } + '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)\ + 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] - } + '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) @@ -342,8 +344,7 @@ class QuestionPaperListTestCase(TestCase): # Given data = {'quiz': self.quiz1.id, 'fixed_questions': [self.question1.id], - 'random_questions': [self.questionset.id] - } + 'random_questions': [self.questionset.id]} # When self.client.login(username=self.username, password=self.password) response = self.client.post(reverse('api:questionpapers'), data) @@ -375,14 +376,12 @@ class QuestionPaperListTestCase(TestCase): # 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] - } + 'random_questions': [self.questionset.id]} # When self.client.login(username=self.username, password=self.password) response = self.client.put(reverse('api:questionpaper', @@ -392,17 +391,17 @@ class QuestionPaperListTestCase(TestCase): 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})) + kwargs={'pk': questionpaper.id})) # Then self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertFalse(QuestionPaper.objects.filter(quiz=self.quiz2).exists()) + questionpapers = QuestionPaper.objects.filter(quiz=self.quiz2) + self.assertFalse(questionpapers.exists()) def tearDown(self): self.client.logout() @@ -414,7 +413,7 @@ class QuestionPaperListTestCase(TestCase): class QuizListTestCase(TestCase): """ Test get all quizzes and create a new quiz """ - + def setUp(self): self.client = APIClient() self.username = 'demo' @@ -430,7 +429,6 @@ class QuizListTestCase(TestCase): # Then self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - def test_get_all_quizzes(self): # Given quizzes = Quiz.objects.filter(creator=self.user) @@ -566,7 +564,7 @@ class QuizDetailTestCase(TestCase): 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') @@ -577,14 +575,14 @@ class QuizDetailTestCase(TestCase): # 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})) + kwargs={'pk': quiz.id})) # Then self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(Quiz.objects.filter(pk=quiz.id).exists()) @@ -609,14 +607,16 @@ class AnswerPaperListTestCase(TestCase): 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.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, + 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) @@ -624,23 +624,25 @@ class AnswerPaperListTestCase(TestCase): 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.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) @@ -649,7 +651,6 @@ class AnswerPaperListTestCase(TestCase): self.course.students.add(self.student) self.course.save() - def test_get_all_answerpapers(self): # Given answerpapers = [self.answerpaper1] @@ -665,41 +666,43 @@ class AnswerPaperListTestCase(TestCase): def test_create_answerpaper_valid_data(self): # Given data = {'question_paper': self.questionpaper1.id, - 'attempt_number': 1, 'course': self.course.id - } + 'attempt_number': 1, 'course': self.course.id} # When - self.client.login(username=self.studentusername, password=self.password) + 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) + 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) + 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 - } + '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) + 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) @@ -713,7 +716,7 @@ class AnswerPaperListTestCase(TestCase): class AnswerValidatorTestCase(TestCase): - + @classmethod def setUpClass(self): self.client = APIClient() @@ -725,15 +728,20 @@ class AnswerValidatorTestCase(TestCase): 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') + points=1.0, language='python', + type='code') self.question2 = Question.objects.create(summary='Q2', user=self.user, - points=1.0, language='python', type='mcq') + points=1.0, language='python', + type='mcq') self.question3 = Question.objects.create(summary='Q3', user=self.user, - points=1.0, language='python', type='mcc') + points=1.0, language='python', + type='mcc') self.question4 = Question.objects.create(summary='Q4', user=self.user, - points=1.0, language='python', type='mcq') + points=1.0, language='python', + type='mcq') self.question5 = Question.objects.create(summary='Q5', user=self.user, - points=1.0, language='python', type='mcq') + points=1.0, language='python', + type='mcq') self.assertion_testcase = StandardTestCase( question=self.question1, test_case='assert add(1, 3) == 4', @@ -776,7 +784,7 @@ class AnswerValidatorTestCase(TestCase): # 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) @@ -811,50 +819,57 @@ class AnswerValidatorTestCase(TestCase): def test_correct_mcq(self): # Given - data = {'answer': str( self.mcq_based_testcase1.id)} + 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) + 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(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)} + 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) + 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']) - + self.assertFalse(response.data.get('success')) + def test_correct_mcc(self): # Given - data = {'answer': str( self.mcc_based_testcase.id)} + 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) + 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(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): @@ -868,21 +883,24 @@ class AnswerValidatorTestCase(TestCase): 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) + 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') + self.assertEqual(response.data.get('status'), 'running') uid = response.data['uid'] time.sleep(2) - response = self.client.get(reverse('api:validator', kwargs={'uid': uid})) + 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']) + 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['status'], 'running') - + self.assertEqual(response.data.get('status'), 'running') diff --git a/api/urls.py b/api/urls.py index be276da..5dfd8c7 100644 --- a/api/urls.py +++ b/api/urls.py @@ -11,9 +11,10 @@ urlpatterns = [ url(r'quizzes/(?P[0-9]+)/$', views.QuizDetail.as_view(), name='quiz'), url(r'questionpapers/$', views.QuestionPaperList.as_view(), name='questionpapers'), - url(r'questionpapers/(?P[0-9]+)/$', views.QuestionPaperDetail.as_view(), - name='questionpaper'), - url(r'answerpapers/$', views.AnswerPaperList.as_view(), name='answerpapers'), + url(r'questionpapers/(?P[0-9]+)/$', + views.QuestionPaperDetail.as_view(), name='questionpaper'), + url(r'answerpapers/$', views.AnswerPaperList.as_view(), + name='answerpapers'), url(r'validate/(?P[0-9]+)/(?P[0-9]+)/$', views.AnswerValidator.as_view(), name='validators'), url(r'validate/(?P[0-9]+)/$', diff --git a/api/views.py b/api/views.py index 14e04f0..ff21828 100644 --- a/api/views.py +++ b/api/views.py @@ -17,13 +17,12 @@ 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(): @@ -85,7 +84,8 @@ class AnswerPaperList(APIView): 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 + return user in course.students.all() or user in course.teachers.all() \ + or user == course.creator def post(self, request, format=None): try: @@ -100,13 +100,14 @@ class AnswerPaperList(APIView): 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) + 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) @@ -144,7 +145,8 @@ class AnswerValidator(APIView): 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) + result = answerpaper.validate_answer(user_answer, question, json_data, + answer.id) # updaTE RESult if question.type not in ['code', 'upload']: @@ -273,7 +275,7 @@ class QuestionPaperList(APIView): 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) @@ -327,4 +329,3 @@ class QuestionPaperDetail(APIView): questionpaper = self.get_questionpaper(pk, request.user) questionpaper.delete() return Response(status=status.HTTP_204_NO_CONTENT) - -- cgit From a7b4b9e0c6699987d1dee4007e9c62da15559432 Mon Sep 17 00:00:00 2001 From: prathamesh Date: Fri, 21 Jun 2019 03:23:45 +0530 Subject: Add API to get complete course details and AnswerPaper for the quiz Courses are retrieved in which the user is a student. Complete course info is made available. Answerpaper is created if does not exists on start quiz. AnswerPaper detail info is made available. If attempts are not allowed then the message is retrieved. --- api/serializers.py | 42 +++++++++++++++++++++++++++++++++++++++++- api/urls.py | 3 +++ api/views.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index d34f269..df66730 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,5 +1,8 @@ from rest_framework import serializers -from yaksh.models import Question, Quiz, QuestionPaper, AnswerPaper +from yaksh.models import ( + Question, Quiz, QuestionPaper, AnswerPaper, Course, + LearningModule, LearningUnit, Lesson +) class QuestionSerializer(serializers.ModelSerializer): @@ -21,6 +24,43 @@ class QuestionPaperSerializer(serializers.ModelSerializer): 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 + fields = '__all__' diff --git a/api/urls.py b/api/urls.py index 5dfd8c7..22a9b54 100644 --- a/api/urls.py +++ b/api/urls.py @@ -7,6 +7,9 @@ urlpatterns = [ url(r'questions/$', views.QuestionList.as_view(), name='questions'), url(r'questions/(?P[0-9]+)/$', views.QuestionDetail.as_view(), name='question'), + url(r'get_courses/$', views.CourseList.as_view(), name='get_courses'), + url(r'start_quiz/(?P[0-9]+)/(?P[0-9]+)/$', views.StartQuiz.as_view(), + name='start_quiz'), url(r'quizzes/$', views.QuizList.as_view(), name='quizzes'), url(r'quizzes/(?P[0-9]+)/$', views.QuizDetail.as_view(), name='quiz'), url(r'questionpapers/$', views.QuestionPaperList.as_view(), diff --git a/api/views.py b/api/views.py index ff21828..b81e895 100644 --- a/api/views.py +++ b/api/views.py @@ -3,7 +3,7 @@ from yaksh.models import ( ) from api.serializers import ( QuestionSerializer, QuizSerializer, QuestionPaperSerializer, - AnswerPaperSerializer + AnswerPaperSerializer, CourseSerializer ) from rest_framework.views import APIView from rest_framework.response import Response @@ -31,6 +31,49 @@ class QuestionList(APIView): 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): + 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) + return Response(serializer.data) + + 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) + return Response(serializer.data, status=status.HTTP_201_CREATED) + + class QuestionDetail(APIView): """ Retrieve, update or delete a question """ -- cgit From 96539298001042e3a215278b918ea1dffef1f403 Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 30 Mar 2020 18:56:50 +0530 Subject: Fix ImproperlyConfigured URL issue --- api/urls.py | 1 + grades/urls.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/api/urls.py b/api/urls.py index 22a9b54..e81a275 100644 --- a/api/urls.py +++ b/api/urls.py @@ -2,6 +2,7 @@ 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'), 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"), -- cgit From 4a582c494f7397eec4e5d5a9fad4eb2e49b77d9c Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 30 Mar 2020 19:00:46 +0530 Subject: Remove unnecessary requirements, update DRF to 3.11.0 - Update DRF to 3.11.0 compatible with Django 3. - Add mysqlclient to requirements. --- requirements/requirements-common.txt | 4 +++- requirements/requirements-py2.txt | 2 -- requirements/requirements-py3.txt | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 requirements/requirements-py2.txt delete mode 100644 requirements/requirements-py3.txt diff --git a/requirements/requirements-common.txt b/requirements/requirements-common.txt index bd38920..bab7fe7 100644 --- a/requirements/requirements-common.txt +++ b/requirements/requirements-common.txt @@ -10,4 +10,6 @@ coverage ruamel.yaml==0.15.23 markdown==2.6.9 pygments==2.2.0 -djangorestframework==3.9.0 +djangorestframework==3.11.0 +django-cors-headers==3.1.0 +mysqlclient 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/requirements/requirements-py3.txt b/requirements/requirements-py3.txt deleted file mode 100644 index 3d13335..0000000 --- a/requirements/requirements-py3.txt +++ /dev/null @@ -1,2 +0,0 @@ --r requirements-common.txt -mysqlclient==1.3.9 -- cgit From c3a18de3b8f120b16dea005d2b0ce375804f24ec Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 30 Mar 2020 20:23:40 +0530 Subject: Add corsheaders to INSTALLED_APPS --- online_test/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/online_test/settings.py b/online_test/settings.py index fa3712e..99c6d96 100644 --- a/online_test/settings.py +++ b/online_test/settings.py @@ -47,10 +47,13 @@ INSTALLED_APPS = ( 'grades', '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', -- cgit From 7d75a279112bce3969d2d4a2b70d74dd67570e29 Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 30 Mar 2020 20:39:55 +0530 Subject: Show question test_cases, exclude unncessary fields from API --- api/serializers.py | 19 ++++++++++++++++--- yaksh/models.py | 13 +++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index df66730..1c1e6a4 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -6,15 +6,21 @@ from yaksh.models import ( 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 - fields = '__all__' + exclude = ('partial_grading', ) class QuizSerializer(serializers.ModelSerializer): class Meta: model = Quiz - fields = '__all__' + exclude = ('view_answerpaper', ) class QuestionPaperSerializer(serializers.ModelSerializer): @@ -63,4 +69,11 @@ class CourseSerializer(serializers.ModelSerializer): class Meta: model = Course - fields = '__all__' + exclude = ( + 'teachers', + 'rejected', + 'requests', + 'students', + 'grading_system', + 'view_grade', + ) diff --git a/yaksh/models.py b/yaksh/models.py index 949b87e..813453d 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1426,6 +1426,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 -- cgit From ec8e7485a8306dd9851d134578d3721abc7b9e8a Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 30 Mar 2020 22:11:05 +0530 Subject: Add Login endpoint, quit quiz endpoint --- api/tests.py | 2 +- api/urls.py | 5 ++++ api/views.py | 63 +++++++++++++++++++++++++++++++++++++++++++------ online_test/settings.py | 4 ++++ 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/api/tests.py b/api/tests.py index feffcc0..4ef6fa4 100644 --- a/api/tests.py +++ b/api/tests.py @@ -1,5 +1,5 @@ from django.test import TestCase -from django.core.urlresolvers import reverse +from django.urls import reverse from django.contrib.auth.models import User from rest_framework.test import APIClient from rest_framework import status diff --git a/api/urls.py b/api/urls.py index e81a275..3edf945 100644 --- a/api/urls.py +++ b/api/urls.py @@ -23,6 +23,11 @@ urlpatterns = [ views.AnswerValidator.as_view(), name='validators'), url(r'validate/(?P[0-9]+)/$', views.AnswerValidator.as_view(), name='validator'), + url(r'course/(?P[0-9]+)/$', + views.ICourse.as_view(), name='get_course'), + url(r'quit/(?P\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 index b81e895..5bbc077 100644 --- a/api/views.py +++ b/api/views.py @@ -9,7 +9,12 @@ 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 @@ -50,6 +55,7 @@ class StartQuiz(APIView): 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() @@ -58,7 +64,9 @@ class StartQuiz(APIView): questionpaper, user, course_id) if last_attempt and last_attempt.is_attempt_inprogress(): serializer = AnswerPaperSerializer(last_attempt) - return Response(serializer.data) + 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: @@ -71,7 +79,9 @@ class StartQuiz(APIView): answerpaper = questionpaper.make_answerpaper(user, ip, attempt_number, course_id) serializer = AnswerPaperSerializer(answerpaper) - return Response(serializer.data, status=status.HTTP_201_CREATED) + context["time_left"] = answerpaper.time_left() + context["answerpaper"] = serializer.data + return Response(context, status=status.HTTP_201_CREATED) class QuestionDetail(APIView): @@ -175,7 +185,7 @@ class AnswerValidator(APIView): def post(self, request, answerpaper_id, question_id, format=None): try: - user_answer = request.data['answer'] + user_answer = str(request.data['answer']) except KeyError: return Response(status=status.HTTP_400_BAD_REQUEST) user = request.user @@ -196,8 +206,8 @@ class AnswerValidator(APIView): if result.get('success'): answer.correct = True answer.marks = question.points - answer.error = json.dumps(result.get('error')) - answer.save() + answer.error = json.dumps(result.get('error')) + answer.save() answerpaper.update_marks(state='inprogress') return Response(result) @@ -208,11 +218,11 @@ class AnswerValidator(APIView): # update result if result['status'] == 'done': final_result = json.loads(result.get('result')) - answer.error = json.dumps(result.get('error')) + answer.error = json.dumps(final_result.get('error')) if final_result.get('success'): answer.correct = True answer.marks = answer.question.points - answer.save() + answer.save() answerpaper = answer.answerpaper_set.get() answerpaper.update_marks(state='inprogress') return Response(result) @@ -372,3 +382,42 @@ class QuestionPaperDetail(APIView): questionpaper = self.get_questionpaper(pk, request.user) questionpaper.delete() return Response(status=status.HTTP_204_NO_CONTENT) + + +class ICourse(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/online_test/settings.py b/online_test/settings.py index 99c6d96..284ec64 100644 --- a/online_test/settings.py +++ b/online_test/settings.py @@ -218,6 +218,7 @@ 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', ], @@ -226,3 +227,6 @@ REST_FRAMEWORK = { ], 'TEST_REQUEST_DEFAULT_FORMAT': 'json' } + +CORS_ORIGIN_ALLOW_ALL = True +CORS_ALLOW_CREDENTIALS = True \ No newline at end of file -- cgit From b42100d3a631adb79f641b70a1e2acec82e1d735 Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 30 Mar 2020 23:32:23 +0530 Subject: Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0dd53fb..1e735b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,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 --settings online_test.test_settings api + - coverage run -p manage.py test -v 2 api after_success: - coverage combine -- cgit From edf774b171444e2a671a838bf030931a1800ae12 Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Wed, 1 Apr 2020 15:59:17 +0530 Subject: Update ruamel version, change view name - Update ruamel version to 0.16.10. - Change view name from ICourse to GetCourse. --- api/urls.py | 2 +- api/views.py | 2 +- requirements/requirements-common.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/urls.py b/api/urls.py index 3edf945..f519aea 100644 --- a/api/urls.py +++ b/api/urls.py @@ -24,7 +24,7 @@ urlpatterns = [ url(r'validate/(?P[0-9]+)/$', views.AnswerValidator.as_view(), name='validator'), url(r'course/(?P[0-9]+)/$', - views.ICourse.as_view(), name='get_course'), + views.GetCourse.as_view(), name='get_course'), url(r'quit/(?P\d+)/$', views.QuitQuiz.as_view(), name="quit_quiz"), url(r'login/$', views.login, name='login') diff --git a/api/views.py b/api/views.py index 5bbc077..550ee6d 100644 --- a/api/views.py +++ b/api/views.py @@ -384,7 +384,7 @@ class QuestionPaperDetail(APIView): return Response(status=status.HTTP_204_NO_CONTENT) -class ICourse(APIView): +class GetCourse(APIView): def get(self, request, pk, format=None): course = Course.objects.get(id=pk) serializer = CourseSerializer(course) diff --git a/requirements/requirements-common.txt b/requirements/requirements-common.txt index bab7fe7..adb9fb1 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 djangorestframework==3.11.0 -- cgit From 1d3d121831100d2b33d63899eeaa408cd16fb060 Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Fri, 3 Apr 2020 17:59:20 +0530 Subject: Get user_answer based on question type --- api/views.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/api/views.py b/api/views.py index 550ee6d..cd2965c 100644 --- a/api/views.py +++ b/api/views.py @@ -184,13 +184,23 @@ class AnswerValidator(APIView): raise Http404 def post(self, request, answerpaper_id, question_id, format=None): - try: - user_answer = str(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) + 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'] + print(user_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) -- cgit From bb266a85116a1d3e92fe181305d49e861c3e4ea8 Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 6 Apr 2020 17:55:21 +0530 Subject: Remove print statement --- api/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/views.py b/api/views.py index cd2965c..8d2da83 100644 --- a/api/views.py +++ b/api/views.py @@ -196,7 +196,6 @@ class AnswerValidator(APIView): user_answer = float(request.data['answer'][0]) elif question.type == 'string': user_answer = request.data['answer'] - print(user_answer) else: user_answer = request.data['answer'] except KeyError: -- cgit From 6fc01303dba0b821aa2344b761e98dc36b33807b Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 6 Apr 2020 20:32:06 +0530 Subject: Add mysqlclient to requirements-production.txt --- requirements/requirements-common.txt | 1 - requirements/requirements-production.txt | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 requirements/requirements-production.txt diff --git a/requirements/requirements-common.txt b/requirements/requirements-common.txt index adb9fb1..ff5d2f8 100644 --- a/requirements/requirements-common.txt +++ b/requirements/requirements-common.txt @@ -12,4 +12,3 @@ markdown==2.6.9 pygments==2.2.0 djangorestframework==3.11.0 django-cors-headers==3.1.0 -mysqlclient diff --git a/requirements/requirements-production.txt b/requirements/requirements-production.txt new file mode 100644 index 0000000..3d13335 --- /dev/null +++ b/requirements/requirements-production.txt @@ -0,0 +1,2 @@ +-r requirements-common.txt +mysqlclient==1.3.9 -- cgit From d94f28984782e2fd14f7730dff29a078cf6976c0 Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 21 Apr 2020 17:31:26 +0530 Subject: Replace students list of names with a drop down --- yaksh/static/yaksh/css/custom.css | 7 +++++++ yaksh/templates/yaksh/grade_user.html | 24 +++++++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/yaksh/static/yaksh/css/custom.css b/yaksh/static/yaksh/css/custom.css index 63ee455..c3050d5 100644 --- a/yaksh/static/yaksh/css/custom.css +++ b/yaksh/static/yaksh/css/custom.css @@ -97,3 +97,10 @@ body, .dropdown-menu { min-height: 100vh; transition: all 0.3s; } + +@media (min-width: 1024px) { + + .students-list { + position: fixed !important; + } +} diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html index 7206525..14666e1 100644 --- a/yaksh/templates/yaksh/grade_user.html +++ b/yaksh/templates/yaksh/grade_user.html @@ -87,16 +87,17 @@ $(document).ready(function()
{% if status == "grade" %} {% if users %} -
- +
+
+ +
{% else %}
@@ -110,7 +111,8 @@ $(document).ready(function() {% endif %} {% endif %} -
+
+
{% if has_quiz_assignments %}  Download All Assignments -- cgit From d5cdfe91335af6c79231d41453991af29a39e96e Mon Sep 17 00:00:00 2001 From: adityacp Date: Sat, 25 Apr 2020 10:57:51 +0530 Subject: Add a search bar to search users --- yaksh/templates/yaksh/grade_user.html | 36 ++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html index 14666e1..f4c7d67 100644 --- a/yaksh/templates/yaksh/grade_user.html +++ b/yaksh/templates/yaksh/grade_user.html @@ -15,7 +15,24 @@ $(document).ready(function() { $("#marks_table").tablesorter({sortList: [[2,1]]}); }); +function searchNames() { + var input, filter, ul, li, a, i, txtValue; + input = document.getElementById('myInput'); + filter = input.value.toUpperCase(); + $("#myUL").toggle(); + ul = document.getElementById("myUL"); + li = ul.getElementsByTagName('li'); + for (i = 0; i < li.length; i++) { + a = li[i].getElementsByTagName("a")[0]; + txtValue = a.textContent || a.innerText; + if (txtValue.toUpperCase().indexOf(filter) > -1) { + li[i].style.display = ""; + } else { + li[i].style.display = "none"; + } + } +} {% endblock script %} @@ -92,8 +109,8 @@ $(document).ready(function() @@ -112,7 +129,6 @@ $(document).ready(function() {% endif %} {% endfor %} {% endif %} + {% if status == "grade" and users %} + + + {% endif %} {% if data %} +
Student Details -- cgit