diff options
author | prathamesh | 2016-09-23 13:15:06 +0530 |
---|---|---|
committer | prathamesh | 2016-09-23 13:15:06 +0530 |
commit | d4a0ace8042f590659b8e7ec02fcdc3a24fe0592 (patch) | |
tree | fe804651f57ba0e364a11d1719edcf3d260f5ebe | |
parent | 03e54d7f6a42c0d9207db554abba9da854d494e4 (diff) | |
parent | 8e4ead8ab4caa9366e5357840e1912cbfb8cdb85 (diff) | |
download | online_test-d4a0ace8042f590659b8e7ec02fcdc3a24fe0592.tar.gz online_test-d4a0ace8042f590659b8e7ec02fcdc3a24fe0592.tar.bz2 online_test-d4a0ace8042f590659b8e7ec02fcdc3a24fe0592.zip |
Merge branch 'master' of https://github.com/FOSSEE/online_test into regrade_papers
Resolved
Conflicts:
yaksh/models.py
yaksh/test_models.py
yaksh/urls.py
yaksh/views.py
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .travis.yml | 5 | ||||
-rwxr-xr-x | yaksh/bash_files/sample2.args | 1 | ||||
-rwxr-xr-x | yaksh/bash_files/sample2.sh | 2 | ||||
-rw-r--r-- | yaksh/file_utils.py | 22 | ||||
-rw-r--r-- | yaksh/fixtures/demo_questions.zip | bin | 0 -> 1510 bytes | |||
-rw-r--r-- | yaksh/live_server_tests/__init__.py | 0 | ||||
-rw-r--r-- | yaksh/live_server_tests/load_test.py | 77 | ||||
-rw-r--r-- | yaksh/live_server_tests/selenium_test.py | 145 | ||||
-rw-r--r-- | yaksh/models.py | 110 | ||||
-rw-r--r-- | yaksh/templates/manage.html | 20 | ||||
-rw-r--r-- | yaksh/templates/yaksh/courses.html | 10 | ||||
-rw-r--r-- | yaksh/templates/yaksh/showquestions.html | 2 | ||||
-rw-r--r-- | yaksh/test_models.py | 74 | ||||
-rw-r--r-- | yaksh/test_views.py | 1 | ||||
-rw-r--r-- | yaksh/tests/test_questions.json | 50 | ||||
-rw-r--r-- | yaksh/urls.py | 1 | ||||
-rw-r--r-- | yaksh/views.py | 45 |
18 files changed, 518 insertions, 48 deletions
@@ -41,3 +41,4 @@ apache/* migrations wsgi.log *.sqlite3 +data/
\ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 8ad6c5f..3484d2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,11 +14,16 @@ install: - pip install -q Django==$DJANGO - pip install -q pytz==2016.4 - pip install -q python-social-auth==0.2.19 + - pip install selenium before_install: - sudo apt-get update -qq - sudo apt-get install -y scilab + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - sleep 3 # give xvfb some time to start # command to run tests script: - python manage.py test -v 2 yaksh + - python manage.py test -v 2 yaksh.live_server_tests diff --git a/yaksh/bash_files/sample2.args b/yaksh/bash_files/sample2.args new file mode 100755 index 0000000..cf4499d --- /dev/null +++ b/yaksh/bash_files/sample2.args @@ -0,0 +1 @@ +file1.csv file2.csv file3.csv diff --git a/yaksh/bash_files/sample2.sh b/yaksh/bash_files/sample2.sh new file mode 100755 index 0000000..5dc55b8 --- /dev/null +++ b/yaksh/bash_files/sample2.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cat $1 | cut -d: -f2 | paste -d: $3 - $2 diff --git a/yaksh/file_utils.py b/yaksh/file_utils.py index 8f6f6e5..afcf9e8 100644 --- a/yaksh/file_utils.py +++ b/yaksh/file_utils.py @@ -13,12 +13,10 @@ def copy_files(file_paths): file_name = os.path.basename(file_path) files.append(file_name) shutil.copy(file_path, os.getcwd()) - if extract and zipfile.is_zipfile(file_name): - unzip = zipfile.ZipFile(file_name) - for zip_files in unzip.namelist(): - files.append(zip_files) - unzip.extractall() - unzip.close() + if extract: + z_files = extract_files(file_name) + for file in z_files: + files.append(file) return files @@ -31,3 +29,15 @@ def delete_files(files): os.remove(file) else: shutil.rmtree(file) + + +def extract_files(zip_file): + zfiles = [] + if zipfile.is_zipfile(zip_file): + zip_file = zipfile.ZipFile(zip_file, 'r') + for z_file in zip_file.namelist(): + zfiles.append(z_file) + zip_file.extractall() + zip_file.close() + return zfiles + diff --git a/yaksh/fixtures/demo_questions.zip b/yaksh/fixtures/demo_questions.zip Binary files differnew file mode 100644 index 0000000..562bf8a --- /dev/null +++ b/yaksh/fixtures/demo_questions.zip diff --git a/yaksh/live_server_tests/__init__.py b/yaksh/live_server_tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/yaksh/live_server_tests/__init__.py diff --git a/yaksh/live_server_tests/load_test.py b/yaksh/live_server_tests/load_test.py new file mode 100644 index 0000000..17934d4 --- /dev/null +++ b/yaksh/live_server_tests/load_test.py @@ -0,0 +1,77 @@ +import os +import signal +import subprocess +from datetime import datetime +import pytz +from threading import Thread +from selenium.webdriver.firefox.webdriver import WebDriver + +from django.contrib.staticfiles.testing import StaticLiveServerTestCase + +from yaksh.models import User, Profile, Question, Quiz, Course, QuestionPaper, TestCase +from selenium_test import SeleniumTest + +from yaksh.code_server import ServerPool +from yaksh import settings + + +class YakshSeleniumTests(StaticLiveServerTestCase): + @classmethod + def setUpClass(cls): + super(YakshSeleniumTests, cls).setUpClass() + # setup a demo code server + settings.code_evaluators['python']['standardtestcase'] = \ + "yaksh.python_assertion_evaluator.PythonAssertionEvaluator" + settings.code_evaluators['c']['standardtestcase'] = \ + "yaksh.cpp_code_evaluator.CppCodeEvaluator" + settings.code_evaluators['bash']['standardtestcase'] = \ + "yaksh.bash_code_evaluator.BashCodeEvaluator" + settings.SERVER_POOL_PORT = 53578 + code_server_pool = ServerPool(ports=settings.SERVER_PORTS, pool_port=settings.SERVER_POOL_PORT) + cls.code_server_pool = code_server_pool + cls.code_server_thread = t = Thread(target=code_server_pool.run) + t.start() + + demo_student = User.objects.create_user(username='demo_student', + password='demo_student', + email='demo_student@test.com' + ) + demo_student_profile = Profile.objects.create(user=demo_student, + roll_number=3, institute='IIT', + department='Chemical', position='Student' + ) + + demo_mod = User.objects.create_user(username='demo_mod', + password='demo_mod', + email='demo_mod@test.com' + ) + demo_mod_profile = Profile.objects.create(user=demo_mod, + roll_number=0, institute='IIT', + department='Chemical', position='Moderator' + ) + + course_obj = Course() + course_obj.create_demo(demo_mod) + demo_course = Course.objects.get(id=1) + + demo_course.students.add(demo_student) + + @classmethod + def tearDownClass(cls): + User.objects.all().delete() + Question.objects.all().delete() + Quiz.objects.all().delete() + Course.objects.all().delete() + + settings.SERVER_POOL_PORT = 53579 + + cls.code_server_pool.stop() + cls.code_server_thread.join() + + super(YakshSeleniumTests, cls).tearDownClass() + + def test_load(self): + url = '%s%s' % (self.live_server_url, '/exam/login/') + quiz_name = "Yaksh Demo quiz" + selenium_test = SeleniumTest(url=url, quiz_name=quiz_name) + selenium_test.run_load_test(url=url, username='demo_student', password='demo_student') diff --git a/yaksh/live_server_tests/selenium_test.py b/yaksh/live_server_tests/selenium_test.py new file mode 100644 index 0000000..01ccc96 --- /dev/null +++ b/yaksh/live_server_tests/selenium_test.py @@ -0,0 +1,145 @@ +from selenium import webdriver +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import WebDriverException + +import multiprocessing +import argparse + +class SeleniumTest(): + def __init__(self, url, quiz_name): + self.driver = webdriver.Firefox() + self.quiz_name = quiz_name + self.url = url + + def run_load_test(self, url, username, password): + try: + self.driver.delete_all_cookies() + self.driver.get(self.url) + self.login(username, password) + self.open_quiz() + self.close_quiz() + self.logout() + self.driver.close() + except Exception as e: + with open("/tmp/yaksh_load_test_log.txt", "ab") as f: + f.write('Username: {0}\nError: {1}\n'.format(username, e)) + self.driver.close() + + def login(self, username, password): + # get the username, password and submit form elements + username_elem = self.driver.find_element_by_id("id_username") + password_elem = self.driver.find_element_by_id("id_password") + submit_login_elem = self.driver.find_element_by_css_selector('button.btn') + + # Type in the username, password and submit form + username_elem.send_keys(username) + password_elem.send_keys(password) + submit_login_elem.click() + + def submit_answer(self, question_label, answer, loop_count=1): + self.driver.implicitly_wait(2) + for count in range(loop_count): + self.driver.find_element_by_link_text(question_label).click() + submit_answer_elem = self.driver.find_element_by_id("check") + self.driver.execute_script('editor.setValue({})'.format(answer)) + submit_answer_elem.click() + + def test_c_question(self, question_label): + # Incorrect Answer + loop_count = 10 + answer = '\"int add(int a, int b, int c)\\n{return;}\"' + self.submit_answer(question_label, answer, loop_count) + + # Infinite Loop + loop_count = 3 + answer = '\"int add(int a, int b, int c)\\n{while(1){}}\"' + self.submit_answer(question_label, answer, loop_count) + + # Correct Answer + loop_count = 1 + answer = '\"int add(int a, int b, int c)\\n{return a + b + c;}\"' + self.submit_answer(question_label, answer, loop_count) + + def test_python_question(self, question_label): + # Incorrect Answer + loop_count = 10 + answer = '\"def is_palindrome(s):\\n return s\"' + self.submit_answer(question_label, answer, loop_count) + + # Infinite Loop + loop_count = 3 + answer = '\"while True:\\n pass"' + self.submit_answer(question_label, answer, loop_count) + + # Correct Answer + loop_count = 1 + answer = '\"def is_palindrome(s):\\n return s[::-1] == s\"' + self.submit_answer(question_label, answer, loop_count) + + def test_bash_question(self, question_label): + # Incorrect Answer + loop_count = 10 + answer = '\"#!/bin/bash\\nls\"' + self.submit_answer(question_label, answer, loop_count) + + # Infinite Loop + loop_count = 3 + answer = '\"#!/bin/bash\\nwhile [ 1 ]; do : ; done\"' + self.submit_answer(question_label, answer, loop_count) + + # Correct Answer + loop_count = 1 + answer = '\"#!/bin/bash\\ncat $1 | cut -d: -f2 | paste -d: $3 - $2\"' + self.submit_answer(question_label, answer, loop_count) + + def open_quiz(self): + # open quiz link + quiz_link_elem = self.driver.find_element_by_link_text(self.quiz_name).click() + + # Get page elements + start_exam_elem = WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located((By.NAME, "start")) + ) + start_exam_elem.click() + + self.test_c_question(question_label=1) + self.test_python_question(question_label=3) + self.test_bash_question(question_label=2) + + def close_quiz(self): + quit_link_elem = WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located((By.ID, "login_again")) + ) + quit_link_elem.click() + + def logout(self): + logout_link_elem = WebDriverWait(self.driver, 5).until( + EC.presence_of_element_located((By.ID, "logout")) + ) + logout_link_elem.click() + +def user_gen(url, ids): + return [(url, 'User%d'%x, 'User%d'%x) for x in ids] + +def wrap_run_load_test(args): + url = "http://yaksh.fossee.aero.iitb.ac.in/exam/" + selenium_test = SeleniumTest(url=url, quiz_name=quiz_name) + return selenium_test.run_load_test(*args) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('url', type=str, help="url of the website being tested") + parser.add_argument('start', type=int, help="Starting user id") + parser.add_argument("-n", "--number", type=int, default=10, help="number of users") + opts = parser.parse_args() + + quiz_name = "Demo quiz" + selenium_test = SeleniumTest(url=opts.url, quiz_name=quiz_name) + pool = multiprocessing.Pool(opts.number) + pool.map(wrap_run_load_test, user_gen(opts.url, range(opts.start, opts.start + opts.number))) + pool.close() + pool.join() + diff --git a/yaksh/models.py b/yaksh/models.py index d14943a..7c4d5c4 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -10,17 +10,23 @@ from django.forms.models import model_to_dict from django.contrib.contenttypes.models import ContentType from taggit.managers import TaggableManager from django.utils import timezone +from django.core.files import File +from StringIO import StringIO import pytz import os import stat from os.path import join, abspath, dirname, exists import shutil +import zipfile +import tempfile +from file_utils import extract_files from yaksh.xmlrpc_clients import code_server # The directory where user data can be saved. OUTPUT_DIR = abspath(join(dirname(__file__), 'output')) + languages = ( ("python", "Python"), ("bash", "Bash"), @@ -157,6 +163,23 @@ class Course(models.Model): def remove_teachers(self, *teachers): self.teachers.remove(*teachers) + def create_demo(self, user): + course = Course.objects.filter(creator=user, name="Yaksh Demo course") + if not course: + course = Course.objects.create(name="Yaksh Demo course", + enrollment="open", + creator=user) + quiz = Quiz() + demo_quiz = quiz.create_demo_quiz(course) + demo_ques = Question() + demo_ques.create_demo_questions(user) + demo_que_ppr = QuestionPaper() + demo_que_ppr.create_demo_quiz_ppr(demo_quiz) + success = True + else: + success = False + return success + def __unicode__(self): return self.name @@ -247,30 +270,35 @@ class Question(models.Model): return json.dumps(question_data) - def dump_into_json(self, question_ids, user): + def dump_questions(self, question_ids, user): questions = Question.objects.filter(id__in=question_ids, user_id=user.id) questions_dict = [] + zip_file_name = StringIO() + zip_file = zipfile.ZipFile(zip_file_name, "a") for question in questions: test_case = question.get_test_cases() + file_names = question._add_and_get_files(zip_file) q_dict = {'summary': question.summary, 'description': question.description, - 'points': question.points, - 'language': question.language, - 'type': question.type, - 'active': question.active, + 'points': question.points, 'language': question.language, + 'type': question.type, 'active': question.active, 'test_case_type': question.test_case_type, 'snippet': question.snippet, - 'testcase': [case.get_field_value() for case in test_case]} + 'testcase': [case.get_field_value() for case in test_case], + 'files': file_names} questions_dict.append(q_dict) + question._add_json_to_zip(zip_file, questions_dict) + return zip_file_name - return json.dumps(questions_dict, indent=2) - - def load_from_json(self, questions_list, user): + def load_questions(self, questions_list, user): questions = json.loads(questions_list) for question in questions: question['user'] = user + file_names = question.pop('files') test_cases = question.pop('testcase') que, result = Question.objects.get_or_create(**question) + if file_names: + que._add_files_to_db(file_names) model_class = get_model_class(que.test_case_type) for test_case in test_cases: model_class.objects.get_or_create(question=que, **test_case) @@ -297,6 +325,48 @@ class Question(models.Model): return test_case + def _add_and_get_files(self, zip_file): + files = FileUpload.objects.filter(question=self) + files_list = [] + for f in files: + zip_file.write(f.file.path, (os.path.basename(f.file.path))) + files_list.append(((os.path.basename(f.file.path)), f.extract)) + return files_list + + def _add_files_to_db(self, file_names): + for file_name, extract in file_names: + que_file = open(file_name, 'r') + #Converting to Python file object with some Django-specific additions + django_file = File(que_file) + FileUpload.objects.get_or_create(file=django_file, + question=self, + extract=extract) + os.remove(file_name) + + def _add_json_to_zip(self, zip_file, q_dict): + json_data = json.dumps(q_dict, indent=2) + tmp_file_path = tempfile.mkdtemp() + json_path = os.path.join(tmp_file_path, "questions_dump.json") + with open(json_path, "w") as json_file: + json_file.write(json_data) + zip_file.write(json_path, os.path.basename(json_path)) + zip_file.close() + shutil.rmtree(tmp_file_path) + + def read_json(self, json_file, user): + if os.path.exists(json_file): + with open(json_file, 'r') as q_file: + questions_list = q_file.read() + self.load_questions(questions_list, user) + os.remove(json_file) + + def create_demo_questions(self, user): + zip_file_path = os.path.join(os.getcwd(), 'yaksh', + 'fixtures', 'demo_questions.zip') + extract_files(zip_file_path) + self.read_json("questions_dump.json", user) + + def __unicode__(self): return self.summary @@ -461,6 +531,17 @@ class Quiz(models.Model): def has_prerequisite(self): return True if self.prerequisite else False + + def create_demo_quiz(self, course): + demo_quiz = Quiz.objects.create(start_date_time=timezone.now(), + end_date_time=timezone.now() + timedelta(176590), + duration=30, active=True, + attempts_allowed=-1, + time_between_attempts=0, + description='Yaksh Demo quiz', pass_criteria=0, + language='Python', prerequisite=None, + course=course) + return demo_quiz def __unicode__(self): desc = self.description or 'Quiz' @@ -592,6 +673,17 @@ class QuestionPaper(models.Model): if self.quiz.has_prerequisite(): prerequisite = self._get_prequisite_paper() return prerequisite._is_questionpaper_passed(user) + + def create_demo_quiz_ppr(self, demo_quiz): + question_paper = QuestionPaper.objects.create(quiz=demo_quiz, + total_marks=5.0, + shuffle_questions=True + ) + questions = Question.objects.filter(active=True, + summary="Yaksh Demo Question") + # add fixed set of questions to the question paper + for question in questions: + question_paper.fixed_questions.add(question) def __unicode__(self): return "Question Paper for " + self.quiz.description diff --git a/yaksh/templates/manage.html b/yaksh/templates/manage.html index 9e004e6..63c0ea7 100644 --- a/yaksh/templates/manage.html +++ b/yaksh/templates/manage.html @@ -78,6 +78,26 @@ <h4>Moderator's Dashboard!</h4> <h5>Click on the button given below to add a new course.</h5> <button class="btn" type="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/add_course");'>Add New Course</button> + <h5>Click on the button to Create a Demo course. + <a href="" onclick="$('#help').show(); return false;">Help </a></h5> + <button class="btn" type="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/create_demo_course");'>Create Demo Course</button> + <div style="display: none;" id="help"> + <ol> + <ul> + <li>A Demo Course and Demo Quiz will be created (Click Courses link on nav bar to view courses).</li> + <li>Some Demo Questions are also created for you (Click Questions link on nav bar to view questions).</li> + <li>In Courses you can view Demo Quiz.</li> + <li>Click on the Demo Quiz and Click on User Mode or God Mode to take the quiz. + </li> + <li>You can also edit the Demo quiz. + </li> + </ul> + </p> + <a href="" onclick="$('#help').hide(); return false"> Close </a> + </div> + {% if msg %} + <h4>{{ msg }}</h4> + {% endif %} </center> {% if trial_paper %} <h5/> You have trial papers. diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html index 109b996..43f323b 100644 --- a/yaksh/templates/yaksh/courses.html +++ b/yaksh/templates/yaksh/courses.html @@ -15,6 +15,10 @@ {% else %} <center><h3> Course(s) Created</h3></center> {% for course in courses %} + {% if user != course.creator %} + <h4> {{course.creator.get_full_name}} added you to this course</h4> + {% endif %} + <div class="row show-grid"> <div class="span14"> <div class="row"> @@ -81,10 +85,9 @@ <br><br> {% endfor %} {% endif %} - +<hr/> {% if allotted_courses %} <center><h3> Course(s) Allotted </h3></center> - {% for course in allotted_courses %} <div class="row show-grid"> <div class="span14"> @@ -141,6 +144,7 @@ {% endif %} </div> </div> + <br/> <button class="btn primary"type="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/addquiz/{{course.id}}/");'>Add New Quiz</button> </div> </div> @@ -150,6 +154,6 @@ <center><h4> No new Courses allotted</h4></center> <br><br> {% endif %} +<hr/> <center><button class="btn primary" type="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/add_course");'>Add New Course</button></center> - {% endblock %} diff --git a/yaksh/templates/yaksh/showquestions.html b/yaksh/templates/yaksh/showquestions.html index 2f4d218..185cbfb 100644 --- a/yaksh/templates/yaksh/showquestions.html +++ b/yaksh/templates/yaksh/showquestions.html @@ -11,7 +11,7 @@ {% block manage %} -<h4>Upload json file for adding questions</h4> +<h4>Upload ZIP file for adding questions</h4> <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} {{ upload_form.as_p }} diff --git a/yaksh/test_models.py b/yaksh/test_models.py index 31513ad..bce2b1b 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -1,13 +1,17 @@ import unittest from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ - StdioBasedTestCase, McqTestCase + StdioBasedTestCase, FileUpload, McqTestCase import json from datetime import datetime, timedelta from django.utils import timezone import pytz from django.contrib.auth.models import Group - +from django.core.files import File +import zipfile +import os +import shutil +import tempfile def setUpModule(): # create user profile @@ -58,7 +62,11 @@ def tearDownModule(): User.objects.all().delete() Question.objects.all().delete() Quiz.objects.all().delete() - + + que_id_list = ["25", "22", "24", "27"] + for que_id in que_id_list: + dir_path = os.path.join(os.getcwd(), "yaksh", "data","question_{0}".format(que_id)) + shutil.rmtree(dir_path) ############################################################################### class ProfileTestCases(unittest.TestCase): @@ -106,6 +114,20 @@ class QuestionTestCases(unittest.TestCase): ) self.question2.save() + # create a temp directory and add files for loading questions test + file_path = os.path.join(os.getcwd(), "yaksh", "test.txt") + self.load_tmp_path = tempfile.mkdtemp() + shutil.copy(file_path, self.load_tmp_path) + file1 = os.path.join(self.load_tmp_path, "test.txt") + + # create a temp directory and add files for dumping questions test + self.dump_tmp_path = tempfile.mkdtemp() + shutil.copy(file_path, self.dump_tmp_path) + file2 = os.path.join(self.dump_tmp_path, "test.txt") + file = open(file2, "r") + django_file = File(file) + file = FileUpload.objects.create(file=django_file, question=self.question2) + self.question1.tags.add('python', 'function') self.assertion_testcase = StandardTestCase(question=self.question1, test_case='assert myfunc(12, 13) == 15' @@ -122,9 +144,14 @@ class QuestionTestCases(unittest.TestCase): "language": "Python", "type": "Code", "test_case_type": "standardtestcase", "testcase": self.test_case_upload_data, + "files": [[file1, 0]], "summary": "Json Demo"}] self.json_questions_data = json.dumps(questions_data) + def tearDown(self): + shutil.rmtree(self.load_tmp_path) + shutil.rmtree(self.dump_tmp_path) + def test_question(self): """ Test question """ self.assertEqual(self.question1.summary, 'Demo question') @@ -139,28 +166,38 @@ class QuestionTestCases(unittest.TestCase): tag_list.append(tag.name) self.assertEqual(tag_list, ['python', 'function']) - def test_dump_questions_into_json(self): + def test_dump_questions(self): """ Test dump questions into json """ question = Question() question_id = [self.question2.id] - questions = json.loads(question.dump_into_json(question_id, self.user2)) + questions_zip = question.dump_questions(question_id, self.user2) + que_file = FileUpload.objects.get(question=self.question2.id) + zip_file = zipfile.ZipFile(questions_zip, "r") + tmp_path = tempfile.mkdtemp() + zip_file.extractall(tmp_path) test_case = self.question2.get_test_cases() - for q in questions: - self.assertEqual(self.question2.summary, q['summary']) - self.assertEqual(self.question2.language, q['language']) - self.assertEqual(self.question2.type, q['type']) - self.assertEqual(self.question2.description, q['description']) - self.assertEqual(self.question2.points, q['points']) - self.assertTrue(self.question2.active) - self.assertEqual(self.question2.snippet, q['snippet']) - self.assertEqual(self.question2.test_case_type, q['test_case_type']) - self.assertEqual([case.get_field_value() for case in test_case], q['testcase']) - - def test_load_questions_from_json(self): + with open("{0}/questions_dump.json".format(tmp_path), "r") as f: + questions = json.loads(f.read()) + for q in questions: + self.assertEqual(self.question2.summary, q['summary']) + self.assertEqual(self.question2.language, q['language']) + self.assertEqual(self.question2.type, q['type']) + self.assertEqual(self.question2.description, q['description']) + self.assertEqual(self.question2.points, q['points']) + self.assertTrue(self.question2.active) + self.assertEqual(self.question2.snippet, q['snippet']) + self.assertEqual(self.question2.test_case_type, q['test_case_type']) + self.assertEqual(os.path.basename(que_file.file.path), q['files'][0][0]) + self.assertEqual([case.get_field_value() for case in test_case], q['testcase']) + for file in zip_file.namelist(): + os.remove(os.path.join(tmp_path, file)) + + def test_load_questions(self): """ Test load questions into database from json """ question = Question() - result = question.load_from_json(self.json_questions_data, self.user1) + result = question.load_questions(self.json_questions_data, self.user1) question_data = Question.objects.get(pk=25) + file = FileUpload.objects.get(question=25) test_case = question_data.get_test_cases() self.assertEqual(question_data.summary, 'Json Demo') self.assertEqual(question_data.language, 'Python') @@ -170,6 +207,7 @@ class QuestionTestCases(unittest.TestCase): self.assertTrue(question_data.active) self.assertEqual(question_data.snippet, 'def fact()') self.assertEqual(question_data.test_case_type, 'standardtestcase') + self.assertEqual(os.path.basename(file.file.path), "test.txt") self.assertEqual([case.get_field_value() for case in test_case], self.test_case_upload_data) diff --git a/yaksh/test_views.py b/yaksh/test_views.py index b5830ec..b52dd2a 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -650,7 +650,6 @@ class TestCourses(TestCase): self.client = Client() self.mod_group = Group.objects.create(name='moderator') - # Create Moderator with profile self.user1_plaintext_pass = 'demo1' self.user1 = User.objects.create_user( diff --git a/yaksh/tests/test_questions.json b/yaksh/tests/test_questions.json new file mode 100644 index 0000000..d617f22 --- /dev/null +++ b/yaksh/tests/test_questions.json @@ -0,0 +1,50 @@ +[ + { + "snippet": "", + "testcase": [ + { + "test_case": "assert is_palindrome(\"hello\") == False" + }, + { + "test_case": "assert is_palindrome(\"nitin\") == True" + } + ], + "points": 3.0, + "test_case_type": "standardtestcase", + "description": "Write a function <code>is_palindrome(arg)</code> which will take one string argument. Return true if the argument is palindrome & false otherwise.\r\n<br><br>\r\nFor Example:\r\n<br>\r\n<code>is_palindrome(\"Hello\")</code> should return <code>False</code>\r\n<br>\r\n<br><br>", + "language": "python", + "active": true, + "type": "code", + "summary": "Python, check palindrome (Code)" + }, + { + "snippet": "#!/bin/bash", + "testcase": [ + { + "test_case": "bash_files/sample.sh, bash_files/sample.args" + } + ], + "points": 1.0, + "test_case_type": "standardtestcase", + "description": "Write a bash script that takes exactly two arguments and returns the sum of the numbers", + "language": "bash", + "active": true, + "type": "code", + "summary": "Bash Question Concatenate Files(Code)" + }, + { + "snippet": "", + "testcase": [ + { + "test_case": "c_cpp_files/main2.c" + } + ], + "points": 1.0, + "test_case_type": "standardtestcase", + "description": "Write a program to add 3 nos", + "language": "c", + "active": true, + "type": "code", + "summary": "selenium test" + } +] diff --git a/yaksh/urls.py b/yaksh/urls.py index bbc5bdc..c4619b6 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -111,4 +111,5 @@ urlpatterns += [ url(r'^manage/regrade/paper/(?P<course_id>\d+)/(?P<answerpaper_id>\d+)/$', views.regrade, name='regrade'), url(r'^manage/(?P<mode>[\w\-]+)/(?P<quiz_id>\d+)/$', views.test_quiz), + url(r'^manage/create_demo_course/$', views.create_demo_course), ] diff --git a/yaksh/views.py b/yaksh/views.py index 44caef9..4c5b9b8 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -20,6 +20,7 @@ import pytz from taggit.models import Tag from itertools import chain import json +import zipfile # Local imports. from yaksh.models import get_model_class, Quiz, Question, QuestionPaper, QuestionSet, Course from yaksh.models import Profile, Answer, AnswerPaper, User, TestCase, FileUpload,\ @@ -30,6 +31,7 @@ from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\ get_object_form, FileForm from settings import URL_ROOT from yaksh.models import AssignmentUpload +from file_utils import extract_files @@ -54,6 +56,7 @@ def is_moderator(user): if user.groups.filter(name='moderator').exists(): return True + def add_to_group(users): """ add users to moderator group """ group = Group.objects.get(name="moderator") @@ -61,6 +64,7 @@ def add_to_group(users): if not is_moderator(user): user.groups.add(group) + def index(request): """The start page. """ @@ -234,7 +238,7 @@ def add_quiz(request, course_id, quiz_id=None): course = get_object_or_404(Course, pk=course_id) ci = RequestContext(request) if not is_moderator(user) or (user != course.creator and user not in course.teachers.all()): - raise Http404('You are not allowed to view this page!') + raise Http404('You are not allowed to view this course !') context = {} if request.method == "POST": if quiz_id is None: @@ -626,6 +630,7 @@ def courses(request): raise Http404('You are not allowed to view this page') courses = Course.objects.filter(creator=user, is_trial=False) allotted_courses = Course.objects.filter(teachers=user, is_trial=False) + context = {'courses': courses, "allotted_courses": allotted_courses} return my_render_to_response('yaksh/courses.html', context, context_instance=ci) @@ -832,22 +837,25 @@ def show_all_questions(request): form = UploadFileForm(request.POST, request.FILES) if form.is_valid(): questions_file = request.FILES['file'] - if questions_file.name.split('.')[-1] == "json": - questions_list = questions_file.read() - question = Question() - question.load_from_json(questions_list, user) + file_name = questions_file.name.split('.') + if file_name[-1] == "zip": + ques = Question() + extract_files(questions_file) + ques.read_json("questions_dump.json", user) else: - message = "Please Upload a JSON file" + message = "Please Upload a ZIP file" context['message'] = message if request.POST.get('download') == 'download': question_ids = request.POST.getlist('question') if question_ids: question = Question() - questions = question.dump_into_json(question_ids, user) - response = HttpResponse(questions, content_type='text/json') - response['Content-Disposition'] = 'attachment; filename=\ - "{0}_questions.json"'.format(user) + zip_file = question.dump_questions(question_ids, user) + response = HttpResponse(content_type='application/zip') + response['Content-Disposition'] = '''attachment;\ + filename={0}_questions.zip'''.format(user) + zip_file.seek(0) + response.write(zip_file.read()) return response else: context['msg'] = "Please select atleast one question to download" @@ -1235,6 +1243,23 @@ def view_answerpaper(request, questionpaper_id): @login_required +def create_demo_course(request): + """ creates a demo course for user """ + user = request.user + ci = RequestContext(request) + if not is_moderator(user): + raise("You are not allowed to view this page") + demo_course = Course() + success = demo_course.create_demo(user) + if success: + msg = "Created Demo course successfully" + else: + msg = "Demo course already created" + context = {'msg': msg} + return my_render_to_response('manage.html', context, context_instance=ci) + + +@login_required def grader(request, extra_context=None): user = request.user if not is_moderator(user): |