summaryrefslogtreecommitdiff
path: root/testapp/exam/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'testapp/exam/views.py')
-rw-r--r--testapp/exam/views.py351
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))
+