diff options
-rw-r--r-- | .gitignore | 1 | ||||
-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/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/urls.py | 3 | ||||
-rw-r--r-- | yaksh/views.py | 45 |
13 files changed, 242 insertions, 49 deletions
@@ -41,3 +41,4 @@ apache/* migrations wsgi.log *.sqlite3 +data/
\ No newline at end of file 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/models.py b/yaksh/models.py index 8e8a0d3..71cd258 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -10,9 +10,15 @@ 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 shutil +import zipfile +import tempfile +from file_utils import extract_files + languages = ( ("python", "Python"), @@ -150,6 +156,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 @@ -228,30 +251,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) @@ -278,6 +306,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 @@ -442,6 +512,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' @@ -573,6 +654,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 b628a44..a736b0a 100644 --- a/yaksh/templates/manage.html +++ b/yaksh/templates/manage.html @@ -77,6 +77,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 ca64f8c..0056638 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 + StdioBasedTestCase, FileUpload 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 7d23ce9..8a38942 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/urls.py b/yaksh/urls.py index daa6008..fb23c7b 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -101,5 +101,6 @@ urlpatterns += [ url(r'^manage/remove_teachers/(?P<course_id>\d+)/$', views.remove_teachers, name='remove_teacher'), url(r'^manage/download_questions/$', views.show_all_questions), url(r'^manage/upload_questions/$', views.show_all_questions), - url(r'^manage/(?P<mode>[\w\-]+)/(?P<quiz_id>\d+)/$', views.test_quiz) + 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 9f7c7a9..959d323 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -22,6 +22,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 @@ -34,6 +35,7 @@ from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\ from yaksh.xmlrpc_clients import code_server from settings import URL_ROOT from yaksh.models import AssignmentUpload +from file_utils import extract_files # The directory where user data can be saved. OUTPUT_DIR = abspath(join(dirname(__file__), 'output')) @@ -73,6 +75,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") @@ -80,6 +83,7 @@ def add_to_group(users): if not is_moderator(user): user.groups.add(group) + def index(request): """The start page. """ @@ -253,7 +257,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: @@ -679,6 +683,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) @@ -885,22 +890,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" @@ -1286,3 +1294,20 @@ def view_answerpaper(request, questionpaper_id): return my_render_to_response('yaksh/view_answerpaper.html', context) else: return my_redirect('/exam/quizzes/') + + +@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) |