summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--__init__.py0
-rw-r--r--exam/__init__.py0
-rw-r--r--exam/admin.py4
-rw-r--r--exam/forms.py15
-rw-r--r--exam/models.py102
-rw-r--r--exam/tests.py16
-rw-r--r--exam/urls.py9
-rw-r--r--exam/views.py150
-rwxr-xr-xmanage.py14
-rw-r--r--settings.py153
-rw-r--r--templates/exam/complete.html3
-rw-r--r--templates/exam/index.html12
-rw-r--r--templates/exam/question.html20
-rw-r--r--templates/exam/register.html7
-rw-r--r--urls.py14
15 files changed, 519 insertions, 0 deletions
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/__init__.py
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
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..3e4eedc
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+import imp
+try:
+ imp.find_module('settings') # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
+ sys.exit(1)
+
+import settings
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/settings.py b/settings.py
new file mode 100644
index 0000000..3678399
--- /dev/null
+++ b/settings.py
@@ -0,0 +1,153 @@
+# Django settings for tester project.
+
+from os.path import dirname, join, expanduser
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DB_FILE = join(dirname(__file__), 'exam.db')
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': DB_FILE, # Or path to database file if using sqlite3.
+ 'USER': '', # Not used with sqlite3.
+ 'PASSWORD': '', # Not used with sqlite3.
+ 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': '', # Set to empty string for default. Not used with sqlite3.
+ }
+}
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://media.lawrence.com/static/"
+STATIC_URL = '/static/'
+
+# URL prefix for admin static files -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+ # Put strings here, like "/home/html/static" or "C:/www/django/static".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '9h*01@*#3ok+lbj5k=ym^eb)e=rf-g70&n0^nb_q6mtk!r(qr)'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'tester.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ expanduser("~/stuff/django/tester/templates"),
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ # Uncomment the next line to enable the admin:
+ 'django.contrib.admin',
+ # Uncomment the next line to enable admin documentation:
+ # 'django.contrib.admindocs',
+ 'exam',
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'handlers': {
+ 'mail_admins': {
+ 'level': 'ERROR',
+ 'class': 'django.utils.log.AdminEmailHandler'
+ }
+ },
+ 'loggers': {
+ 'django.request': {
+ 'handlers': ['mail_admins'],
+ 'level': 'ERROR',
+ 'propagate': True,
+ },
+ }
+}
+
+AUTH_PROFILE_MODULE = 'exam.Profile' \ No newline at end of file
diff --git a/templates/exam/complete.html b/templates/exam/complete.html
new file mode 100644
index 0000000..e42704f
--- /dev/null
+++ b/templates/exam/complete.html
@@ -0,0 +1,3 @@
+<p>Quiz is complete. Thank you. </p>
+<br />
+<p>You may now close the browser.</p>
diff --git a/templates/exam/index.html b/templates/exam/index.html
new file mode 100644
index 0000000..5470cf5
--- /dev/null
+++ b/templates/exam/index.html
@@ -0,0 +1,12 @@
+<p> Welcome to the Examination. </p>
+<br></br>
+
+{% if question_list %}
+ <ul>
+ {% for question in question_list %}
+ <li> <a href="/exam/{{ question.id }}/">{{ question.summary }} </a> </li>
+ {% endfor %}
+ </ul>
+{% else %}
+ <p> Lucky you, no questions available.</p>
+{% endif %}
diff --git a/templates/exam/question.html b/templates/exam/question.html
new file mode 100644
index 0000000..05e80a8
--- /dev/null
+++ b/templates/exam/question.html
@@ -0,0 +1,20 @@
+<h2> {{ question.summary }} </h2>
+
+<p>{{ question.question }} </p>
+
+{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
+
+<form action="/exam/{{ question.id }}/check/" method="post">
+{% csrf_token %}
+
+<textarea rows="20" cols="100" name="answer">
+# Enter your answer here.
+</textarea>
+
+<br/>
+
+<input type="submit" name="check" value="Check Answer" />
+<input type="submit" name="skip" value="Skip question" />
+</form>
+
+<p> You have {{quiz.questions_left}} question(s) left. </p> \ No newline at end of file
diff --git a/templates/exam/register.html b/templates/exam/register.html
new file mode 100644
index 0000000..414da72
--- /dev/null
+++ b/templates/exam/register.html
@@ -0,0 +1,7 @@
+Please provide the following details before you start the test.
+<form action="" method="post">
+{% csrf_token %}
+
+{{ form.as_p }}
+<input type="submit" value="submit">
+</form>
diff --git a/urls.py b/urls.py
new file mode 100644
index 0000000..f55309a
--- /dev/null
+++ b/urls.py
@@ -0,0 +1,14 @@
+from django.conf.urls.defaults import patterns, include, url
+
+# Uncomment the next two lines to enable the admin:
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ url(r'^exam/', include('exam.urls')),
+
+ # Uncomment the admin/doc line below to enable admin documentation:
+ # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+ # Uncomment the next line to enable the admin:
+ url(r'^admin/', include(admin.site.urls)),
+)