diff options
author | Prabhu Ramachandran | 2011-11-08 02:10:03 +0530 |
---|---|---|
committer | Prabhu Ramachandran | 2011-11-08 02:10:03 +0530 |
commit | ec738bb79132a581a1778187a55632983d1fcb53 (patch) | |
tree | d9491ee26367bb504143f5b5bd3a392160c2dbbb /exam | |
download | online_test-ec738bb79132a581a1778187a55632983d1fcb53.tar.gz online_test-ec738bb79132a581a1778187a55632983d1fcb53.tar.bz2 online_test-ec738bb79132a581a1778187a55632983d1fcb53.zip |
NEW: First cut of exam app.
This application allows us to create questions via the admin interface.
The questions are proper programming questions and one enters test cases
for each question. The user logs in and submits the answer which is
checked. The app is rather simple now but does work.
Diffstat (limited to 'exam')
-rw-r--r-- | exam/__init__.py | 0 | ||||
-rw-r--r-- | exam/admin.py | 4 | ||||
-rw-r--r-- | exam/forms.py | 15 | ||||
-rw-r--r-- | exam/models.py | 102 | ||||
-rw-r--r-- | exam/tests.py | 16 | ||||
-rw-r--r-- | exam/urls.py | 9 | ||||
-rw-r--r-- | exam/views.py | 150 |
7 files changed, 296 insertions, 0 deletions
diff --git a/exam/__init__.py b/exam/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/exam/__init__.py diff --git a/exam/admin.py b/exam/admin.py new file mode 100644 index 0000000..d40a7d6 --- /dev/null +++ b/exam/admin.py @@ -0,0 +1,4 @@ +from exam.models import Question +from django.contrib import admin + +admin.site.register(Question)
\ No newline at end of file diff --git a/exam/forms.py b/exam/forms.py new file mode 100644 index 0000000..bd5732e --- /dev/null +++ b/exam/forms.py @@ -0,0 +1,15 @@ +from django import forms +from exam.models import Profile + +class UserRegisterForm(forms.ModelForm): + + first_name = forms.CharField(max_length=30) + last_name = forms.CharField(max_length=30) + roll_number = forms.CharField(max_length=30) + #email_address = forms.EmailField() + #password = forms.CharField(max_length=30, widget=forms.PasswordInput()) + + class Meta: + model = Profile + fields = ['first_name', 'last_name', 'roll_number'] + diff --git a/exam/models.py b/exam/models.py new file mode 100644 index 0000000..247b362 --- /dev/null +++ b/exam/models.py @@ -0,0 +1,102 @@ +from django.db import models +from django.contrib.auth.models import User + +# Create your models here. + +class Profile(models.Model): + """Profile for a user to store roll number etc.""" + user = models.ForeignKey(User) + roll_number = models.CharField(max_length=20) + +class Question(models.Model): + """A question in the database.""" + # An optional one-line summary of the question. + summary = models.CharField(max_length=256) + # The question text. + question = models.TextField() + + # Number of points for the question. + points = models.IntegerField() + + # Test cases for the question in the form of code that is run. + # This is simple Python code. + test = models.TextField() + + def __unicode__(self): + return self.summary + + +class Answer(models.Model): + """Answers submitted by users. + """ + question = models.ForeignKey(Question) + # The last answer submitted by the user. + answer = models.TextField() + attempts = models.IntegerField() + + # Is the question correct. + correct = models.BooleanField() + # Marks obtained. + marks = models.IntegerField() + + def __unicode__(self): + return self.answer + +class Quiz(models.Model): + """A quiz for a student. + """ + user = models.ForeignKey(User) + user_ip = models.CharField(max_length=15) + key = models.CharField(max_length=10) + questions = models.CharField(max_length=128) + questions_answered = models.CharField(max_length=128) + + def current_question(self): + """Returns the current active question to display.""" + qs = self.questions.split('|') + if len(qs) > 0: + return qs[0] + else: + return '' + + def questions_left(self): + """Returns the number of questions left.""" + qs = self.questions + if len(qs) == 0: + return 0 + else: + return qs.count('|') + 1 + + def answered_question(self, question_id): + """Removes the question from the list of questions and returns + the next.""" + qa = self.questions_answered + if len(qa) > 0: + self.questions_answered = '|'.join([qa, str(question_id)]) + else: + self.questions_answered = str(question_id) + qs = self.questions.split('|') + qs.remove(unicode(question_id)) + self.questions = '|'.join(qs) + self.save() + if len(qs) == 0: + return '' + else: + return qs[0] + + def skip(self): + """Skip the current question and return the next available question.""" + qs = self.questions.split('|') + if len(qs) == 0: + return '' + else: + # Put head at the end. + head = qs.pop(0) + qs.append(head) + self.questions = '|'.join(qs) + self.save() + return qs[0] + + def __unicode__(self): + u = self.user + return u'Quiz for {0} {1}'.format(u.first_name, u.last_name) diff --git a/exam/tests.py b/exam/tests.py new file mode 100644 index 0000000..501deb7 --- /dev/null +++ b/exam/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/exam/urls.py b/exam/urls.py new file mode 100644 index 0000000..7922255 --- /dev/null +++ b/exam/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import patterns, include, url + +urlpatterns = patterns('exam.views', + url(r'^$', 'index'), + url(r'^start/$', 'start'), + url(r'^complete/$', 'complete'), + url(r'^(?P<q_id>\d+)/$', 'question'), + url(r'^(?P<q_id>\d+)/check/$', 'check'), +) diff --git a/exam/views.py b/exam/views.py new file mode 100644 index 0000000..10d6c9a --- /dev/null +++ b/exam/views.py @@ -0,0 +1,150 @@ +import random +import sys +import traceback +import string + +from django.db import IntegrityError +from django.contrib.auth.models import User +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 exam.models import Question, Quiz, Profile +from exam.forms import UserRegisterForm + +def gen_key(no_of_chars): + allowed_chars = string.digits+string.uppercase + return ''.join([random.choice(allowed_chars) for i in range(no_of_chars)]) + +def index_old(request): + """The start page. + """ + question_list = Question.objects.all() + context = {'question_list': question_list} + return render_to_response('exam/index.html', context) + +def index(request): + """The start page. + """ + # Largely copied from Nishanth's quiz app. + user = request.user + if user.is_authenticated(): + return redirect("/exam/start/") + else: + try: + ip = request.META['REMOTE_ADDR'] + Quiz.objects.get(user_ip=ip) + return redirect("/exam/complete") + except Quiz.DoesNotExist: + pass + + if request.method == "POST": + form = UserRegisterForm(request.POST) + if form.is_valid(): + data = form.cleaned_data + + while True: + try: + username = gen_key(20) + new_user = User.objects.create_user(username, "temp@temp.com", "123") + break + except IntegrityError: + pass + + new_user.first_name = data['first_name'] + new_user.last_name = data['last_name'] + new_user.save() + + new_profile = Profile(user=new_user) + new_profile.roll_number = data['roll_number'] + new_profile.save() + + user = authenticate(username=username, password="123") + login(request, user) + return redirect("/exam/start/") + + else: + return render_to_response('exam/register.html',{'form':form}, + context_instance=RequestContext(request)) + else: + form = UserRegisterForm() + return render_to_response('exam/register.html',{'form':form}, + context_instance=RequestContext(request)) + +def show_question(request, q_id): + if len(q_id) == 0: + return redirect("/exam/complete") + else: + return question(request, q_id) + +def start(request): + user = request.user + try: + old_quiz = Quiz.objects.get(user=user) + q = old_quiz.current_question() + return show_question(request, q) + except Quiz.DoesNotExist: + ip = request.META['REMOTE_ADDR'] + key = gen_key(10) + new_quiz = Quiz(user=user, user_ip=ip, key=key) + + questions = [ str(_.id) for _ in Question.objects.all() ] + random.shuffle(questions) + questions = questions[:3] + + new_quiz.questions = "|".join(questions) + new_quiz.save() + q = new_quiz.current_question() + + return show_question(request, q) + +def question(request, q_id): + q = get_object_or_404(Question, pk=q_id) + try: + quiz = Quiz.objects.get(user=request.user) + except Quiz.DoesNotExist: + redirect('/exam/start') + context = {'question': q, 'quiz': quiz} + ci = RequestContext(request) + return render_to_response('exam/question.html', context, + context_instance=ci) + +def test_answer(func_code, test_code): + exec func_code + exec test_code + +def check(request, q_id): + user = request.user + question = get_object_or_404(Question, pk=q_id) + quiz = Quiz.objects.get(user=user) + answer = request.POST.get('answer') + skip = request.POST.get('skip', None) + + if skip is not None: + next_q = quiz.skip() + return show_question(request, next_q) + + # Otherwise we were asked to check. + retry = True + try: + test_answer(answer, question.test) + except: + type, value, tb = sys.exc_info() + info = traceback.extract_tb(tb) + fname, lineno, func, text = info[-1] + err = "{0}: {1} In code: {2}".format(type.__name__, str(value), text) + else: + retry = False + err = 'Correct answer' + + ci = RequestContext(request) + if retry: + context = {'question': question, 'error_message': err} + return render_to_response('exam/question.html', context, + context_instance=ci) + else: + next_q = quiz.answered_question(question.id) + return show_question(request, next_q) + +def complete(request): + return render_to_response('exam/complete.html') +
\ No newline at end of file |