diff options
-rw-r--r-- | yaksh/bash_code_evaluator.py | 2 | ||||
-rw-r--r-- | yaksh/bash_stdio_evaluator.py | 2 | ||||
-rw-r--r-- | yaksh/code_evaluator.py | 3 | ||||
-rw-r--r-- | yaksh/file_utils.py | 25 | ||||
-rw-r--r-- | yaksh/forms.py | 15 | ||||
-rw-r--r-- | yaksh/models.py | 70 | ||||
-rw-r--r-- | yaksh/output/README.txt | 4 | ||||
-rw-r--r-- | yaksh/static/yaksh/css/question_paper_creation.css | 12 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/question_paper_creation.js | 218 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_question.html | 2 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_quiz.html | 2 | ||||
-rw-r--r-- | yaksh/templates/yaksh/ajax_questions.html | 31 | ||||
-rw-r--r-- | yaksh/templates/yaksh/courses.html | 25 | ||||
-rw-r--r-- | yaksh/templates/yaksh/design_questionpaper.html | 175 | ||||
-rw-r--r-- | yaksh/test_views.py | 4 | ||||
-rw-r--r-- | yaksh/urls.py | 8 | ||||
-rw-r--r-- | yaksh/views.py | 192 |
17 files changed, 317 insertions, 473 deletions
diff --git a/yaksh/bash_code_evaluator.py b/yaksh/bash_code_evaluator.py index e148fa8..e4b961c 100644 --- a/yaksh/bash_code_evaluator.py +++ b/yaksh/bash_code_evaluator.py @@ -22,10 +22,10 @@ class BashCodeEvaluator(CodeEvaluator): def teardown(self): # Delete the created file. - super(BashCodeEvaluator, self).teardown() os.remove(self.submit_code_path) if self.files: delete_files(self.files) + super(BashCodeEvaluator, self).teardown() def check_code(self, user_answer, file_paths, test_case): """ Function validates student script using instructor script as diff --git a/yaksh/bash_stdio_evaluator.py b/yaksh/bash_stdio_evaluator.py index e5e0da6..a7ea1a4 100644 --- a/yaksh/bash_stdio_evaluator.py +++ b/yaksh/bash_stdio_evaluator.py @@ -17,10 +17,10 @@ class BashStdioEvaluator(StdIOEvaluator): self.submit_code_path = self.create_submit_code_file('Test.sh') def teardown(self): - super(BashStdioEvaluator, self).teardown() os.remove(self.submit_code_path) if self.files: delete_files(self.files) + super(BashStdioEvaluator, self).teardown() def compile_code(self, user_answer, file_paths, expected_input, expected_output): self.files = [] diff --git a/yaksh/code_evaluator.py b/yaksh/code_evaluator.py index 870a67f..79f616d 100644 --- a/yaksh/code_evaluator.py +++ b/yaksh/code_evaluator.py @@ -94,6 +94,9 @@ class CodeEvaluator(object): # Private Protocol ########## def setup(self): + if self.in_dir: + if not os.path.exists(self.in_dir): + os.makedirs(self.in_dir) self._change_dir(self.in_dir) def safe_evaluate(self, user_answer, test_case_data, file_paths=None): diff --git a/yaksh/file_utils.py b/yaksh/file_utils.py index afcf9e8..f41c531 100644 --- a/yaksh/file_utils.py +++ b/yaksh/file_utils.py @@ -1,6 +1,7 @@ import shutil import os import zipfile +import tempfile def copy_files(file_paths): @@ -14,16 +15,19 @@ def copy_files(file_paths): files.append(file_name) shutil.copy(file_path, os.getcwd()) if extract: - z_files = extract_files(file_name) + z_files, path = extract_files(file_name, os.getcwd()) for file in z_files: files.append(file) return files -def delete_files(files): - """ Delete Files from current directory """ - - for file in files: +def delete_files(files, file_path=None): + """ Delete Files from directory """ + for file_name in files: + if file_path: + file = os.path.join(file_path, file_name) + else: + file = file_name if os.path.exists(file): if os.path.isfile(file): os.remove(file) @@ -31,13 +35,18 @@ def delete_files(files): shutil.rmtree(file) -def extract_files(zip_file): +def extract_files(zip_file, path=None): + """ extract files from zip """ 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() + if path: + extract_path = path + else: + extract_path = tempfile.gettempdir() + zip_file.extractall(extract_path) zip_file.close() - return zfiles + return zfiles, extract_path diff --git a/yaksh/forms.py b/yaksh/forms.py index a12ce9a..1931fad 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -1,6 +1,6 @@ from django import forms -from yaksh.models import get_model_class, Profile, Quiz, Question, TestCase, Course, StandardTestCase, StdioBasedTestCase - +from yaksh.models import get_model_class, Profile, Quiz, Question, TestCase, Course,\ + QuestionPaper, StandardTestCase, StdioBasedTestCase from django.contrib.auth import authenticate from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType @@ -177,7 +177,7 @@ class QuestionForm(forms.ModelForm): class Meta: model = Question - exclude = ['user'] + exclude = ['user', 'active'] class FileForm(forms.Form): @@ -199,7 +199,7 @@ class QuestionFilterForm(forms.Form): super(QuestionFilterForm, self).__init__(*args, **kwargs) questions = Question.objects.filter(user_id=user.id) points_list = questions.values_list('points', flat=True).distinct() - points_options = [('select', 'Select Marks')] + points_options = [(None, 'Select Marks')] points_options.extend([(point, point) for point in points_list]) self.fields['marks'] = forms.FloatField(widget=forms.Select\ (choices=points_options)) @@ -215,6 +215,7 @@ class CourseForm(forms.ModelForm): model = Course fields = ['name', 'active', 'enrollment'] + class ProfileForm(forms.ModelForm): """ profile form for students and moderators """ @@ -236,3 +237,9 @@ class ProfileForm(forms.ModelForm): class UploadFileForm(forms.Form): file = forms.FileField() + + +class QuestionPaperForm(forms.ModelForm): + class Meta: + model = QuestionPaper + fields = ['shuffle_questions'] diff --git a/yaksh/models.py b/yaksh/models.py index 444df4a..7f9eead 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -22,7 +22,7 @@ from os.path import join, abspath, dirname, exists import shutil import zipfile import tempfile -from .file_utils import extract_files +from .file_utils import extract_files, delete_files from yaksh.xmlrpc_clients import code_server from django.conf import settings @@ -174,7 +174,7 @@ class Course(models.Model): demo_ques = Question() demo_ques.create_demo_questions(user) demo_que_ppr = QuestionPaper() - demo_que_ppr.create_demo_quiz_ppr(demo_quiz) + demo_que_ppr.create_demo_quiz_ppr(demo_quiz, user) success = True else: success = False @@ -206,12 +206,6 @@ class Profile(models.Model): """Return the output directory for the user.""" user_dir = join(settings.OUTPUT_DIR, str(self.user.username)) - if not exists(user_dir): - os.makedirs(user_dir) - # Make it rwx by others. - os.chmod(user_dir, stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH - | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR - | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP) return user_dir @@ -271,7 +265,7 @@ class Question(models.Model): return json.dumps(question_data) def dump_questions(self, question_ids, user): - questions = Question.objects.filter(id__in=question_ids, user_id=user.id) + questions = Question.objects.filter(id__in=question_ids, user_id=user.id, active=True) questions_dict = [] zip_file_name = string_io() zip_file = zipfile.ZipFile(zip_file_name, "a") @@ -290,7 +284,7 @@ class Question(models.Model): question._add_json_to_zip(zip_file, questions_dict) return zip_file_name - def load_questions(self, questions_list, user): + def load_questions(self, questions_list, user, file_path=None, files_list=None): questions = json.loads(questions_list) for question in questions: question['user'] = user @@ -298,10 +292,12 @@ class Question(models.Model): test_cases = question.pop('testcase') que, result = Question.objects.get_or_create(**question) if file_names: - que._add_files_to_db(file_names) + que._add_files_to_db(file_names, file_path) 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) + if files_list: + delete_files(files_list, file_path) def get_test_cases(self, **kwargs): test_case_ctype = ContentType.objects.get(app_label="yaksh", @@ -333,15 +329,18 @@ class Question(models.Model): files_list.append(((os.path.basename(f.file.path)), f.extract)) return files_list - def _add_files_to_db(self, file_names): + def _add_files_to_db(self, file_names, path): 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) + q_file = os.path.join(path, file_name) + if os.path.exists(q_file): + que_file = open(q_file, 'r') + # Converting to Python file object with + # some Django-specific additions + django_file = File(que_file) + file_upload = FileUpload() + file_upload.question = self + file_upload.extract = extract + file_upload.file.save(file_name, django_file, save=True) def _add_json_to_zip(self, zip_file, q_dict): json_data = json.dumps(q_dict, indent=2) @@ -353,18 +352,18 @@ class Question(models.Model): zip_file.close() shutil.rmtree(tmp_file_path) - def read_json(self, json_file, user): + def read_json(self, file_path, user, files=None): + json_file = os.path.join(file_path, "questions_dump.json") 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) + self.load_questions(questions_list, user, file_path, files) 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) + 'fixtures', 'demo_questions.zip') + files, extract_path = extract_files(zip_file_path) + self.read_json(extract_path, user, files) def __str__(self): @@ -604,10 +603,10 @@ class QuestionPaper(models.Model): random_questions = models.ManyToManyField("QuestionSet") # Option to shuffle questions, each time a new question paper is created. - shuffle_questions = models.BooleanField(default=False) + shuffle_questions = models.BooleanField(default=False, blank=False) # Total marks for the question paper. - total_marks = models.FloatField() + total_marks = models.FloatField(default=0.0, blank=True) objects = QuestionPaperManager() @@ -623,8 +622,7 @@ class QuestionPaper(models.Model): def _get_questions_for_answerpaper(self): """ Returns fixed and random questions for the answer paper""" - questions = [] - questions = list(self.fixed_questions.all()) + questions = list(self.fixed_questions.filter(active=True)) for question_set in self.random_questions.all(): questions += question_set.get_random_questions() return questions @@ -674,13 +672,14 @@ class QuestionPaper(models.Model): prerequisite = self._get_prequisite_paper() return prerequisite._is_questionpaper_passed(user) - def create_demo_quiz_ppr(self, demo_quiz): + def create_demo_quiz_ppr(self, demo_quiz, user): question_paper = QuestionPaper.objects.create(quiz=demo_quiz, - total_marks=5.0, + total_marks=7.0, shuffle_questions=True ) questions = Question.objects.filter(active=True, - summary="Yaksh Demo Question") + summary="Yaksh Demo Question", + user=user) # add fixed set of questions to the question paper for question in questions: question_paper.fixed_questions.add(question) @@ -764,8 +763,9 @@ class AnswerPaperManager(models.Manager): attempt_number) questions = self.get_all_questions(questionpaper_id, attempt_number) all_questions = Question.objects.filter( - id__in=set(questions) - ).order_by('type') + id__in=set(questions), + active=True + ).order_by('type') for question in all_questions: if question.id in questions_answered: question_stats[question] = [questions_answered[question.id], @@ -992,7 +992,7 @@ class AnswerPaper(models.Model): return q_a def get_questions(self): - return self.questions.all() + return self.questions.filter(active=True) def get_questions_answered(self): return self.questions_answered.all() diff --git a/yaksh/output/README.txt b/yaksh/output/README.txt deleted file mode 100644 index 3163ed4..0000000 --- a/yaksh/output/README.txt +++ /dev/null @@ -1,4 +0,0 @@ -This directory contains files generated/saved by users as per their -username. The test executor will chdir into this user directory for each -user when they run the test. Do not delete this directory and ensure that -it is writeable by all.
\ No newline at end of file diff --git a/yaksh/static/yaksh/css/question_paper_creation.css b/yaksh/static/yaksh/css/question_paper_creation.css index c915320..588b65c 100644 --- a/yaksh/static/yaksh/css/question_paper_creation.css +++ b/yaksh/static/yaksh/css/question_paper_creation.css @@ -5,9 +5,6 @@ body { line-height: 18px; color: #404040; } -.clearfix { - clear: both; -} .tabs li { text-align: center; width: 33%; @@ -48,7 +45,7 @@ body { padding: 7px 0; border: 2px solid #f5f5f5; } -#selectors .span4 { +#selectors .col-md-6 { margin-left: 0; } #id_question_type { @@ -57,8 +54,8 @@ body { #id_marks { width: 100%; } -#fixed-questions .span7 > div, -#random-questions .span7 > div{ +#fixed-questions .col-md-6 > div, +#random-questions .col-md-6 > div{ background: #f5f5f5; height: 200px; border: 1px solid #333333; @@ -111,9 +108,6 @@ body { .red-alert { border: 2px solid red; } -#myModal .qcard .remove{ - display: none; -} .well{ padding: 5px; } diff --git a/yaksh/static/yaksh/js/question_paper_creation.js b/yaksh/static/yaksh/js/question_paper_creation.js index a144540..898e491 100644 --- a/yaksh/static/yaksh/js/question_paper_creation.js +++ b/yaksh/static/yaksh/js/question_paper_creation.js @@ -1,204 +1,33 @@ $(document).ready(function(){ - /* selectors for the 3 step tabs*/ - $fixed_tab = $("#fixed-tab"); - $random_tab = $("#random-tab"); - $finish_tab = $("#finish-tab"); - - $question_type = $("#id_question_type"); - $marks = $("#id_marks"); - - $total_marks = $("#total_marks"); + $question_type = $('#id_question_type'); + $qpaper_id = $('#qpaper_id'); + $marks = $('#id_marks'); + $show = $('#show'); /* ajax requsts on selectors change */ $question_type.change(function() { - $.ajax({ - url: "/exam/ajax/questionpaper/marks/", - type: "POST", - data: { - question_type: $question_type.val() - }, - dataType: "html", - success: function(output) { - $marks.html(output); - } - }); + this.form.submit(); }); $marks.change(function() { - var fixed_question_list = []; - var fixed_inputs = $("input[name=fixed]"); - var random_question_list = []; - var random_inputs = $("input[name=random]"); - for(var i = 0; i < fixed_inputs.length; i++){ - fixed_question_list.push($(fixed_inputs[i]).val()); - } - for(var i = 0; i < random_inputs.length; i++){ - random_question_list.push($(random_inputs[i]).val()); - } - $.ajax({ - url: "/exam/ajax/questionpaper/questions/", - type: "POST", - data: { - question_type: $question_type.val(), - marks: $marks.val(), - fixed_list: fixed_question_list, - random_list: random_question_list - }, - dataType: "html", - success: function(output) { - if($fixed_tab.hasClass("active")) { - var questions = $(output).filter("#questions").html(); - $("#fixed-available").html(questions); - } else if($random_tab.hasClass("active")) { - var questions = $(output).filter("#questions").html(); - var numbers = $(output).filter("#num").html(); - $("#random-available").html(questions); - $("#number-wrapper").html(numbers); - } - } - }); - }); - - /* adding fixed questions */ - $("#add-fixed").click(function(e) { - var count = 0; - var selected = []; - var html = ""; - var $element; - var total_marks = parseFloat($total_marks.text()); - var marks_per = parseFloat($marks.val()) - $("#fixed-available input:checkbox").each(function(index, element) { - if($(this).attr("checked")) { - qid = $(this).attr("data-qid"); - if(!$(this).hasClass("ignore")) { - selected.push(qid); - $element = $("<div class='qcard'></div>"); - html += "<li>" + $(this).next().html() + "</li>"; - count++; - } - } - }); - html = "<ul>" + html + "</ul>"; - selected = selected.join(","); - var $input = $("<input type='hidden'>"); - $input.attr({ - value: selected, - name: "fixed" - }); - $remove = $("<a href='#' class='remove' data-num="+count+" data-marks = "+marks_per +">×</div>"); - $element.html(count + " question(s) added").append(html).append($input).append($remove); - $("#fixed-added").prepend($element); - total_marks = total_marks + count * marks_per; - $total_marks.text(total_marks) - e.preventDefault(); - }); - - /* adding random questions */ - $("#add-random").click(function(e) { - $numbers = $("#numbers"); - random_number = $numbers.val() - if($numbers.val()) { - $numbers.removeClass("red-alert"); - var count = 0; - var selected = []; - var html = ""; - var $element; - var total_marks = parseFloat($total_marks.text()); - var marks_per = parseFloat($marks.val()) - $("#random-available input:checkbox").each(function(index, element) { - if($(this).attr("checked")) { - qid = $(this).attr("data-qid"); - if(!$(this).hasClass("ignore")) { - selected.push(qid); - $element = $("<div class='qcard'></div>"); - html += "<li>" + $(this).next().html() + "</li>"; - count++; - } - } - }); - html = "<ul>" + html + "</ul>"; - selected = selected.join(","); - var $input_random = $("<input type='hidden'>"); - $input_random.attr({ - value: selected, - name: "random" - }); - var $input_number = $("<input type='hidden'>"); - $input_number.attr({ - value: $numbers.val(), - name: "number" - }); - $remove = $("<a href='#' class='remove' data-num="+random_number+" data-marks = "+marks_per +">×</div>"); - $element.html(random_number + " question(s) will be selected from " + count + " question(s)").append(html).append($input_random).append($input_number).append($remove); - $("#random-added").prepend($element); - total_marks = total_marks + random_number * marks_per; - $total_marks.text(total_marks) - } else { - $numbers.addClass("red-alert"); - } - e.preventDefault(); - }); - - /* removing added questions */ - $(".qcard .remove").live("click", function(e) { - var marks_per = $(this).attr('data-marks'); - var num_question = $(this).attr('data-num'); - var sub_marks = marks_per*num_question; - var total_marks = parseFloat($total_marks.text()); - total_marks = total_marks - sub_marks; - $total_marks.text(total_marks); - - $(this).parent().slideUp("normal", function(){ $(this).remove(); }); - e.preventDefault(); + this.form.submit(); }); /* showing/hiding selectors on tab click */ $(".tabs li").click(function() { if($(this).attr("id") == "finish-tab") { $("#selectors").hide(); + $('#is_active').val("finish"); } else { - $question_type.val('select'); - $marks.val('select') - $("#selectors").show(); - } - }); - /* check all questions on checked*/ - $("#checkall").live("click", function(){ - if($(this).attr("checked")) { - if($("#fixed-tab").hasClass("active")) { - $("#fixed-available input:checkbox").each(function(index, element) { - $(this).attr('checked','checked'); - }); + if($(this).attr("id") == "fixed-tab") { + $('#is_active').val("fixed"); } - else { - $("#random-available input:checkbox").each(function(index, element) { - $(this).attr('checked','checked'); - }); + if($(this).attr("id") == "random-tab") { + $('#is_active').val("random"); } - } - else { - if($("#fixed-tab").hasClass("active")) { - $("#fixed-available input:checkbox").each(function(index, element) { - $(this).removeAttr('checked'); - }); - } - else { - $("#random-available input:checkbox").each(function(index, element) { - $(this).removeAttr('checked'); - }); - } - } - }); - - /* show preview on preview click */ - $("#preview").click(function(){ - questions = getQuestions() - if(questions.trim() == ""){ - $('#modal_body').html("No questions selected"); + $question_type.val('select'); + $marks.val('select') + $("#selectors").show(); } - else { - $('#modal_body').html(questions); - } - $("#myModal").modal('show'); }); /* tab change on next or previous button click */ @@ -217,21 +46,4 @@ $(document).ready(function(){ $("#random").click(); }); - /* Check at least one question is present before saving */ - $('#save').click(function(){ - questions = getQuestions(); - if(questions.trim() == ""){ - $("#modalSave").modal("show"); - } - else { - document.forms["frm"].submit(); - } - }); - - /* Fetch selected questions */ - function getQuestions(){ - var fixed_div = $("#fixed-added").html(); - var random_div = $("#random-added").html(); - return fixed_div+random_div; - } -}); //document +});//document diff --git a/yaksh/templates/yaksh/add_question.html b/yaksh/templates/yaksh/add_question.html index 9142bb7..c0d53f8 100644 --- a/yaksh/templates/yaksh/add_question.html +++ b/yaksh/templates/yaksh/add_question.html @@ -18,7 +18,7 @@ <center><table class="table"> <tr><td>Summary: <td>{{ form.summary }}{{ form.summary.errors }} <tr><td> Language: <td> {{form.language}}{{form.language.errors}} - <tr><td> Active: <td> {{ form.active }}{{form.active.errors}} Type: {{ form.type }}{{form.type.errors}} + <tr><td> Type: <td> {{ form.type }}{{form.type.errors}} <tr><td>Points:<td><button class="btn-mini" type="button" onClick="increase(frm);">+</button>{{form.points }}<button class="btn-mini" type="button" onClick="decrease(frm);">-</button>{{ form.points.errors }} <tr><td><strong>Rendered: </strong><td><p id='my'></p> <tr><td>Description: <td>{{ form.description}} {{form.description.errors}} diff --git a/yaksh/templates/yaksh/add_quiz.html b/yaksh/templates/yaksh/add_quiz.html index c674f4b..97bf5a2 100644 --- a/yaksh/templates/yaksh/add_quiz.html +++ b/yaksh/templates/yaksh/add_quiz.html @@ -20,7 +20,7 @@ </table> </center> - <center><button class="btn" type="submit" id="submit" name="questionpaper">Design Question Paper</button> + <center><button class="btn" type="submit" id="submit" name="questionpaper"> Save </button> <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/");'>Cancel</button> </center> diff --git a/yaksh/templates/yaksh/ajax_questions.html b/yaksh/templates/yaksh/ajax_questions.html deleted file mode 100644 index e343f9b..0000000 --- a/yaksh/templates/yaksh/ajax_questions.html +++ /dev/null @@ -1,31 +0,0 @@ -<div id="questions"> - {% if questions %} - <input type="checkbox" id="checkall" class="ignore"> - <span><b> <font size="3"> Select All </font></b></span> - {% endif %} - <ul class="inputs-list"> - - {% for question in questions %} - <li> - <label> - <input type="checkbox" name="questions" data-qid="{{question.id}}"> - <span> {{ question.summary }} </span> - </label> - </li> - {% endfor %} - </ul> -</div> - -<div id="num"> - <select id="numbers"> - <option value="">Number of questions to be picked from the pool</option> - {% for q in questions %} - {% if forloop.counter0 != 0 %} - <option value={{forloop.counter0}}>{{ forloop.counter0}}</option> - {% endif %} - {% if questions|length == 1%} - <option value=1>1</option> - {% endif %} - {% endfor %} - </select> -</div> diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html index 0a61d72..24e29af 100644 --- a/yaksh/templates/yaksh/courses.html +++ b/yaksh/templates/yaksh/courses.html @@ -18,11 +18,10 @@ {% 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"> - <div class="span6"> + <div class="span4"> <p> <b><u>Course</u></b> {% if course.active %} @@ -35,7 +34,7 @@ </br></br> {% if user == course.creator %} <div class="row"> - <div class="span6 wrap"> + <div class="span3 wrap"> <center><b><u>Teacher(s) Added to {{ course }}</u></b></center> {% if course.get_teachers %} <div align="left"> @@ -65,12 +64,23 @@ <p><b><a href="{{URL_ROOT}}/exam/manage/searchteacher/{{course.id}}/">Add Teacher</a></b></p> </div> {% endif %} - <div class="span6"> - <p><b><u>Quiz(zes)</u></b></p> + <div class="span4" style="text-align:left"> {% if course.get_quizzes %} - {% for quiz in course.get_quizzes %} + <p><b><u>Quiz(zes)</u></b></p> + {% for quiz in course.get_quizzes %} <a href="{{URL_ROOT}}/exam/manage/addquiz/{{course.id}}/{{quiz.id}}/">{{ quiz.description }}</a><br> - + {% endfor %} + </div> + <div class="span4" style="text-align:left"> + <p><b><u>Question Paper(s)</u></b></p> + {% for quiz in course.get_quizzes %} + {% if quiz.questionpaper_set.get %} + <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ quiz.id }}/{{quiz.questionpaper_set.get.id}}/">Question Paper for {{ quiz.description }}</a><br> + {% else %} + <p><b>No Question Paper </b> + <button class="btn small primary" type="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/quiz/designquestionpaper/{{ quiz.id }}/");'>Add</button> + </p> + {% endif %} {% endfor %} {% else %} <p><b>No quiz </b></p> @@ -79,7 +89,6 @@ </div> <br/> <button class="btn primary"type="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/addquiz/{{course.id}}/");'>Add New Quiz</button> - </div> </div> <br><br> diff --git a/yaksh/templates/yaksh/design_questionpaper.html b/yaksh/templates/yaksh/design_questionpaper.html index 9c190b7..387402a 100644 --- a/yaksh/templates/yaksh/design_questionpaper.html +++ b/yaksh/templates/yaksh/design_questionpaper.html @@ -1,12 +1,12 @@ {% extends "manage.html" %} -{% block subtitle %}Design Question Paper{% endblock %} +{% block title %} Design Question Paper {% endblock title %} + +{% block subtitle %} Design Question Paper {% endblock %} {% block css %} -<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/base.css" type="text/css" /> -<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/question_quiz.css" type="text/css" /> -<link rel="stylesheet" media="all" type="text/css" href="{{ URL_ROOT }}/static/yaksh/css/autotaggit.css" /> -<link rel="stylesheet" media="all" type="text/css" href="{{ URL_ROOT }}/static/yaksh/css/question_paper_creation.css" /> + <link rel="stylesheet" media="all" type="text/css" href="{{ URL_ROOT }}/static/yaksh/css/bootstrap.css" /> + <link rel="stylesheet" media="all" type="text/css" href="{{ URL_ROOT }}/static/yaksh/css/question_paper_creation.css" /> <style> select { @@ -14,17 +14,20 @@ select } </style> {% endblock %} -{% block script %} -<script src="{{ URL_ROOT }}/static/yaksh/js/jquery-1.4.2.min.js" type="text/javascript"></script> -<script src="{{ URL_ROOT }}/static/yaksh/js/bootstrap-tabs.js"></script> -<script src="{{ URL_ROOT }}/static/yaksh/js/add_questionpaper.js"></script> -<script src="{{ URL_ROOT }}/static/yaksh/js/question_paper_creation.js"></script> -<script src="{{ URL_ROOT }}/static/yaksh/js/bootstrap-modal.js"></script> +{% block script %} + <script src="{{ URL_ROOT }}/static/yaksh/js/jquery-1.9.1.min.js"></script> + <script src="{{ URL_ROOT }}/static/yaksh/js/bootstrap.js"></script> + <script src="{{ URL_ROOT }}/static/yaksh/js/bootstrap-tabs.js"></script> + <script src="{{ URL_ROOT }}/static/yaksh/js/question_paper_creation.js"></script> {% endblock %} {% block content %} <input type=hidden id="url_root" value={{ URL_ROOT }}> +<form action="{{ URL_ROOT }}/exam/manage/designquestionpaper/{{ qpaper.quiz.id }}/{{ qpaper.id }}/" method="POST"> +<input class ="btn primary small" type="submit" name="back" id="back" value="Cancel"> + {% csrf_token %} + <input type=hidden name="is_active" id="is_active" value="{{ state }}"> <center><b>Manual mode to design the {{lang}} Question Paper</center><br> <ul class="tabs" data-tabs="tabs"> <li class="active" id="fixed-tab"> @@ -46,42 +49,62 @@ select </a></li> </ul> -<form action="{{URL_ROOT}}/exam/manage/designquestionpaper/" method="post" name=frm > {% csrf_token %} <div> - <h3>Total Marks: <span id="total_marks" class="well">0</span></h3> + <h3>Total Marks: <span id="total_marks" class="well"> {{ qpaper.total_marks }} </span></h3> </div> <div class="tab-content"> <!-- common to fixed and random questions --> <div class="row" id="selectors"> <h5 style="padding-left: 20px;">Please select Question type and Marks</h5> - <div class="span4"> - {{ form.question_type }} - </div> <!-- /.span4 --> - <div class="span4"> - {{ form.marks }} - </div> <!-- /.span4 --> - <div class="span4"> - <div class="pull-left" id="number-wrapper"></div> - </div> <!-- /.span4 --> + <div class="col-md-6"> + {{ filter_form.question_type }} + </div> + <div class="col-md-6"> + {{ filter_form.marks }} + </div> </div> <!-- /.row --> <br><br> - <div class="tab-pane active" id="fixed-questions"> <div class="row"> - <div class="span7"> + <div class="col-md-6"> <div id="fixed-available-wrapper"> <p><u>Select questions to add:</u></p> <div id="fixed-available"> + {% if state == "fixed" or state == "None" %} + <ul class="inputs-list"> + {% for question in questions %} + <li> + <label> + <input type="checkbox" name="questions" data-qid="{{question.id}}" value={{question.id}}> + <span> {{ question.summary }} </span> <span> {{ question.points }} </span> + </label> + </li> + {% endfor %} + </ul> + {% endif %} </div> - <a id="add-fixed" class="btn small primary pull-right" href="#">Add to paper</a> + <br /><br /> + <button id="add-fixed" name="add-fixed" class="btn small primary pull-right" type="submit">Add to paper</button> </div> </div> - <div class="span7"> + <div class="col-md-6"> <div id="fixed-added-wrapper"> <p><u>Fixed questions currently in paper:</u></p> <div id="fixed-added"> + <ul class="inputs-list"> + {% for question in fixed_questions %} + <li> + <label> + <input type="checkbox" name="added-questions" data-qid="{{question.id}}" value={{question.id}}> + <span> {{ question.summary }} </span> <span> {{ question.points }} </span> + </label> + </li> + {% endfor %} + </ul> </div> + <br /> + <button id="remove-fixed" name="remove-fixed" class="btn small primary pull-right" type="submit"> Remove from paper</button> </div> </div> </div> <!-- /.row --> @@ -89,25 +112,66 @@ select <div class="pull-right"> <a class="btn" id="fixed-next">Next ></a> </div> - </div> <!-- /#fixed-questions --> - <div class="tab-pane" id="random-questions"> <div class="row"> - <div class="span7"> + <div class="col-md-6"> <div id="random-available-wrapper"> <p><u>Select questions to add to the pool:</u></p> <div id="random-available"> + {% if state == "random" %} + <select id="num_of_questions" name="num_of_questions"> + <option value="1">Number of questions to be picked from the pool</option> + {% for q in questions %} + {% if forloop.counter0 != 0 %} + <option value={{forloop.counter0}}>{{ forloop.counter0}}</option> + {% endif %} + {% if questions|length == 1%} + <option value=1>1</option> + {% endif %} + {% endfor %} + </select> + <ul class="inputs-list"> + {% for question in questions %} + <li> + <label> + <input type="checkbox" name="random_questions" data-qid="{{question.id}}" value={{question.id}}> + <span> {{ question.summary }} </span> <span> {{ question.points }} </span> + </label> + </li> + {% endfor %} + </ul> + {% endif %} </div> - <a id="add-random" class="btn small primary pull-right" href="#">Add to paper</a> + <br /><br /> + <button id="add-random" name="add-random" class="btn small primary pull-right" type="submit">Add to paper</button> </div> </div> - <div class="span7"> + <div class="col-md-6"> <div id="random-added-wrapper"> <p><u>Pool of questions currently in paper:</u></p> <div id="random-added"> + <ul class="inputs-list"> + {% for random_set in random_sets %} + <li> + <label> + <input type="checkbox" name="random_sets" data-qid="{{random_set.id}}" value={{random_set.id}}> + <span> Random Set {{ forloop.counter }} (will take {{ random_set.num_questions }} randomly out of {{ random_set.questions.count }})</span> + </label> + </li> + {% for question in random_set.questions.all %} + <li> + <label> + <span> {{ question.summary }} </span> <span> {{ question.points }} </span> + </label> + </li> + {% endfor %} + {% endfor %} + </ul> </div> + <br /> + <button id="remove-random" name="remove-random" class="btn small primary pull-right" type="submit"> Remove from paper</button> </div> </div> </div> <!-- /.row --> @@ -124,11 +188,10 @@ select <center> <h5>Almost finished creating your question paper</h5> <label style="float: none;"> - {{ form.shuffle_questions }} + {{ qpaper_form.shuffle_questions }} <span>Auto shuffle.</span> </label> <br><br> - <input class ="btn primary large" type="button" id="preview" value="Preview question paper"> - <input class ="btn primary large" type="button" id="save" value="Save question paper"> + <input class ="btn primary large" type="submit" name="save" id="save" value="Save question paper"> <br> <div class="pull-left"> <a class="btn" id="finish-prev">< Previous</a> @@ -139,44 +202,18 @@ select <!-- /.tab-content --> </form> <br> -<div class="clearfix"></div> - -<!-- Modal --> -<div class="modal fade " id="myModal" > - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <h4 class="modal-title" id="myModalLabel">Question Paper Preview</h4> - </div> - <div id = "modal_body"class="modal-body"> - </div> - <div class="modal-footer"> - <button type="button" class="btn primary close" data-dismiss="modal">OK</button> - </div> - </div> - </div> -</div> - -<div class="modal fade " id="modalSave" > - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <h4 class="modal-title" id="myModalLabel">Cannot Save</h4> - </div> - <div class="modal-body"> - Please select questions for your paper - </div> - <div class="modal-footer"> - <button type="button" class="btn primary close" data-dismiss="modal">OK</button> - </div> - </div> - </div> -</div> -</div> - <script> $(function () { $('.tabs').tabs() + {% if state == "fixed" %} + $('#fixed').tab('show'); + {% elif state == "random" %} + $("#random").tab('show'); + {% elif state == "finish" %} + $('#finished').tab('show'); + {% endif %} }) </script> {% endblock %} +{% block manage %} +{% endblock %} diff --git a/yaksh/test_views.py b/yaksh/test_views.py index cf547b5..1d1c3f3 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -294,7 +294,7 @@ class TestAddQuiz(TestCase): self.assertEqual(updated_quiz.course, self.course) self.assertEqual(response.status_code, 302) - self.assertRedirects(response, '/exam/manage/') + self.assertRedirects(response, '/exam/manage/courses/') def test_add_quiz_post_new_quiz(self): """ @@ -340,7 +340,7 @@ class TestAddQuiz(TestCase): self.assertEqual(new_quiz.course, self.course) self.assertEqual(response.status_code, 302) - self.assertRedirects(response, '/exam/manage/designquestionpaper/') + self.assertRedirects(response, '/exam/manage/courses/') class TestAddTeacher(TestCase): def setUp(self): diff --git a/yaksh/urls.py b/yaksh/urls.py index c4619b6..bb8cf2e 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -68,9 +68,10 @@ urlpatterns += [ url(r'^manage/user_data/(?P<user_id>\d+)/(?P<questionpaper_id>\d+)/$', views.user_data), url(r'^manage/user_data/(?P<user_id>\d+)/$', views.user_data), - url(r'^manage/designquestionpaper/$', views.design_questionpaper, name='design_questionpaper'), - url(r'^manage/designquestionpaper/(?P<questionpaper_id>\d+)/$',\ - views.design_questionpaper), + url(r'^manage/quiz/designquestionpaper/(?P<quiz_id>\d+)/$', views.design_questionpaper, + name='design_questionpaper'), + url(r'^manage/designquestionpaper/(?P<quiz_id>\d+)/(?P<questionpaper_id>\d+)/$', + views.design_questionpaper, name='designquestionpaper'), url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/$', views.show_statistics), url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<attempt_number>\d+)/$', @@ -87,7 +88,6 @@ urlpatterns += [ url(r'manage/enrolled/reject/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.reject, {'was_enrolled': True}), url(r'manage/toggle_status/(?P<course_id>\d+)/$', views.toggle_course_status), - url(r'^ajax/questionpaper/(?P<query>.+)/$', views.ajax_questionpaper), url(r'^ajax/questions/filter/$', views.ajax_questions_filter), url(r'^editprofile/$', views.edit_profile, name='edit_profile'), url(r'^viewprofile/$', views.view_profile, name='view_profile'), diff --git a/yaksh/views.py b/yaksh/views.py index d6e54e2..2113477 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -29,7 +29,7 @@ from yaksh.models import Profile, Answer, AnswerPaper, User, TestCase, FileUploa from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\ QuestionForm, RandomQuestionForm,\ QuestionFilterForm, CourseForm, ProfileForm, UploadFileForm,\ - get_object_form, FileForm + get_object_form, FileForm, QuestionPaperForm from .settings import URL_ROOT from yaksh.models import AssignmentUpload from .file_utils import extract_files @@ -246,7 +246,7 @@ def add_quiz(request, course_id, quiz_id=None): form = QuizForm(request.POST, user=user, course=course_id) if form.is_valid(): form.save() - return my_redirect(reverse('yaksh:design_questionpaper')) + return my_redirect("/exam/manage/courses/") else: context["form"] = form return my_render_to_response('yaksh/add_quiz.html', @@ -259,7 +259,7 @@ def add_quiz(request, course_id, quiz_id=None): if form.is_valid(): form.save() context["quiz_id"] = quiz_id - return my_redirect("/exam/manage/") + return my_redirect("/exam/manage/courses/") else: if quiz_id is None: form = QuizForm(course=course_id, user=user) @@ -415,8 +415,6 @@ def start(request, questionpaper_id=None, attempt_num=None): msg = 'You do not have a profile and cannot take the quiz!' raise Http404(msg) new_paper = quest_paper.make_answerpaper(user, ip, attempt_num) - # Make user directory. - user_dir = new_paper.user.profile.get_user_dir() return show_question(request, new_paper.current_question(), new_paper) @@ -631,7 +629,6 @@ 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) @@ -813,6 +810,95 @@ def ajax_questions_filter(request): {'questions': questions}) +def _get_questions(user, question_type, marks): + if question_type is None and marks is None: + return None + if question_type: + questions = Question.objects.filter(type=question_type, user=user) + if marks: + questions = questions.filter(points=marks) + return questions + + +def _remove_already_present(questionpaper_id, questions): + if questionpaper_id is None: + return questions + questionpaper = QuestionPaper.objects.get(pk=questionpaper_id) + questions = questions.exclude( + id__in=questionpaper.fixed_questions.values_list('id', flat=True)) + for random_set in questionpaper.random_questions.all(): + questions = questions.exclude( + id__in=random_set.questions.values_list('id', flat=True)) + return questions + + +@login_required +def design_questionpaper(request, quiz_id, questionpaper_id=None): + user = request.user + + if not is_moderator(user): + raise Http404('You are not allowed to view this page!') + + filter_form = QuestionFilterForm(user=user) + questions = None + marks = None + state = None + if questionpaper_id is None: + question_paper = QuestionPaper.objects.get_or_create(quiz_id=quiz_id)[0] + else: + question_paper = get_object_or_404(QuestionPaper, id=questionpaper_id) + qpaper_form = QuestionPaperForm(instance=question_paper) + + if request.method == 'POST': + + filter_form = QuestionFilterForm(request.POST, user=user) + qpaper_form = QuestionPaperForm(request.POST, instance=question_paper) + question_type = request.POST.get('question_type', None) + marks = request.POST.get('marks', None) + state = request.POST.get('is_active', None) + + if 'add-fixed' in request.POST: + question_ids = request.POST.getlist('questions', None) + for question in Question.objects.filter(id__in=question_ids): + question_paper.fixed_questions.add(question) + + if 'remove-fixed' in request.POST: + question_ids = request.POST.getlist('added-questions', None) + question_paper.fixed_questions.remove(*question_ids) + + if 'add-random' in request.POST: + question_ids = request.POST.getlist('random_questions', None) + num_of_questions = request.POST.get('num_of_questions', 1) + if question_ids and marks: + random_set = QuestionSet(marks=marks, num_questions=num_of_questions) + random_set.save() + for question in Question.objects.filter(id__in=question_ids): + random_set.questions.add(question) + question_paper.random_questions.add(random_set) + + if 'remove-random' in request.POST: + random_set_ids = request.POST.getlist('random_sets', None) + question_paper.random_questions.remove(*random_set_ids) + + if 'save' in request.POST or 'back' in request.POST: + qpaper_form.save() + return my_redirect('/exam/manage/courses/') + + if marks: + questions = _get_questions(user, question_type, marks) + questions = _remove_already_present(questionpaper_id, questions) + + question_paper.update_total_marks() + question_paper.save() + random_sets = question_paper.random_questions.all() + fixed_questions = question_paper.fixed_questions.all() + context = {'qpaper_form': qpaper_form, 'filter_form': filter_form, 'qpaper': + question_paper, 'questions': questions, 'fixed_questions': fixed_questions, + 'state': state, 'random_sets': random_sets} + return my_render_to_response('yaksh/design_questionpaper.html', context, + context_instance=RequestContext(request)) + + @login_required def show_all_questions(request): """Show a list of all the questions currently in the database.""" @@ -827,12 +913,10 @@ def show_all_questions(request): if request.POST.get('delete') == 'delete': data = request.POST.getlist('question') if data is not None: - questions = Question.objects.filter(id__in=data, user_id=user.id) - files = FileUpload.objects.filter(question_id__in=questions) - if files: - for file in files: - file.remove() - questions.delete() + questions = Question.objects.filter(id__in=data, user_id=user.id, active=True) + for question in questions: + question.active = False + question.save() if request.POST.get('upload') == 'upload': form = UploadFileForm(request.POST, request.FILES) @@ -841,8 +925,8 @@ def show_all_questions(request): file_name = questions_file.name.split('.') if file_name[-1] == "zip": ques = Question() - extract_files(questions_file) - ques.read_json("questions_dump.json", user) + files, extract_path = extract_files(questions_file) + ques.read_json(extract_path, user, files) else: message = "Please Upload a ZIP file" context['message'] = message @@ -871,7 +955,7 @@ def show_all_questions(request): else: context["msg"] = "Please select atleast one question to test" - questions = Question.objects.filter(user_id=user.id) + questions = Question.objects.filter(user_id=user.id, active=True) form = QuestionFilterForm(user=user) upload_form = UploadFileForm() context['papers'] = [] @@ -999,80 +1083,6 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None): ) -@csrf_exempt -def ajax_questionpaper(request, query): - """ - During question paper creation, ajax call made to get question details. - """ - - user = request.user - if query == 'marks': - question_type = request.POST.get('question_type') - questions = Question.objects.filter(type=question_type, user=user) - marks = questions.values_list('points').distinct() - return my_render_to_response('yaksh/ajax_marks.html', {'marks': marks}) - elif query == 'questions': - question_type = request.POST['question_type'] - marks_selected = request.POST['marks'] - fixed_questions = request.POST.getlist('fixed_list[]') - fixed_question_list = ",".join(fixed_questions).split(',') - random_questions = request.POST.getlist('random_list[]') - random_question_list = ",".join(random_questions).split(',') - question_list = fixed_question_list + random_question_list - questions = list(Question.objects.filter(type=question_type, - points=marks_selected, user=user)) - questions = [question for question in questions \ - if not str(question.id) in question_list] - return my_render_to_response('yaksh/ajax_questions.html', - {'questions': questions}) - - -@login_required -def design_questionpaper(request): - user = request.user - ci = RequestContext(request) - - if not is_moderator(user): - raise Http404('You are not allowed to view this page!') - - if request.method == 'POST': - fixed_questions = request.POST.getlist('fixed') - random_questions = request.POST.getlist('random') - random_number = request.POST.getlist('number') - is_shuffle = request.POST.get('shuffle_questions', False) - if is_shuffle == 'on': - is_shuffle = True - - question_paper = QuestionPaper(shuffle_questions=is_shuffle) - quiz = Quiz.objects.order_by("-id")[0] - tot_marks = 0 - question_paper.quiz = quiz - question_paper.total_marks = tot_marks - question_paper.save() - if fixed_questions: - fixed_questions_ids = ",".join(fixed_questions) - fixed_questions_ids_list = fixed_questions_ids.split(',') - for question_id in fixed_questions_ids_list: - question_paper.fixed_questions.add(question_id) - if random_questions: - for random_question, num in zip(random_questions, random_number): - qid = random_question.split(',')[0] - question = Question.objects.get(id=int(qid)) - marks = question.points - question_set = QuestionSet(marks=marks, num_questions=num) - question_set.save() - for question_id in random_question.split(','): - question_set.questions.add(question_id) - question_paper.random_questions.add(question_set) - question_paper.update_total_marks() - question_paper.save() - return my_redirect('/exam/manage/courses') - else: - form = RandomQuestionForm() - context = {'form': form, 'questionpaper':True} - return my_render_to_response('yaksh/design_questionpaper.html', - context, context_instance=ci) - @login_required def view_profile(request): """ view moderators and users profile """ @@ -1151,7 +1161,6 @@ def search_teacher(request, course_id): Q(id=course.creator.id)) context['success'] = True context['teachers'] = teachers - return my_render_to_response('yaksh/addteacher.html', context, context_instance=ci) @@ -1180,7 +1189,6 @@ def add_teacher(request, course_id): course.add_teachers(*teachers) context['status'] = True context['teachers_added'] = teachers - return my_render_to_response('yaksh/addteacher.html', context, context_instance=ci) @@ -1188,7 +1196,7 @@ def add_teacher(request, course_id): @login_required def remove_teachers(request, course_id): """ remove user from a course """ - + user = request.user course = get_object_or_404(Course, pk=course_id) if not is_moderator(user) and (user != course.creator and user not in course.teachers.all()): |