summaryrefslogtreecommitdiff
path: root/exam
diff options
context:
space:
mode:
authorparth2011-12-09 04:02:19 +0530
committerparth2011-12-09 04:02:19 +0530
commit861d2cc5e36835f60bace61a919e73b4bd27274b (patch)
treed08d776f58167903781c371d147f5b9e1f83e168 /exam
parent7104f495d01fb934af11c8dfd09da087174c1b12 (diff)
downloadonline_test-861d2cc5e36835f60bace61a919e73b4bd27274b.tar.gz
online_test-861d2cc5e36835f60bace61a919e73b4bd27274b.tar.bz2
online_test-861d2cc5e36835f60bace61a919e73b4bd27274b.zip
Moved all the apps to testapp folder
Diffstat (limited to 'exam')
-rw-r--r--exam/__init__.py0
-rw-r--r--exam/admin.py5
-rw-r--r--exam/forms.py95
-rw-r--r--exam/management/__init__.py0
-rw-r--r--exam/management/commands/__init__.py0
-rw-r--r--exam/management/commands/dump_user_data.py98
-rw-r--r--exam/management/commands/load_exam.py55
-rw-r--r--exam/management/commands/load_questions_xml.py73
-rw-r--r--exam/management/commands/results2csv.py69
-rw-r--r--exam/migrations/0001_initial.py193
-rw-r--r--exam/migrations/__init__.py0
-rw-r--r--exam/models.py221
-rw-r--r--exam/tests.py16
-rw-r--r--exam/urls.py16
-rw-r--r--exam/views.py351
-rw-r--r--exam/xmlrpc_clients.py78
16 files changed, 0 insertions, 1270 deletions
diff --git a/exam/__init__.py b/exam/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/exam/__init__.py
+++ /dev/null
diff --git a/exam/admin.py b/exam/admin.py
deleted file mode 100644
index 8482ef9..0000000
--- a/exam/admin.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from exam.models import Question, Quiz
-from django.contrib import admin
-
-admin.site.register(Question)
-admin.site.register(Quiz)
diff --git a/exam/forms.py b/exam/forms.py
deleted file mode 100644
index a5ca26f..0000000
--- a/exam/forms.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from django import forms
-from exam.models import Profile
-
-from django.contrib.auth import authenticate
-from django.contrib.auth.models import User
-
-from string import letters, punctuation, digits
-
-UNAME_CHARS = letters + "._" + digits
-PWD_CHARS = letters + punctuation + digits
-
-class UserRegisterForm(forms.Form):
-
- username = forms.CharField(max_length=30,
- help_text='Letters, digits, period and underscores only.')
- email = forms.EmailField()
- password = forms.CharField(max_length=30,
- widget=forms.PasswordInput())
- confirm_password = forms.CharField(max_length=30,
- widget=forms.PasswordInput())
- first_name = forms.CharField(max_length=30)
- last_name = forms.CharField(max_length=30)
- roll_number = forms.CharField(max_length=30,
- help_text="Use a dummy if you don't have one.")
- institute = forms.CharField(max_length=128,
- help_text='Institute/Organization')
- department = forms.CharField(max_length=64,
- help_text='Department you work/study at')
- position = forms.CharField(max_length=64,
- help_text='Student/Faculty/Researcher/Industry/etc.')
-
- def clean_username(self):
- u_name = self.cleaned_data["username"]
-
- if u_name.strip(UNAME_CHARS):
- msg = "Only letters, digits, period and underscore characters are "\
- "allowed in username"
- raise forms.ValidationError(msg)
-
- try:
- User.objects.get(username__exact = u_name)
- raise forms.ValidationError("Username already exists.")
- except User.DoesNotExist:
- return u_name
-
- def clean_password(self):
- pwd = self.cleaned_data['password']
- if pwd.strip(PWD_CHARS):
- raise forms.ValidationError("Only letters, digits and punctuation are \
- allowed in password")
- return pwd
-
- def clean_confirm_password(self):
- c_pwd = self.cleaned_data['confirm_password']
- pwd = self.data['password']
- if c_pwd != pwd:
- raise forms.ValidationError("Passwords do not match")
-
- return c_pwd
-
- def save(self):
- u_name = self.cleaned_data["username"]
- u_name = u_name.lower()
- pwd = self.cleaned_data["password"]
- email = self.cleaned_data['email']
- new_user = User.objects.create_user(u_name, email, pwd)
-
- new_user.first_name = self.cleaned_data["first_name"]
- new_user.last_name = self.cleaned_data["last_name"]
- new_user.save()
-
- cleaned_data = self.cleaned_data
- new_profile = Profile(user=new_user)
- new_profile.roll_number = cleaned_data["roll_number"]
- new_profile.institute = cleaned_data["institute"]
- new_profile.department = cleaned_data["department"]
- new_profile.position = cleaned_data["position"]
- new_profile.save()
-
- return u_name, pwd
-
-class UserLoginForm(forms.Form):
- username = forms.CharField(max_length = 30)
- password = forms.CharField(max_length=30, widget=forms.PasswordInput())
-
- def clean(self):
- super(UserLoginForm, self).clean()
- u_name, pwd = self.cleaned_data["username"], self.cleaned_data["password"]
- user = authenticate(username = u_name, password = pwd)
-
- if not user:
- raise forms.ValidationError("Invalid username/password")
-
- return user
-
diff --git a/exam/management/__init__.py b/exam/management/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/exam/management/__init__.py
+++ /dev/null
diff --git a/exam/management/commands/__init__.py b/exam/management/commands/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/exam/management/commands/__init__.py
+++ /dev/null
diff --git a/exam/management/commands/dump_user_data.py b/exam/management/commands/dump_user_data.py
deleted file mode 100644
index ec016bb..0000000
--- a/exam/management/commands/dump_user_data.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import sys
-
-# Django imports.
-from django.core.management.base import BaseCommand
-from django.template import Template, Context
-
-# Local imports.
-from exam.views import get_user_data
-from exam.models import User
-
-data_template = Template('''\
-===============================================================================
-Data for {{ data.user.get_full_name.title }} ({{ data.user.username }})
-
-Name: {{ data.user.get_full_name.title }}
-Username: {{ data.user.username }}
-{% if data.profile %}\
-Roll number: {{ data.profile.roll_number }}
-Position: {{ data.profile.position }}
-Department: {{ data.profile.department }}
-Institute: {{ data.profile.institute }}
-{% endif %}\
-Email: {{ data.user.email }}
-Date joined: {{ data.user.date_joined }}
-Last login: {{ data.user.last_login }}
-{% for paper in data.papers %}
-Paper: {{ paper.quiz.description }}
----------------------------------------
-Marks obtained: {{ paper.get_total_marks }}
-Questions correctly answered: {{ paper.get_answered_str }}
-Total attempts at questions: {{ paper.answers.count }}
-Start time: {{ paper.start_time }}
-User IP address: {{ paper.user_ip }}
-{% if paper.answers.count %}
-Answers
--------
-{% for question, answers in paper.get_question_answers.items %}
-Question: {{ question.id }}. {{ question.summary }} (Points: {{ question.points }})
-{% if question.type == "mcq" %}\
-###############################################################################
-Choices: {% for option in question.options.strip.splitlines %} {{option}}, {% endfor %}
-Student answer: {{ answers.0|safe }}
-{% else %}{# non-mcq questions #}\
-{% for answer in answers %}\
-###############################################################################
-{{ answer.answer.strip|safe }}
-# Autocheck: {{ answer.error|safe }}
-{% endfor %}{# for answer in answers #}\
-{% endif %}\
-{% with answers|last as answer %}\
-Marks: {{answer.marks}}
-{% endwith %}\
-{% endfor %}{# for question, answers ... #}\
-
-Teacher comments
------------------
-{{ paper.comments|default:"None" }}
-{% endif %}{# if paper.answers.count #}\
-{% endfor %}{# for paper in data.papers #}
-''')
-
-
-def dump_user_data(unames, stdout):
- '''Dump user data given usernames (a sequence) if none is given dump all
- their data. The data is dumped to stdout.
- '''
- if not unames:
- try:
- users = User.objects.all()
- except User.DoesNotExist:
- pass
- else:
- users = []
- for uname in unames:
- try:
- user = User.objects.get(username__exact = uname)
- except User.DoesNotExist:
- stdout.write('User %s does not exist'%uname)
- else:
- users.append(user)
-
- for user in users:
- data = get_user_data(user.username)
- context = Context({'data': data})
- result = data_template.render(context)
- stdout.write(result.encode('ascii', 'xmlcharrefreplace'))
-
-class Command(BaseCommand):
- args = '<username1> ... <usernamen>'
- help = '''Dumps all user data to stdout, optional usernames can be
- specified. If none is specified all user data is dumped.
- '''
-
- def handle(self, *args, **options):
- """Handle the command."""
- # Dump data.
- dump_user_data(args, self.stdout)
-
diff --git a/exam/management/commands/load_exam.py b/exam/management/commands/load_exam.py
deleted file mode 100644
index 3f247a1..0000000
--- a/exam/management/commands/load_exam.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# System library imports.
-from os.path import basename
-
-# Django imports.
-from django.core.management.base import BaseCommand
-
-# Local imports.
-from exam.models import Question, Quiz
-
-def clear_exam():
- """Deactivate all questions from the database."""
- for question in Question.objects.all():
- question.active = False
- question.save()
-
- # Deactivate old quizzes.
- for quiz in Quiz.objects.all():
- quiz.active = False
- quiz.save()
-
-def load_exam(filename):
- """Load questions and quiz from the given Python file. The Python file
- should declare a list of name "questions" which define all the questions
- in pure Python. It can optionally load a Quiz from an optional 'quiz'
- object.
- """
- # Simply exec the given file and we are done.
- exec(open(filename).read())
-
- if 'questions' not in locals():
- msg = 'No variable named "questions" with the Questions in file.'
- raise NameError(msg)
-
- for question in questions:
- question.save()
-
- if 'quiz' in locals():
- quiz.save()
-
-class Command(BaseCommand):
- args = '<q_file1.py q_file2.py>'
- help = '''loads the questions from given Python files which declare the
- questions in a list called "questions".'''
-
- def handle(self, *args, **options):
- """Handle the command."""
- # Delete existing stuff.
- clear_exam()
-
- # Load from files.
- for fname in args:
- self.stdout.write('Importing from {0} ... '.format(basename(fname)))
- load_exam(fname)
- self.stdout.write('Done\n')
-
diff --git a/exam/management/commands/load_questions_xml.py b/exam/management/commands/load_questions_xml.py
deleted file mode 100644
index 8bc2701..0000000
--- a/exam/management/commands/load_questions_xml.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# System library imports.
-from os.path import basename
-from xml.dom.minidom import parse
-from htmlentitydefs import name2codepoint
-import re
-
-# Django imports.
-from django.core.management.base import BaseCommand
-
-# Local imports.
-from exam.models import Question
-
-def decode_html(html_str):
- """Un-escape or decode HTML strings to more usable Python strings.
- From here: http://wiki.python.org/moin/EscapingHtml
- """
- return re.sub('&(%s);' % '|'.join(name2codepoint),
- lambda m: unichr(name2codepoint[m.group(1)]), html_str)
-
-def clear_questions():
- """Deactivate all questions from the database."""
- for question in Question.objects.all():
- question.active = False
- question.save()
-
-def load_questions_xml(filename):
- """Load questions from the given XML file."""
- q_bank = parse(filename).getElementsByTagName("question")
-
- for question in q_bank:
-
- summary_node = question.getElementsByTagName("summary")[0]
- summary = (summary_node.childNodes[0].data).strip()
-
- desc_node = question.getElementsByTagName("description")[0]
- description = (desc_node.childNodes[0].data).strip()
-
- type_node = question.getElementsByTagName("type")[0]
- type = (type_node.childNodes[0].data).strip()
-
- points_node = question.getElementsByTagName("points")[0]
- points = float((points_node.childNodes[0].data).strip()) \
- if points_node else 1.0
-
- test_node = question.getElementsByTagName("test")[0]
- test = decode_html((test_node.childNodes[0].data).strip())
-
- opt_node = question.getElementsByTagName("options")[0]
- opt = decode_html((opt_node.childNodes[0].data).strip())
-
- new_question = Question(summary=summary,
- description=description,
- points=points,
- options=opt,
- type=type,
- test=test)
- new_question.save()
-
-class Command(BaseCommand):
- args = '<q_file1.xml q_file2.xml>'
- help = 'loads the questions from given XML files'
-
- def handle(self, *args, **options):
- """Handle the command."""
- # Delete existing stuff.
- clear_questions()
-
- # Load from files.
- for fname in args:
- self.stdout.write('Importing from {0} ... '.format(basename(fname)))
- load_questions_xml(fname)
- self.stdout.write('Done\n')
-
diff --git a/exam/management/commands/results2csv.py b/exam/management/commands/results2csv.py
deleted file mode 100644
index 2993745..0000000
--- a/exam/management/commands/results2csv.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# System library imports.
-import sys
-from os.path import basename
-
-# Django imports.
-from django.core.management.base import BaseCommand
-from django.template import Template, Context
-
-# Local imports.
-from exam.models import Quiz, QuestionPaper
-
-result_template = Template('''\
-"name","username","rollno","email","answered","total","attempts","position",\
-"department","institute"
-{% for paper in papers %}\
-"{{ paper.user.get_full_name.title }}",\
-"{{ paper.user.username }}",\
-"{{ paper.profile.roll_number }}",\
-"{{ paper.user.email }}",\
-"{{ paper.get_answered_str }}",\
-{{ paper.get_total_marks }},\
-{{ paper.answers.count }},\
-"{{ paper.profile.position }}",\
-"{{ paper.profile.department }}",\
-"{{ paper.profile.institute }}"
-{% endfor %}\
-''')
-
-def results2csv(filename, stdout):
- """Write exam data to a CSV file. It prompts the user to choose the
- appropriate quiz.
- """
- qs = Quiz.objects.all()
-
- if len(qs) > 1:
- print "Select quiz to save:"
- for q in qs:
- stdout.write('%d. %s\n'%(q.id, q.description))
- quiz_id = int(raw_input("Please select quiz: "))
- try:
- quiz = Quiz.objects.get(id=quiz_id)
- except Quiz.DoesNotExist:
- stdout.write("Sorry, quiz %d does not exist!\n"%quiz_id)
- sys.exit(1)
- else:
- quiz = qs[0]
-
- papers = QuestionPaper.objects.filter(quiz=quiz,
- user__profile__isnull=False)
- stdout.write("Saving results of %s to %s ... "%(quiz.description,
- basename(filename)))
- # Render the data and write it out.
- f = open(filename, 'w')
- context = Context({'papers': papers})
- f.write(result_template.render(context))
- f.close()
-
- stdout.write('Done\n')
-
-class Command(BaseCommand):
- args = '<results.csv>'
- help = '''Writes out the results of a quiz to a CSV file. Prompt user
- to select appropriate quiz if there are multiple.
- '''
-
- def handle(self, *args, **options):
- """Handle the command."""
- # Save to file.
- results2csv(args[0], self.stdout)
diff --git a/exam/migrations/0001_initial.py b/exam/migrations/0001_initial.py
deleted file mode 100644
index 49048cc..0000000
--- a/exam/migrations/0001_initial.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
-
- # Adding model 'Profile'
- db.create_table('exam_profile', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)),
- ('roll_number', self.gf('django.db.models.fields.CharField')(max_length=20)),
- ('institute', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('department', self.gf('django.db.models.fields.CharField')(max_length=64)),
- ('position', self.gf('django.db.models.fields.CharField')(max_length=64)),
- ))
- db.send_create_signal('exam', ['Profile'])
-
- # Adding model 'Question'
- db.create_table('exam_question', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('summary', self.gf('django.db.models.fields.CharField')(max_length=256)),
- ('description', self.gf('django.db.models.fields.TextField')()),
- ('points', self.gf('django.db.models.fields.FloatField')(default=1.0)),
- ('test', self.gf('django.db.models.fields.TextField')(blank=True)),
- ('options', self.gf('django.db.models.fields.TextField')(blank=True)),
- ('type', self.gf('django.db.models.fields.CharField')(max_length=24)),
- ('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ))
- db.send_create_signal('exam', ['Question'])
-
- # Adding model 'Answer'
- db.create_table('exam_answer', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('question', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['exam.Question'])),
- ('answer', self.gf('django.db.models.fields.TextField')()),
- ('error', self.gf('django.db.models.fields.TextField')()),
- ('marks', self.gf('django.db.models.fields.FloatField')(default=0.0)),
- ('correct', self.gf('django.db.models.fields.BooleanField')(default=False)),
- ))
- db.send_create_signal('exam', ['Answer'])
-
- # Adding model 'Quiz'
- db.create_table('exam_quiz', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('start_date', self.gf('django.db.models.fields.DateField')()),
- ('duration', self.gf('django.db.models.fields.IntegerField')(default=20)),
- ('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('description', self.gf('django.db.models.fields.CharField')(max_length=256)),
- ))
- db.send_create_signal('exam', ['Quiz'])
-
- # Adding model 'QuestionPaper'
- db.create_table('exam_questionpaper', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
- ('profile', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['exam.Profile'])),
- ('quiz', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['exam.Quiz'])),
- ('start_time', self.gf('django.db.models.fields.DateTimeField')()),
- ('user_ip', self.gf('django.db.models.fields.CharField')(max_length=15)),
- ('key', self.gf('django.db.models.fields.CharField')(max_length=10)),
- ('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
- ('questions', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('questions_answered', self.gf('django.db.models.fields.CharField')(max_length=128)),
- ('comments', self.gf('django.db.models.fields.TextField')()),
- ))
- db.send_create_signal('exam', ['QuestionPaper'])
-
- # Adding M2M table for field answers on 'QuestionPaper'
- db.create_table('exam_questionpaper_answers', (
- ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
- ('questionpaper', models.ForeignKey(orm['exam.questionpaper'], null=False)),
- ('answer', models.ForeignKey(orm['exam.answer'], null=False))
- ))
- db.create_unique('exam_questionpaper_answers', ['questionpaper_id', 'answer_id'])
-
-
- def backwards(self, orm):
-
- # Deleting model 'Profile'
- db.delete_table('exam_profile')
-
- # Deleting model 'Question'
- db.delete_table('exam_question')
-
- # Deleting model 'Answer'
- db.delete_table('exam_answer')
-
- # Deleting model 'Quiz'
- db.delete_table('exam_quiz')
-
- # Deleting model 'QuestionPaper'
- db.delete_table('exam_questionpaper')
-
- # Removing M2M table for field answers on 'QuestionPaper'
- db.delete_table('exam_questionpaper_answers')
-
-
- models = {
- 'auth.group': {
- 'Meta': {'object_name': 'Group'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
- 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
- },
- 'auth.permission': {
- 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
- },
- 'auth.user': {
- 'Meta': {'object_name': 'User'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
- 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
- },
- 'contenttypes.contenttype': {
- 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
- 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
- },
- 'exam.answer': {
- 'Meta': {'object_name': 'Answer'},
- 'answer': ('django.db.models.fields.TextField', [], {}),
- 'correct': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'error': ('django.db.models.fields.TextField', [], {}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'marks': ('django.db.models.fields.FloatField', [], {'default': '0.0'}),
- 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['exam.Question']"})
- },
- 'exam.profile': {
- 'Meta': {'object_name': 'Profile'},
- 'department': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'institute': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'position': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
- 'roll_number': ('django.db.models.fields.CharField', [], {'max_length': '20'}),
- 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
- },
- 'exam.question': {
- 'Meta': {'object_name': 'Question'},
- 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'description': ('django.db.models.fields.TextField', [], {}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'options': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'points': ('django.db.models.fields.FloatField', [], {'default': '1.0'}),
- 'summary': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'test': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
- 'type': ('django.db.models.fields.CharField', [], {'max_length': '24'})
- },
- 'exam.questionpaper': {
- 'Meta': {'object_name': 'QuestionPaper'},
- 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'answers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['exam.Answer']", 'symmetrical': 'False'}),
- 'comments': ('django.db.models.fields.TextField', [], {}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'key': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
- 'profile': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['exam.Profile']"}),
- 'questions': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'questions_answered': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'quiz': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['exam.Quiz']"}),
- 'start_time': ('django.db.models.fields.DateTimeField', [], {}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
- 'user_ip': ('django.db.models.fields.CharField', [], {'max_length': '15'})
- },
- 'exam.quiz': {
- 'Meta': {'object_name': 'Quiz'},
- 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'description': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
- 'duration': ('django.db.models.fields.IntegerField', [], {'default': '20'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'start_date': ('django.db.models.fields.DateField', [], {})
- }
- }
-
- complete_apps = ['exam']
diff --git a/exam/migrations/__init__.py b/exam/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/exam/migrations/__init__.py
+++ /dev/null
diff --git a/exam/models.py b/exam/models.py
deleted file mode 100644
index 717e02e..0000000
--- a/exam/models.py
+++ /dev/null
@@ -1,221 +0,0 @@
-import datetime
-from django.db import models
-from django.contrib.auth.models import User
-
-################################################################################
-class Profile(models.Model):
- """Profile for a user to store roll number and other details."""
- user = models.OneToOneField(User)
- roll_number = models.CharField(max_length=20)
- institute = models.CharField(max_length=128)
- department = models.CharField(max_length=64)
- position = models.CharField(max_length=64)
-
-
-QUESTION_TYPE_CHOICES = (
- ("python", "Python"),
- ("bash", "Bash"),
- ("mcq", "MultipleChoice"),
- )
-
-################################################################################
-class Question(models.Model):
- """A question in the database."""
-
- # A one-line summary of the question.
- summary = models.CharField(max_length=256)
-
- # The question text, should be valid HTML.
- description = models.TextField()
-
- # Number of points for the question.
- points = models.FloatField(default=1.0)
-
- # Test cases for the question in the form of code that is run.
- test = models.TextField(blank=True)
-
- # Any multiple choice options. Place one option per line.
- options = models.TextField(blank=True)
-
- # The type of question.
- type = models.CharField(max_length=24, choices=QUESTION_TYPE_CHOICES)
-
- # Is this question active or not. If it is inactive it will not be used
- # when creating a QuestionPaper.
- active = models.BooleanField(default=True)
-
- def __unicode__(self):
- return self.summary
-
-
-################################################################################
-class Answer(models.Model):
- """Answers submitted by users.
- """
- # The question for which we are an answer.
- question = models.ForeignKey(Question)
-
- # The answer submitted by the user.
- answer = models.TextField()
-
- # Error message when auto-checking the answer.
- error = models.TextField()
-
- # Marks obtained for the answer. This can be changed by the teacher if the
- # grading is manual.
- marks = models.FloatField(default=0.0)
-
- # Is the answer correct.
- correct = models.BooleanField(default=False)
-
- def __unicode__(self):
- return self.answer
-
-################################################################################
-class Quiz(models.Model):
- """A quiz that students will participate in. One can think of this
- as the "examination" event.
- """
-
- # The starting/ending date of the quiz.
- start_date = models.DateField("Date of the quiz")
-
- # This is always in minutes.
- duration = models.IntegerField("Duration of quiz in minutes", default=20)
-
- # Is the quiz active. The admin should deactivate the quiz once it is
- # complete.
- active = models.BooleanField(default=True)
-
- # Description of quiz.
- description = models.CharField(max_length=256)
-
- class Meta:
- verbose_name_plural = "Quizzes"
-
- def __unicode__(self):
- desc = self.description or 'Quiz'
- return '%s: on %s for %d minutes'%(desc, self.start_date, self.duration)
-
-
-################################################################################
-class QuestionPaper(models.Model):
- """A question paper for a student -- one per student typically.
- """
- # The user taking this question paper.
- user = models.ForeignKey(User)
-
- # The user's profile, we store a reference to make it easier to access the
- # data.
- profile = models.ForeignKey(Profile)
-
- # The Quiz to which this question paper is attached to.
- quiz = models.ForeignKey(Quiz)
-
- # The time when this paper was started by the user.
- start_time = models.DateTimeField()
-
- # User's IP which is logged.
- user_ip = models.CharField(max_length=15)
- # Unused currently.
- key = models.CharField(max_length=10)
-
- # used to allow/stop a user from retaking the question paper.
- active = models.BooleanField(default = True)
-
- # The questions (a list of ids separated by '|')
- questions = models.CharField(max_length=128)
- # The questions successfully answered (a list of ids separated by '|')
- questions_answered = models.CharField(max_length=128)
-
- # All the submitted answers.
- answers = models.ManyToManyField(Answer)
-
- # Teacher comments on the question paper.
- comments = models.TextField()
-
- 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 completed_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 time_left(self):
- """Return the time remaining for the user in seconds."""
- dt = datetime.datetime.now() - self.start_time
- try:
- secs = dt.total_seconds()
- except AttributeError:
- # total_seconds is new in Python 2.7. :(
- secs = dt.seconds + dt.days*24*3600
- total = self.quiz.duration*60.0
- remain = max(total - secs, 0)
- return int(remain)
-
- def get_answered_str(self):
- """Returns the answered questions, sorted and as a nice string."""
- qa = self.questions_answered.split('|')
- answered = ', '.join(sorted(qa))
- return answered if answered else 'None'
-
- def get_total_marks(self):
- """Returns the total marks earned by student for this paper."""
- return sum([x.marks for x in self.answers.filter(marks__gt=0.0)])
-
- def get_question_answers(self):
- """Return a dictionary with keys as questions and a list of the corresponding
- answers.
- """
- q_a = {}
- for answer in self.answers.all():
- question = answer.question
- if question in q_a:
- q_a[question].append(answer)
- else:
- q_a[question] = [answer]
- return q_a
-
- def __unicode__(self):
- u = self.user
- return u'Question paper for {0} {1}'.format(u.first_name, u.last_name)
-
diff --git a/exam/tests.py b/exam/tests.py
deleted file mode 100644
index 501deb7..0000000
--- a/exam/tests.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
-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
deleted file mode 100644
index 34e329f..0000000
--- a/exam/urls.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.conf.urls.defaults import patterns, include, url
-
-urlpatterns = patterns('exam.views',
- url(r'^$', 'index'),
- url(r'^login/$', 'user_login'),
- url(r'^register/$', 'user_register'),
- url(r'^start/$', 'start'),
- url(r'^quit/$', 'quit'),
- url(r'^complete/$', 'complete'),
- url(r'^monitor/$', 'monitor'),
- url(r'^monitor/(?P<quiz_id>\d+)/$', 'monitor'),
- url(r'^user_data/(?P<username>[a-zA-Z0-9_.]+)/$', 'user_data'),
- url(r'^grade_user/(?P<username>[a-zA-Z0-9_.]+)/$', 'grade_user'),
- 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
deleted file mode 100644
index c178a0b..0000000
--- a/exam/views.py
+++ /dev/null
@@ -1,351 +0,0 @@
-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))
-
diff --git a/exam/xmlrpc_clients.py b/exam/xmlrpc_clients.py
deleted file mode 100644
index 817e37d..0000000
--- a/exam/xmlrpc_clients.py
+++ /dev/null
@@ -1,78 +0,0 @@
-from xmlrpclib import ServerProxy
-import time
-import random
-import socket
-
-from settings import SERVER_PORTS, SERVER_POOL_PORT
-
-
-class ConnectionError(Exception):
- pass
-
-################################################################################
-# `CodeServerProxy` class.
-################################################################################
-class CodeServerProxy(object):
- """A class that manages accesing the farm of Python servers and making
- calls to them such that no one XMLRPC server is overloaded.
- """
- def __init__(self):
- pool_url = 'http://localhost:%d'%(SERVER_POOL_PORT)
- self.pool_server = ServerProxy(pool_url)
- self.methods = {"python": 'run_python_code',
- "bash": 'run_bash_code'}
-
- def run_code(self, answer, test_code, user_dir, language):
- """Tests given code (`answer`) with the `test_code` supplied. If the
- optional `in_dir` keyword argument is supplied it changes the directory
- to that directory (it does not change it back to the original when
- done). The parameter language specifies which language to use for the
- tests.
-
- Parameters
- ----------
- answer : str
- The user's answer for the question.
- test_code : str
- The test code to check the user code with.
- user_dir : str (directory)
- The directory to run the tests inside.
- language : str
- The programming language to use.
-
- Returns
- -------
- A tuple: (success, error message).
- """
- method_name = self.methods[language]
- try:
- server = self._get_server()
- method = getattr(server, method_name)
- result = method(answer, test_code, user_dir)
- except ConnectionError:
- result = [False, 'Unable to connect to any code servers!']
- return result
-
- def _get_server(self):
- # Get a suitable server from our pool of servers. This may block. We
- # try about 60 times, essentially waiting at most for about 30 seconds.
- done, count = False, 60
-
- while not done and count > 0:
- try:
- port = self.pool_server.get_server_port()
- except socket.error:
- # Wait a while try again.
- time.sleep(random.random())
- count -= 1
- else:
- done = True
- if not done:
- raise ConnectionError("Couldn't connect to a server!")
- proxy = ServerProxy('http://localhost:%d'%port)
- return proxy
-
-# views.py calls this Python server which forwards the request to one
-# of the running servers.
-code_server = CodeServerProxy()
-