diff options
Diffstat (limited to 'testapp/exam/views.py')
-rw-r--r-- | testapp/exam/views.py | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/testapp/exam/views.py b/testapp/exam/views.py new file mode 100644 index 0000000..c178a0b --- /dev/null +++ b/testapp/exam/views.py @@ -0,0 +1,351 @@ +import random +import string +import os +import stat +from os.path import dirname, pardir, abspath, join, exists +import datetime + +from django.contrib.auth import login, logout, authenticate +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 + +# Local imports. +from exam.models import Quiz, Question, QuestionPaper, Profile, Answer, User +from exam.forms import UserRegisterForm, UserLoginForm +from exam.xmlrpc_clients import code_server +from settings import URL_ROOT + +# The directory where user data can be saved. +OUTPUT_DIR = abspath(join(dirname(__file__), pardir, 'output')) + + +def my_redirect(url): + """An overridden redirect to deal with URL_ROOT-ing. See settings.py + for details.""" + return redirect(URL_ROOT + url) + +def my_render_to_response(template, context=None, **kwargs): + """Overridden render_to_response. + """ + if context is None: + context = {'URL_ROOT': URL_ROOT} + else: + context['URL_ROOT'] = URL_ROOT + return render_to_response(template, context, **kwargs) + + +def gen_key(no_of_chars): + """Generate a random key of the number of characters.""" + allowed_chars = string.digits+string.uppercase + return ''.join([random.choice(allowed_chars) for i in range(no_of_chars)]) + +def get_user_dir(user): + """Return the output directory for the user.""" + user_dir = join(OUTPUT_DIR, str(user.username)) + if not exists(user_dir): + os.mkdir(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 + +def index(request): + """The start page. + """ + user = request.user + if user.is_authenticated(): + return my_redirect("/exam/start/") + + return my_redirect("/exam/login/") + +def user_register(request): + """ Register a new user. + Create a user and corresponding profile and store roll_number also.""" + + user = request.user + if user.is_authenticated(): + return my_redirect("/exam/start/") + + if request.method == "POST": + form = UserRegisterForm(request.POST) + if form.is_valid(): + data = form.cleaned_data + u_name, pwd = form.save() + + new_user = authenticate(username = u_name, password = pwd) + login(request, new_user) + return my_redirect("/exam/start/") + + else: + return my_render_to_response('exam/register.html', + {'form':form}, + context_instance=RequestContext(request)) + else: + form = UserRegisterForm() + return my_render_to_response('exam/register.html', + {'form':form}, + context_instance=RequestContext(request)) + +def user_login(request): + """Take the credentials of the user and log the user in.""" + + user = request.user + if user.is_authenticated(): + return my_redirect("/exam/start/") + + if request.method == "POST": + form = UserLoginForm(request.POST) + if form.is_valid(): + user = form.cleaned_data + login(request, user) + return my_redirect("/exam/start/") + else: + context = {"form": form} + return my_render_to_response('exam/login.html', context, + context_instance=RequestContext(request)) + else: + form = UserLoginForm() + context = {"form": form} + return my_render_to_response('exam/login.html', context, + context_instance=RequestContext(request)) + +def start(request): + user = request.user + try: + # Right now the app is designed so there is only one active quiz + # at a particular time. + quiz = Quiz.objects.get(active=True) + except Quiz.DoesNotExist: + msg = 'No active quiz found, please contact your '\ + 'instructor/administrator. Please login again thereafter.' + return complete(request, reason=msg) + try: + old_paper = QuestionPaper.objects.get(user=user, quiz=quiz) + q = old_paper.current_question() + return show_question(request, q) + except QuestionPaper.DoesNotExist: + ip = request.META['REMOTE_ADDR'] + key = gen_key(10) + try: + profile = user.get_profile() + except Profile.DoesNotExist: + msg = 'You do not have a profile and cannot take the quiz!' + raise Http404(msg) + + new_paper = QuestionPaper(user=user, user_ip=ip, key=key, + quiz=quiz, profile=profile) + new_paper.start_time = datetime.datetime.now() + + # Make user directory. + user_dir = get_user_dir(user) + + questions = [ str(_.id) for _ in Question.objects.filter(active=True) ] + random.shuffle(questions) + + new_paper.questions = "|".join(questions) + new_paper.save() + + # Show the user the intro page. + context = {'user': user} + ci = RequestContext(request) + return my_render_to_response('exam/intro.html', context, + context_instance=ci) + +def question(request, q_id): + user = request.user + if not user.is_authenticated(): + return my_redirect('/exam/login/') + q = get_object_or_404(Question, pk=q_id) + try: + paper = QuestionPaper.objects.get(user=request.user, quiz__active=True) + except QuestionPaper.DoesNotExist: + return my_redirect('/exam/start') + if not paper.quiz.active: + return complete(request, reason='The quiz has been deactivated!') + + time_left = paper.time_left() + if time_left == 0: + return complete(request, reason='Your time is up!') + quiz_name = paper.quiz.description + context = {'question': q, 'paper': paper, 'user': user, + 'quiz_name': quiz_name, + 'time_left': time_left} + ci = RequestContext(request) + return my_render_to_response('exam/question.html', context, + context_instance=ci) + +def show_question(request, q_id): + """Show a question if possible.""" + if len(q_id) == 0: + msg = 'Congratulations! You have successfully completed the quiz.' + return complete(request, msg) + else: + return question(request, q_id) + +def check(request, q_id): + user = request.user + if not user.is_authenticated(): + return my_redirect('/exam/login/') + question = get_object_or_404(Question, pk=q_id) + paper = QuestionPaper.objects.get(user=user, quiz__active=True) + answer = request.POST.get('answer') + skip = request.POST.get('skip', None) + + if skip is not None: + next_q = paper.skip() + return show_question(request, next_q) + + # Add the answer submitted, regardless of it being correct or not. + new_answer = Answer(question=question, answer=answer, correct=False) + new_answer.save() + paper.answers.add(new_answer) + + # If we were not skipped, we were asked to check. For any non-mcq + # questions, we obtain the results via XML-RPC with the code executed + # safely in a separate process (the code_server.py) running as nobody. + if question.type == 'mcq': + success = True # Only one attempt allowed for MCQ's. + if answer.strip() == question.test.strip(): + new_answer.correct = True + new_answer.marks = question.points + new_answer.error = 'Correct answer' + else: + new_answer.error = 'Incorrect answer' + else: + user_dir = get_user_dir(user) + success, err_msg = code_server.run_code(answer, question.test, + user_dir, question.type) + new_answer.error = err_msg + if success: + # Note the success and save it along with the marks. + new_answer.correct = success + new_answer.marks = question.points + + new_answer.save() + + if not success: # Should only happen for non-mcq questions. + time_left = paper.time_left() + if time_left == 0: + return complete(request, reason='Your time is up!') + if not paper.quiz.active: + return complete(request, reason='The quiz has been deactivated!') + + context = {'question': question, 'error_message': err_msg, + 'paper': paper, 'last_attempt': answer, + 'quiz_name': paper.quiz.description, + 'time_left': time_left} + ci = RequestContext(request) + + return my_render_to_response('exam/question.html', context, + context_instance=ci) + else: + next_q = paper.completed_question(question.id) + return show_question(request, next_q) + +def quit(request): + return my_render_to_response('exam/quit.html', + context_instance=RequestContext(request)) + +def complete(request, reason=None): + user = request.user + no = False + message = reason or 'The quiz has been completed. Thank you.' + if request.method == 'POST' and 'no' in request.POST: + no = request.POST.get('no', False) + if not no: + # Logout the user and quit with the message given. + logout(request) + context = {'message': message} + return my_render_to_response('exam/complete.html', context) + else: + return my_redirect('/exam/') + + +def monitor(request, quiz_id=None): + """Monitor the progress of the papers taken so far.""" + user = request.user + if not user.is_authenticated() and not user.is_staff: + raise Http404('You are not allowed to view this page!') + + if quiz_id is None: + quizzes = Quiz.objects.all() + context = {'papers': [], + 'quiz': None, + 'quizzes':quizzes} + return my_render_to_response('exam/monitor.html', context, + context_instance=RequestContext(request)) + # quiz_id is not None. + try: + quiz = Quiz.objects.get(id=quiz_id) + except Quiz.DoesNotExist: + papers = [] + quiz = None + else: + papers = QuestionPaper.objects.all().annotate( + total=Sum('answers__marks')).order_by('-total') + + context = {'papers': papers, 'quiz': quiz, 'quizzes': None} + return my_render_to_response('exam/monitor.html', context, + context_instance=RequestContext(request)) + +def get_user_data(username): + """For a given username, this returns a dictionary of important data + related to the user including all the user's answers submitted. + """ + user = User.objects.get(username=username) + papers = QuestionPaper.objects.filter(user=user) + + data = {} + try: + profile = user.get_profile() + except Profile.DoesNotExist: + # Admin user may have a paper by accident but no profile. + profile = None + data['user'] = user + data['profile'] = profile + data['papers'] = papers + return data + +def user_data(request, username): + """Render user data.""" + current_user = request.user + if not current_user.is_authenticated() and not current_user.is_staff: + raise Http404('You are not allowed to view this page!') + + data = get_user_data(username) + + context = {'data': data} + return my_render_to_response('exam/user_data.html', context, + context_instance=RequestContext(request)) + +def grade_user(request, username): + """Present an interface with which we can easily grade a user's papers + and update all their marks and also give comments for each paper. + """ + current_user = request.user + if not current_user.is_authenticated() and not current_user.is_staff: + raise Http404('You are not allowed to view this page!') + + data = get_user_data(username) + if request.method == 'POST': + papers = data['papers'] + for paper in papers: + for question, answers in paper.get_question_answers().iteritems(): + marks = float(request.POST.get('q%d_marks'%question.id)) + last_ans = answers[-1] + last_ans.marks = marks + last_ans.save() + paper.comments = request.POST.get('comments_%d'%paper.quiz.id) + paper.save() + + context = {'data': data} + return my_render_to_response('exam/user_data.html', context, + context_instance=RequestContext(request)) + else: + context = {'data': data} + return my_render_to_response('exam/grade_user.html', context, + context_instance=RequestContext(request)) + |