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 # 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) 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) 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.filter(quiz=quiz, user__profile__isnull=False) papers = sorted(papers, cmp=lambda x, y: cmp(x.get_total_marks(), y.get_total_marks()), reverse=True) 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))