diff options
author | parth | 2011-12-09 04:02:19 +0530 |
---|---|---|
committer | parth | 2011-12-09 04:02:19 +0530 |
commit | 861d2cc5e36835f60bace61a919e73b4bd27274b (patch) | |
tree | d08d776f58167903781c371d147f5b9e1f83e168 /exam | |
parent | 7104f495d01fb934af11c8dfd09da087174c1b12 (diff) | |
download | online_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__.py | 0 | ||||
-rw-r--r-- | exam/admin.py | 5 | ||||
-rw-r--r-- | exam/forms.py | 95 | ||||
-rw-r--r-- | exam/management/__init__.py | 0 | ||||
-rw-r--r-- | exam/management/commands/__init__.py | 0 | ||||
-rw-r--r-- | exam/management/commands/dump_user_data.py | 98 | ||||
-rw-r--r-- | exam/management/commands/load_exam.py | 55 | ||||
-rw-r--r-- | exam/management/commands/load_questions_xml.py | 73 | ||||
-rw-r--r-- | exam/management/commands/results2csv.py | 69 | ||||
-rw-r--r-- | exam/migrations/0001_initial.py | 193 | ||||
-rw-r--r-- | exam/migrations/__init__.py | 0 | ||||
-rw-r--r-- | exam/models.py | 221 | ||||
-rw-r--r-- | exam/tests.py | 16 | ||||
-rw-r--r-- | exam/urls.py | 16 | ||||
-rw-r--r-- | exam/views.py | 351 | ||||
-rw-r--r-- | exam/xmlrpc_clients.py | 78 |
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() - |