diff options
author | Prabhu Ramachandran | 2014-07-03 18:06:26 +0530 |
---|---|---|
committer | Prabhu Ramachandran | 2014-07-03 18:06:26 +0530 |
commit | befad018f2e59389e92eaa109c6968bea30d06f9 (patch) | |
tree | df02d9de7fdd1596c0be1268bdd9148a2f0f15cb | |
parent | ba6308eb5dfe391305f5466fba00be46a4755f7e (diff) | |
parent | 2d04f8cce5c5cf610b401ed37f796bd2034c07e9 (diff) | |
download | online_test-befad018f2e59389e92eaa109c6968bea30d06f9.tar.gz online_test-befad018f2e59389e92eaa109c6968bea30d06f9.tar.bz2 online_test-befad018f2e59389e92eaa109c6968bea30d06f9.zip |
Merge pull request #32 from prathamesh920/question_paper_creation_interface
Question paper creation interface
-rw-r--r-- | testapp/exam/forms.py | 12 | ||||
-rw-r--r-- | testapp/exam/urls.py | 11 | ||||
-rw-r--r-- | testapp/exam/views.py | 137 | ||||
-rw-r--r-- | testapp/static/exam/css/base.css | 253 | ||||
-rw-r--r-- | testapp/static/exam/css/question_paper_creation.css | 119 | ||||
-rw-r--r-- | testapp/static/exam/js/bootstrap-modal.js | 260 | ||||
-rw-r--r-- | testapp/static/exam/js/bootstrap-tabs.js | 80 | ||||
-rw-r--r-- | testapp/static/exam/js/question_paper_creation.js | 237 | ||||
-rw-r--r-- | testapp/templates/exam/ajax_marks.html | 4 | ||||
-rw-r--r-- | testapp/templates/exam/ajax_questions.html | 31 | ||||
-rw-r--r-- | testapp/templates/exam/design_questionpaper.html | 184 | ||||
-rw-r--r-- | testapp/templates/exam/monitor.html | 4 |
12 files changed, 1270 insertions, 62 deletions
diff --git a/testapp/exam/forms.py b/testapp/exam/forms.py index d0b0b74..9bfedbe 100644 --- a/testapp/exam/forms.py +++ b/testapp/exam/forms.py @@ -13,7 +13,7 @@ from string import letters, punctuation, digits import datetime languages = ( - ("select", "Select"), + ("select", "Select Language"), ("python", "Python"), ("bash", "Bash"), ("C", "C Language"), @@ -23,7 +23,7 @@ languages = ( ) question_types = ( - ("select", "Select"), + ("select", "Select Question Type"), ("mcq", "Multiple Choice"), ("mcc", "Multiple Correct Choices"), ("code", "Code"), @@ -207,3 +207,11 @@ class QuestionForm(forms.Form): new_question.active = active new_question.snippet = snippet new_question.save() + + +class RandomQuestionForm(forms.Form): + question_type = forms.CharField(max_length=8, widget=forms.Select\ + (choices=question_types)) + marks = forms.CharField(max_length=8, widget=forms.Select\ + (choices=(('select', 'Select Marks'),))) + shuffle_questions = forms.BooleanField(required=False) diff --git a/testapp/exam/urls.py b/testapp/exam/urls.py index 33b2edf..b659cf6 100644 --- a/testapp/exam/urls.py +++ b/testapp/exam/urls.py @@ -9,7 +9,7 @@ urlpatterns = patterns('exam.views', url(r'^start/(?P<questionpaper_id>\d+)/$','start'), url(r'^quit/(?P<questionpaper_id>\d+)/$', 'quit'), url(r'^intro/(?P<questionpaper_id>\d+)/$','intro'), - url(r'^complete/$', 'complete'), + url(r'^complete/$', 'complete'), url(r'^complete/(?P<questionpaper_id>\d+)/$', 'complete'), url(r'^register/$', 'user_register'), url(r'^(?P<q_id>\d+)/$', 'question'), @@ -19,7 +19,7 @@ urlpatterns = patterns('exam.views', url(r'^manage/$', 'prof_manage'), url(r'^manage/addquestion/$', 'add_question'), - url(r'^manage/addquestion/(?P<question_id>\d+)/$', 'add_question'), + url(r'^manage/addquestion/(?P<question_id>\d+)/$', 'add_question'), url(r'^manage/addquiz/$', 'add_quiz'), url(r'^manage/editquiz/$', 'edit_quiz'), url(r'^manage/editquestion/$', 'edit_question'), @@ -27,12 +27,12 @@ urlpatterns = patterns('exam.views', url(r'^manage/gradeuser/$', 'show_all_users'), url(r'^manage/gradeuser/(?P<username>[a-zA-Z0-9_.]+)/$', 'grade_user'), url(r'^manage/questions/$', 'show_all_questions'), - url(r'^manage/showquiz/$','show_all_quiz'), + url(r'^manage/showquiz/$','show_all_quiz'), url(r'^manage/monitor/$', 'monitor'), - url(r'^manage/showquestionpapers/$','show_all_questionpapers'), + url(r'^manage/showquestionpapers/$','show_all_questionpapers'), url(r'^manage/showquestionpapers/(?P<questionpaper_id>\d+)/$',\ 'show_all_questionpapers'), - url(r'^manage/monitor/(?P<questionpaper_id>\d+)/$', 'monitor'), + url(r'^manage/monitor/(?P<questionpaper_id>\d+)/$', 'monitor'), url(r'^manage/user_data/(?P<username>[a-zA-Z0-9_.]+)/$','user_data'), url(r'^manage/designquestionpaper/$','design_questionpaper'), url(r'^manage/designquestionpaper/(?P<questionpaper_id>\d+)/$',\ @@ -43,4 +43,5 @@ urlpatterns = patterns('exam.views', url(r'^manage/designquestionpaper/manual$','manual_questionpaper'), url(r'^manage/designquestionpaper/manual/(?P<questionpaper_id>\d+)/$',\ 'manual_questionpaper'), + url(r'^ajax/questionpaper/(?P<query>.+)/$', 'ajax_questionpaper'), ) diff --git a/testapp/exam/views.py b/testapp/exam/views.py index 6c29107..38beb0d 100644 --- a/testapp/exam/views.py +++ b/testapp/exam/views.py @@ -11,12 +11,14 @@ from django.shortcuts import render_to_response, get_object_or_404, redirect from django.template import RequestContext from django.http import Http404 from django.db.models import Sum +from django.views.decorators.csrf import csrf_exempt from taggit.models import Tag from itertools import chain # Local imports. -from exam.models import Quiz, Question, QuestionPaper +from exam.models import Quiz, Question, QuestionPaper, QuestionSet from exam.models import Profile, Answer, AnswerPaper, User -from exam.forms import UserRegisterForm, UserLoginForm, QuizForm, QuestionForm +from exam.forms import UserRegisterForm, UserLoginForm, QuizForm,\ + QuestionForm, RandomQuestionForm from exam.xmlrpc_clients import code_server from settings import URL_ROOT @@ -131,32 +133,32 @@ def user_register(request): context_instance=ci) -def quizlist_user(request): - """Show All Quizzes that is available to logged-in user.""" - user = request.user - avail_quizzes = list(QuestionPaper.objects.filter(quiz__active=True)) - user_answerpapers = AnswerPaper.objects.filter(user=user) - quizzes_taken = [] +def quizlist_user(request): + """Show All Quizzes that is available to logged-in user.""" + user = request.user + avail_quizzes = list(QuestionPaper.objects.filter(quiz__active=True)) + user_answerpapers = AnswerPaper.objects.filter(user=user) + quizzes_taken = [] pre_requisites = [] - context = {} - + context = {} + if 'cannot_attempt' in request.GET: context['cannot_attempt'] = True - - if user_answerpapers.count() == 0: + + if user_answerpapers.count() == 0: context['quizzes'] = avail_quizzes - context['user'] = user + context['user'] = user context['quizzes_taken'] = None - return my_render_to_response("exam/quizzes_user.html", context) - - for answer_paper in user_answerpapers: - for quiz in avail_quizzes: + return my_render_to_response("exam/quizzes_user.html", context) + + for answer_paper in user_answerpapers: + for quiz in avail_quizzes: if answer_paper.question_paper.id == quiz.id and \ - answer_paper.end_time != answer_paper.start_time: + answer_paper.end_time != answer_paper.start_time: avail_quizzes.remove(quiz) - quizzes_taken.append(answer_paper) - + quizzes_taken.append(answer_paper) + context['quizzes'] = avail_quizzes context['user'] = user context['quizzes_taken'] = quizzes_taken @@ -181,7 +183,7 @@ def intro(request, questionpaper_id): else: context = {'user': user, 'cannot_attempt':True} return my_redirect("/exam/quizzes/?cannot_attempt=True") - + except: context = {'user': user, 'cannot_attempt':True} return my_redirect("/exam/quizzes/?cannot_attempt=True") @@ -197,7 +199,7 @@ def results_user(request): papers = AnswerPaper.objects.filter(user=user) quiz_marks = [] for paper in papers: - marks_obtained = paper.update_marks_obtained() + marks_obtained = paper.marks_obtained max_marks = paper.question_paper.total_marks percentage = round((marks_obtained/max_marks)*100, 2) temp = paper.question_paper.quiz.description, marks_obtained,\ @@ -249,6 +251,7 @@ def edit_question(request): options = request.POST.getlist('options') type = request.POST.getlist('type') active = request.POST.getlist('active') + language = request.POST.getlist('language') snippet = request.POST.getlist('snippet') for j, question_id in enumerate(question_list): question = Question.objects.get(id=question_id) @@ -258,6 +261,7 @@ def edit_question(request): question.test = test[j] question.options = options[j] question.active = active[j] + question.language = language[j] question.snippet = snippet[j] question.type = type[j] question.save() @@ -292,6 +296,7 @@ def add_question(request, question_id=None): d.options = form['options'].data d.type = form['type'].data d.active = form['active'].data + d.language = form['language'].data d.snippet = form['snippet'].data d.save() question = Question.objects.get(id=question_id) @@ -322,6 +327,7 @@ def add_question(request, question_id=None): form.initial['options'] = d.options form.initial['type'] = d.type form.initial['active'] = d.active + form.initial['language'] = d.language form.initial['snippet'] = d.snippet form_tags = d.tags.all() form_tags_split = form_tags.values('name') @@ -389,15 +395,6 @@ def add_quiz(request, quiz_id=None): context_instance=ci) -def design_questionpaper(request, questionpaper_id=None): - user = request.user - ci = RequestContext(request) - if not user.is_authenticated() or not is_moderator(user): - raise Http404('You are not allowed to view this page!') - return my_render_to_response('exam/add_questionpaper.html', {}, - context_instance=ci) - - def show_all_questionpapers(request, questionpaper_id=None): user = request.user ci = RequestContext(request) @@ -842,7 +839,11 @@ def complete(request, reason=None, questionpaper_id=None): else: q_paper = QuestionPaper.objects.get(id=questionpaper_id) paper = AnswerPaper.objects.get(user=user, question_paper=q_paper) - obt_marks = paper.update_marks_obtained() + paper.update_marks_obtained() + paper.update_percent() + paper.update_passed() + paper.save() + obt_marks = paper.marks_obtained tot_marks = paper.question_paper.total_marks if obt_marks == paper.question_paper.total_marks: context = {'message': "Hurray ! You did an excellent job.\ @@ -1029,6 +1030,7 @@ def show_all_questions(request): form.initial['options'] = d.options form.initial['type'] = d.type form.initial['active'] = d.active + form.initial['language'] = d.language form.initial['snippet'] = d.snippet form_tags = d.tags.all() form_tags_split = form_tags.values('name') @@ -1094,3 +1096,74 @@ def grade_user(request, username): context = {'data': data} return my_render_to_response('exam/grade_user.html', context, context_instance=ci) + + +@csrf_exempt +def ajax_questionpaper(request, query): + """ + During question paper creation, ajax call made to get question details. + """ + if query == 'marks': + question_type = request.POST.get('question_type') + questions = Question.objects.filter(type=question_type) + marks = questions.values_list('points').distinct() + return my_render_to_response('exam/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)) + questions = [ question for question in questions \ + if not str(question.id) in question_list ] + return my_render_to_response('exam/ajax_questions.html', + {'questions': questions}) + + +def design_questionpaper(request): + user = request.user + ci = RequestContext(request) + + if not user.is_authenticated() or 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): + question = Question.objects.get(id=random_question[0]) + 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/showquiz') + else: + form = RandomQuestionForm() + context = {'form': form} + return my_render_to_response('exam/design_questionpaper.html', + context, context_instance=ci) diff --git a/testapp/static/exam/css/base.css b/testapp/static/exam/css/base.css index c822f4d..051ba22 100644 --- a/testapp/static/exam/css/base.css +++ b/testapp/static/exam/css/base.css @@ -295,7 +295,6 @@ a:hover { width: 160px; } .span4 { - min-height : 500px; width: 220px; } .span5 { @@ -1898,26 +1897,6 @@ button.btn::-moz-focus-inner, input[type=submit].btn::-moz-focus-inner { padding: 0; border: 0; } -.close { - float: right; - color: #000000; - font-size: 20px; - font-weight: bold; - line-height: 13.5px; - text-shadow: 0 1px 0 #ffffff; - filter: alpha(opacity=25); - -khtml-opacity: 0.25; - -moz-opacity: 0.25; - opacity: 0.25; -} -.close:hover { - color: #000000; - text-decoration: none; - filter: alpha(opacity=40); - -khtml-opacity: 0.4; - -moz-opacity: 0.4; - opacity: 0.4; -} .alert-message { position: relative; padding: 7px 15px; @@ -2045,4 +2024,236 @@ button.btn::-moz-focus-inner, input[type=submit].btn::-moz-focus-inner { .label.notice { background-color: #62cffc; } +.well { + background-color: #f5f5f5; + margin-bottom: 20px; + padding: 19px; + min-height: 20px; + border: 1px solid #eee; + border: 1px solid rgba(0, 0, 0, 0.05); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} +.modal-backdrop { + background-color: #000000; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 10000; +} +.modal-backdrop.fade { + opacity: 0; +} +.modal-backdrop, .modal-backdrop.fade.in { + filter: alpha(opacity=80); + -khtml-opacity: 0.8; + -moz-opacity: 0.8; + opacity: 0.8; +} +.modal { + position: fixed; + top: 10%; + left: 50%; + z-index: 1050; + width: 560px; + margin-left: -280px; + background-color: #ffffff; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, 0.3); + *border: 1px solid #999; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + outline: none; + -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); + -webkit-background-clip: padding-box; + -moz-background-clip: padding-box; + background-clip: padding-box; +} +.modal.fade { + top: -25%; + -webkit-transition: opacity 0.3s linear, top 0.3s ease-out; + -moz-transition: opacity 0.3s linear, top 0.3s ease-out; + -o-transition: opacity 0.3s linear, top 0.3s ease-out; + transition: opacity 0.3s linear, top 0.3s ease-out; +} + +.modal.fade.in { + top: 10%; +} + +.modal-header { + padding: 9px 15px; + border-bottom: 1px solid #eee; +} + +.modal-header .close { + margin-top: 2px; +} + +.modal-header h3 { + margin: 0; + line-height: 30px; +} + +.modal-body { + position: relative; + max-height: 400px; + padding: 15px; + overflow-y: auto; +} + +.modal-form { + margin-bottom: 0; +} + +.modal-footer { + padding: 14px 15px 15px; + margin-bottom: 0; + text-align: right; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + -webkit-border-radius: 0 0 6px 6px; + -moz-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; + *zoom: 1; + -webkit-box-shadow: inset 0 1px 0 #ffffff; + -moz-box-shadow: inset 0 1px 0 #ffffff; + box-shadow: inset 0 1px 0 #ffffff; +} + +.modal-footer:before, +.modal-footer:after { + display: table; + line-height: 0; + content: ""; +} +.modal-footer:after { + clear: both; +} + +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} + +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} + +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.tabs, .pills { + margin: 0 0 18px; + padding: 0; + list-style: none; + zoom: 1; +} +.tabs:before, +.pills:before, +.tabs:after, +.pills:after { + display: table; + content: ""; + zoom: 1; +} +.tabs:after, .pills:after { + clear: both; +} +.tabs > li, .pills > li { + float: left; +} +.tabs > li > a, .pills > li > a { + display: block; +} +.tabs { + border-color: #ddd; + border-style: solid; + border-width: 0 0 1px; +} +.tabs > li { + position: relative; + margin-bottom: -1px; +} +.tabs > li > a { + padding: 0 15px; + margin-right: 2px; + line-height: 23px; + border: 1px solid transparent; + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} +.tabs > li > a:hover { + text-decoration: none; + background-color: #eee; + border-color: #eee #eee #ddd; +} +.tabs .active > a, .tabs .active > a:hover { + color: #808080; + background-color: #ffffff; + border: 1px solid #ddd; + border-bottom-color: transparent; + cursor: default; +} +.tabs .menu-dropdown, .tabs .dropdown-menu { + top: 35px; + border-width: 1px; + -webkit-border-radius: 0 6px 6px 6px; + -moz-border-radius: 0 6px 6px 6px; + border-radius: 0 6px 6px 6px; +} +.tabs a.menu:after, .tabs .dropdown-toggle:after { + border-top-color: #999; + margin-top: 15px; + margin-left: 5px; +} +.tabs li.open.menu .menu, .tabs .open.dropdown .dropdown-toggle { + border-color: #999; +} +.tabs li.open a.menu:after, .tabs .dropdown.open .dropdown-toggle:after { + border-top-color: #555; +} +.pills a { + margin: 5px 3px 5px 0; + padding: 0 15px; + line-height: 30px; + text-shadow: 0 1px 1px #ffffff; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; + border-radius: 15px; +} +.pills a:hover { + color: #ffffff; + text-decoration: none; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); + background-color: #00438a; +} +.pills .active a { + color: #ffffff; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); + background-color: #0069d6; +} +.pills-vertical > li { + float: none; +} +.tab-content > .tab-pane, .pill-content > .pill-pane { + display: none; +} +.tab-content > .active, .pill-content > .active { + display: block; +} diff --git a/testapp/static/exam/css/question_paper_creation.css b/testapp/static/exam/css/question_paper_creation.css new file mode 100644 index 0000000..c915320 --- /dev/null +++ b/testapp/static/exam/css/question_paper_creation.css @@ -0,0 +1,119 @@ +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 18px; + color: #404040; +} +.clearfix { + clear: both; +} +.tabs li { + text-align: center; + width: 33%; +} +.tabs li:last-child { + width: 34%; +} +.tabs > .active > a { + border: 0; + background: lightgreen; +} +.tabs > .active > a:hover { + border: 0; + background: green; + color: #ffffff; +} +.tabs li a { + border-radius: 0; + margin-right: 0; +} +.tabs { + border: 1px solid #ddd; +} +#progress { + background: red; +} +#content-left{ + text-align: center; + background: grey; +} +#content-right{ + text-align: center; + background: grey; +} +#selectors { + margin-left: 0; + background: #fafafa; + padding: 7px 0; + border: 2px solid #f5f5f5; +} +#selectors .span4 { + margin-left: 0; +} +#id_question_type { + width: 100%; +} +#id_marks { + width: 100%; +} +#fixed-questions .span7 > div, +#random-questions .span7 > div{ + background: #f5f5f5; + height: 200px; + border: 1px solid #333333; + padding: 5px; +} +#fixed-available, +#random-available { + height: 125px; + min-height: 125px; + overflow-y: scroll; + margin-bottom: 15px; +} +#fixed-added, +#random-added { + height: 160px; + overflow-y: scroll; +} +#fixed-added hr, +#random-added hr { + margin: 5px 0 4px; +} +.qcard { + position: relative; + background: #ffffff; + padding: 5px; + margin: 5px 5px; + box-shadow: 1px 1px 5px #cccccc; + -webkit-box-shadow: 1px 1px 5px #cccccc; + -moz-box-shadow: 1px 1px 5px #cccccc; + -o-box-shadow: 1px 1px 5px #cccccc; +} +.qcard ul { + margin-bottom: 5px; +} +.qcard .remove { + position: absolute; + + top: 3px; + right: 3px; + padding: 1px 3px; + text-decoration: none; + color: #ffffff; + background: #ff4136; + border-radius: 3px; + font-weight: bold; +} +.qcard .remove:hover { + background: #333333; +} +.red-alert { + border: 2px solid red; +} +#myModal .qcard .remove{ + display: none; +} +.well{ + padding: 5px; +} diff --git a/testapp/static/exam/js/bootstrap-modal.js b/testapp/static/exam/js/bootstrap-modal.js new file mode 100644 index 0000000..b328217 --- /dev/null +++ b/testapp/static/exam/js/bootstrap-modal.js @@ -0,0 +1,260 @@ +/* ========================================================= + * bootstrap-modal.js v1.4.0 + * http://twitter.github.com/bootstrap/javascript.html#modal + * ========================================================= + * Copyright 2011 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +!function( $ ){ + + "use strict" + + /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) + * ======================================================= */ + + var transitionEnd + + $(document).ready(function () { + + $.support.transition = (function () { + var thisBody = document.body || document.documentElement + , thisStyle = thisBody.style + , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined + return support + })() + + // set CSS transition event type + if ( $.support.transition ) { + transitionEnd = "TransitionEnd" + if ( $.browser.webkit ) { + transitionEnd = "webkitTransitionEnd" + } else if ( $.browser.mozilla ) { + transitionEnd = "transitionend" + } else if ( $.browser.opera ) { + transitionEnd = "oTransitionEnd" + } + } + + }) + + + /* MODAL PUBLIC CLASS DEFINITION + * ============================= */ + + var Modal = function ( content, options ) { + this.settings = $.extend({}, $.fn.modal.defaults, options) + this.$element = $(content) + .delegate('.close', 'click.modal', $.proxy(this.hide, this)) + + if ( this.settings.show ) { + this.show() + } + + return this + } + + Modal.prototype = { + + toggle: function () { + return this[!this.isShown ? 'show' : 'hide']() + } + + , show: function () { + var that = this + this.isShown = true + this.$element.trigger('show') + + escape.call(this) + backdrop.call(this, function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + that.$element + .appendTo(document.body) + .show() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + transition ? + that.$element.one(transitionEnd, function () { that.$element.trigger('shown') }) : + that.$element.trigger('shown') + + }) + + return this + } + + , hide: function (e) { + e && e.preventDefault() + + if ( !this.isShown ) { + return this + } + + var that = this + this.isShown = false + + escape.call(this) + + this.$element + .trigger('hide') + .removeClass('in') + + $.support.transition && this.$element.hasClass('fade') ? + hideWithTransition.call(this) : + hideModal.call(this) + + return this + } + + } + + + /* MODAL PRIVATE METHODS + * ===================== */ + + function hideWithTransition() { + // firefox drops transitionEnd events :{o + var that = this + , timeout = setTimeout(function () { + that.$element.unbind(transitionEnd) + hideModal.call(that) + }, 500) + + this.$element.one(transitionEnd, function () { + clearTimeout(timeout) + hideModal.call(that) + }) + } + + function hideModal (that) { + this.$element + .hide() + .trigger('hidden') + + backdrop.call(this) + } + + function backdrop ( callback ) { + var that = this + , animate = this.$element.hasClass('fade') ? 'fade' : '' + if ( this.isShown && this.settings.backdrop ) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') + .appendTo(document.body) + + if ( this.settings.backdrop != 'static' ) { + this.$backdrop.click($.proxy(this.hide, this)) + } + + if ( doAnimate ) { + this.$backdrop[0].offsetWidth // force reflow + } + + this.$backdrop.addClass('in') + + doAnimate ? + this.$backdrop.one(transitionEnd, callback) : + callback() + + } else if ( !this.isShown && this.$backdrop ) { + this.$backdrop.removeClass('in') + + $.support.transition && this.$element.hasClass('fade')? + this.$backdrop.one(transitionEnd, $.proxy(removeBackdrop, this)) : + removeBackdrop.call(this) + + } else if ( callback ) { + callback() + } + } + + function removeBackdrop() { + this.$backdrop.remove() + this.$backdrop = null + } + + function escape() { + var that = this + if ( this.isShown && this.settings.keyboard ) { + $(document).bind('keyup.modal', function ( e ) { + if ( e.which == 27 ) { + that.hide() + } + }) + } else if ( !this.isShown ) { + $(document).unbind('keyup.modal') + } + } + + + /* MODAL PLUGIN DEFINITION + * ======================= */ + + $.fn.modal = function ( options ) { + var modal = this.data('modal') + + if (!modal) { + + if (typeof options == 'string') { + options = { + show: /show|toggle/.test(options) + } + } + + return this.each(function () { + $(this).data('modal', new Modal(this, options)) + }) + } + + if ( options === true ) { + return modal + } + + if ( typeof options == 'string' ) { + modal[options]() + } else if ( modal ) { + modal.toggle() + } + + return this + } + + $.fn.modal.Modal = Modal + + $.fn.modal.defaults = { + backdrop: false + , keyboard: false + , show: false + } + + + /* MODAL DATA- IMPLEMENTATION + * ========================== */ + + $(document).ready(function () { + $('body').delegate('[data-controls-modal]', 'click', function (e) { + e.preventDefault() + var $this = $(this).data('show', true) + $('#' + $this.attr('data-controls-modal')).modal( $this.data() ) + }) + }) + +}( window.jQuery || window.ender ); diff --git a/testapp/static/exam/js/bootstrap-tabs.js b/testapp/static/exam/js/bootstrap-tabs.js new file mode 100644 index 0000000..a3c7ee1 --- /dev/null +++ b/testapp/static/exam/js/bootstrap-tabs.js @@ -0,0 +1,80 @@ +/* ======================================================== + * bootstrap-tabs.js v1.4.0 + * http://twitter.github.com/bootstrap/javascript.html#tabs + * ======================================================== + * Copyright 2011 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================== */ + + +!function( $ ){ + + "use strict" + + function activate ( element, container ) { + container + .find('> .active') + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + + element.addClass('active') + + if ( element.parent('.dropdown-menu') ) { + element.closest('li.dropdown').addClass('active') + } + } + + function tab( e ) { + var $this = $(this) + , $ul = $this.closest('ul:not(.dropdown-menu)') + , href = $this.attr('href') + , previous + , $href + + if ( /^#\w+/.test(href) ) { + e.preventDefault() + + if ( $this.parent('li').hasClass('active') ) { + return + } + + previous = $ul.find('.active a').last()[0] + $href = $(href) + + activate($this.parent('li'), $ul) + activate($href, $href.parent()) + + $this.trigger({ + type: 'change' + , relatedTarget: previous + }) + } + } + + + /* TABS/PILLS PLUGIN DEFINITION + * ============================ */ + + $.fn.tabs = $.fn.pills = function ( selector ) { + return this.each(function () { + $(this).delegate(selector || '.tabs li > a, .pills > li > a', 'click', tab) + }) + } + + $(document).ready(function () { + $('body').tabs('ul[data-tabs] li > a, ul[data-pills] > li > a') + }) + +}( window.jQuery || window.ender ); diff --git a/testapp/static/exam/js/question_paper_creation.js b/testapp/static/exam/js/question_paper_creation.js new file mode 100644 index 0000000..a144540 --- /dev/null +++ b/testapp/static/exam/js/question_paper_creation.js @@ -0,0 +1,237 @@ +$(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"); + /* 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); + } + }); + }); + + $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(); + }); + + /* showing/hiding selectors on tab click */ + $(".tabs li").click(function() { + if($(this).attr("id") == "finish-tab") { + $("#selectors").hide(); + } 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'); + }); + } + else { + $("#random-available input:checkbox").each(function(index, element) { + $(this).attr('checked','checked'); + }); + } + } + 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"); + } + else { + $('#modal_body').html(questions); + } + $("#myModal").modal('show'); + }); + + /* tab change on next or previous button click */ + $("#fixed-next").click(function(){ + $("#random").click(); + }); + $("#random-next").click(function(){ + $("#finished").click(); + }); + + $("#random-prev").click(function(){ + $("#fixed").click(); + }); + + $("#finish-prev").click(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 diff --git a/testapp/templates/exam/ajax_marks.html b/testapp/templates/exam/ajax_marks.html new file mode 100644 index 0000000..716bb88 --- /dev/null +++ b/testapp/templates/exam/ajax_marks.html @@ -0,0 +1,4 @@ +<option value='select'>Select Marks</option> +{% for mark in marks %} +<option value="{{ mark.0 }}"> {{ mark.0 }} </option> +{% endfor %} diff --git a/testapp/templates/exam/ajax_questions.html b/testapp/templates/exam/ajax_questions.html new file mode 100644 index 0000000..e343f9b --- /dev/null +++ b/testapp/templates/exam/ajax_questions.html @@ -0,0 +1,31 @@ +<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/testapp/templates/exam/design_questionpaper.html b/testapp/templates/exam/design_questionpaper.html new file mode 100644 index 0000000..8994148 --- /dev/null +++ b/testapp/templates/exam/design_questionpaper.html @@ -0,0 +1,184 @@ +{% extends "manage.html" %} + +{% block subtitle %}Design Question Paper{% endblock %} + +{% block css %} +<link rel="stylesheet" href="{{ URL_ROOT }}/static/exam/css/base.css" type="text/css" /> +<link rel="stylesheet" href="{{ URL_ROOT }}/static/exam/css/question_quiz.css" type="text/css" /> +<link rel="stylesheet" media="all" type="text/css" href="{{ URL_ROOT }}/static/exam/css/autotaggit.css" /> +<link rel="stylesheet" media="all" type="text/css" href="{{ URL_ROOT }}/static/exam/css/question_paper_creation.css" /> +<style> +select +{ + width:auto; +} +</style> +{% endblock %} +{% block script %} +<script src="/static/taggit_autocomplete_modified/jquery.min.js" type="text/javascript"></script> +<script src="/static/taggit_autocomplete_modified/jquery.autocomplete.js" type="text/javascript"></script> + + +<script src="{{ URL_ROOT }}/static/exam/js/bootstrap-tabs.js"></script> +<script src="{{ URL_ROOT }}/static/exam/js/add_questionpaper.js"></script> +<script src="{{ URL_ROOT }}/static/exam/js/question_paper_creation.js"></script> +<script src="{{ URL_ROOT }}/static/exam/js/bootstrap-modal.js"></script> +{% endblock %} + +{% block manage %} +<input type=hidden id="url_root" value={{ URL_ROOT }}> +<center><b>Manual mode to design the {{lang}} Question Paper</center><br> + <ul class="tabs" data-tabs="tabs"> + <li class="active" id="fixed-tab"> + <a href="#fixed-questions" id="fixed"> + STEP 1<br> + Add Fixed Questions + </a> + </li> + <li id="random-tab"> + <a href="#random-questions" id="random"> + STEP 2<br> + Add Random Questions + </a> + </li> + <li id="finish-tab"> + <a href="#finish" id="finished"> + STEP 3<br> + Finish + </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> + </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> <!-- /.row --> + <br><br> + + + <div class="tab-pane active" id="fixed-questions"> + <div class="row"> + <div class="span7"> + <div id="fixed-available-wrapper"> + <p><u>Select questions to add:</u></p> + <div id="fixed-available"> + </div> + <a id="add-fixed" class="btn small primary pull-right" href="#">Add to paper</a> + </div> + </div> + <div class="span7"> + <div id="fixed-added-wrapper"> + <p><u>Fixed questions currently in paper:</u></p> + <div id="fixed-added"> + </div> + </div> + </div> + </div> <!-- /.row --> + <br> + <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 id="random-available-wrapper"> + <p><u>Select questions to add to the pool:</u></p> + <div id="random-available"> + </div> + <a id="add-random" class="btn small primary pull-right" href="#">Add to paper</a> + </div> + </div> + <div class="span7"> + <div id="random-added-wrapper"> + <p><u>Pool of questions currently in paper:</u></p> + <div id="random-added"> + </div> + </div> + </div> + </div> <!-- /.row --> + <br> + <div class="pull-left"> + <a class="btn" id="random-prev">< Previous</a> + </div> + <div class="pull-right"> + <a class="btn" id="random-next">Next ></a> + </div> + </div> <!-- /#random-questions --> + + <div class="tab-pane" id="finish"> + <center> + <h5>Almost finished creating your question paper</h5> + <label style="float: none;"> + {{ 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"> + <br> + <div class="pull-left"> + <a class="btn" id="finish-prev">< Previous</a> + </div> + </center> + </div> <!-- /#finish --> +</div> +<!-- /.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 id = "modal_body"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() + }) +</script> +{% endblock %} diff --git a/testapp/templates/exam/monitor.html b/testapp/templates/exam/monitor.html index aa8b678..ecdb852 100644 --- a/testapp/templates/exam/monitor.html +++ b/testapp/templates/exam/monitor.html @@ -47,7 +47,7 @@ <th> Roll number </th> <th> Institute </th> <th> Questions answered </th> - <th> Total marks </th> + <th> Marks obtained </th> <th> Attempts </th> </tr> {% for paper in papers %} @@ -57,7 +57,7 @@ <td> {{ paper.profile.roll_number }} </td> <td> {{ paper.profile.institute }} </td> <td> {{ paper.get_answered_str }} </td> - <td> {{ paper.get_total_marks }} </td> + <td> {{ paper.marks_obtained }} </td> <td> {{ paper.answers.count }} </td> </tr> {% endfor %} |