diff options
-rw-r--r-- | __init__.py | 0 | ||||
-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 | ||||
-rwxr-xr-x | manage.py | 14 | ||||
-rw-r--r-- | settings.py | 153 | ||||
-rw-r--r-- | templates/exam/complete.html | 3 | ||||
-rw-r--r-- | templates/exam/index.html | 12 | ||||
-rw-r--r-- | templates/exam/question.html | 20 | ||||
-rw-r--r-- | templates/exam/register.html | 7 | ||||
-rw-r--r-- | urls.py | 14 |
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> @@ -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)), +) |