summaryrefslogtreecommitdiff
path: root/yaksh
diff options
context:
space:
mode:
Diffstat (limited to 'yaksh')
-rw-r--r--yaksh/demo_templates/demo_settings.py70
-rw-r--r--yaksh/demo_templates/demo_urls.py7
-rw-r--r--yaksh/evaluator_tests/test_python_evaluation.py10
-rw-r--r--yaksh/fixtures/demo_fixtures.json190
-rw-r--r--yaksh/fixtures/demo_questions.zipbin1510 -> 4430 bytes
-rw-r--r--yaksh/models.py64
-rw-r--r--yaksh/python_assertion_evaluator.py2
-rw-r--r--yaksh/scripts/cli.py14
-rw-r--r--yaksh/static/yaksh/css/dashboard.css89
-rw-r--r--yaksh/templates/base.html13
-rw-r--r--yaksh/templates/exam.html78
-rw-r--r--yaksh/templates/manage.html11
-rw-r--r--yaksh/templates/user.html39
-rw-r--r--yaksh/templates/yaksh/editprofile.html19
-rw-r--r--yaksh/templates/yaksh/question.html185
-rw-r--r--yaksh/templates/yaksh/quizzes_user.html120
-rw-r--r--yaksh/templates/yaksh/view_answerpaper.html2
-rw-r--r--yaksh/templates/yaksh/view_profile.html13
-rw-r--r--yaksh/test_models.py56
-rw-r--r--yaksh/urls.py1
-rw-r--r--yaksh/views.py48
21 files changed, 707 insertions, 324 deletions
diff --git a/yaksh/demo_templates/demo_settings.py b/yaksh/demo_templates/demo_settings.py
index 4e12463..3c38794 100644
--- a/yaksh/demo_templates/demo_settings.py
+++ b/yaksh/demo_templates/demo_settings.py
@@ -10,8 +10,13 @@ https://docs.djangoproject.com/en/1.6/ref/settings/
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
+import tempfile
+from yaksh.pipeline.settings import AUTH_PIPELINE
+
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
+# The directory where user data can be saved.
+OUTPUT_DIR = os.path.join(tempfile.gettempdir(), 'output')
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/
@@ -22,10 +27,9 @@ SECRET_KEY = 'TH!S_!S_@_DUMMY_K3Y'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-TEMPLATE_DEBUG = True
-
ALLOWED_HOSTS = []
+URL_ROOT = ''
# Application definition
@@ -40,6 +44,7 @@ INSTALLED_APPS = (
'django.contrib.staticfiles',
'yaksh',
'taggit',
+ 'social.apps.django_app.default',
)
MIDDLEWARE_CLASSES = (
@@ -49,6 +54,9 @@ MIDDLEWARE_CLASSES = (
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'yaksh.middleware.one_session_per_user.OneSessionPerUserMiddleware',
+ 'yaksh.middleware.user_time_zone.TimezoneMiddleware',
+ 'social.apps.django_app.middleware.SocialAuthExceptionMiddleware',
)
ROOT_URLCONF = '{{ root_urlconf }}'
@@ -77,7 +85,7 @@ USE_I18N = True
USE_L10N = True
-USE_TZ = False
+USE_TZ = True
# Static files (CSS, JavaScript, Images)
@@ -85,4 +93,58 @@ USE_TZ = False
STATIC_URL = '/static/'
-AUTH_PROFILE_MODULE = 'yaksh.Profile'
+LOGIN_URL = '/exam/login/'
+
+LOGIN_REDIRECT_URL = '/exam/'
+
+MEDIA_URL = "/data/"
+
+MEDIA_ROOT = os.path.join(BASE_DIR, "yaksh", "data")
+
+SOCIAL_AUTH_LOGIN_ERROR_URL = '/exam/login/'
+
+EMAIL_USE_TLS = False
+
+EMAIL_HOST = 'your_email_host'
+
+EMAIL_PORT = 'your_email_port'
+
+EMAIL_HOST_USER = 'email_host_user'
+
+DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'APP_DIRS': True,
+ 'DIRS': ['yaksh/templates'],
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.contrib.auth.context_processors.auth',
+ 'social.apps.django_app.context_processors.backends',
+ 'social.apps.django_app.context_processors.login_redirect',
+ ],
+ 'debug': False,
+ }
+ },
+]
+
+SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'GOOGLE_KEY_PROVIDED'
+SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'GOOGLE_SECRET_PROVIDED'
+
+SOCIAL_AUTH_FACEBOOK_KEY = 'FACEBOOK_KEY_PROVIDED'
+SOCIAL_AUTH_FACEBOOK_SECRET = 'FACEBOOK_SECRET_PROVIDED'
+
+AUTHENTICATION_BACKENDS = (
+ 'social.backends.google.GoogleOAuth2',
+ 'social.backends.facebook.FacebookOAuth2',
+ 'django.contrib.auth.backends.ModelBackend',
+)
+
+SOCIAL_AUTH_PIPELINE = AUTH_PIPELINE
+
+SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
+SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
+ 'fields': 'id, name, email'
+
+}
diff --git a/yaksh/demo_templates/demo_urls.py b/yaksh/demo_templates/demo_urls.py
index d99e473..5abc121 100644
--- a/yaksh/demo_templates/demo_urls.py
+++ b/yaksh/demo_templates/demo_urls.py
@@ -1,5 +1,6 @@
from django.conf.urls import patterns, include, url
-
+from django.conf import settings
+from django.conf.urls.static import static
from django.contrib import admin
admin.autodiscover()
@@ -9,5 +10,7 @@ urlpatterns = patterns('',
# url(r'^blog/', include('blog.urls')),
url(r'^admin/', include(admin.site.urls)),
- url(r'^exam/', include('yaksh.urls')),
+ url(r'^exam/', include('yaksh.urls', namespace='yaksh', app_name='yaksh')),
+ url(r'^', include('social.apps.django_app.urls', namespace='social')),
)
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file
diff --git a/yaksh/evaluator_tests/test_python_evaluation.py b/yaksh/evaluator_tests/test_python_evaluation.py
index c58d7f1..43dfe6b 100644
--- a/yaksh/evaluator_tests/test_python_evaluation.py
+++ b/yaksh/evaluator_tests/test_python_evaluation.py
@@ -76,13 +76,13 @@ class PythonAssertionEvaluationTestCases(EvaluatorBaseTest):
# Then
self.assertFalse(result.get('success'))
- self.assert_correct_output('AssertionError in: assert(add(1,2)==3)',
+ self.assert_correct_output('AssertionError in:\n assert(add(1,2)==3)',
result.get('error')
)
- self.assert_correct_output('AssertionError in: assert(add(-1,2)==1)',
+ self.assert_correct_output('AssertionError in:\n assert(add(-1,2)==1)',
result.get('error')
)
- self.assert_correct_output('AssertionError in: assert(add(-1,-2)==-3)',
+ self.assert_correct_output('AssertionError in:\n assert(add(-1,-2)==-3)',
result.get('error')
)
@@ -110,10 +110,10 @@ class PythonAssertionEvaluationTestCases(EvaluatorBaseTest):
# Then
self.assertFalse(result.get('success'))
self.assertEqual(result.get('weight'), 2.0)
- self.assert_correct_output('AssertionError in: assert(add(-1,2)==1)',
+ self.assert_correct_output('AssertionError in:\n assert(add(-1,2)==1)',
result.get('error')
)
- self.assert_correct_output('AssertionError in: assert(add(-1,-2)==-3)',
+ self.assert_correct_output('AssertionError in:\n assert(add(-1,-2)==-3)',
result.get('error')
)
diff --git a/yaksh/fixtures/demo_fixtures.json b/yaksh/fixtures/demo_fixtures.json
index 517c2b2..779d4f9 100644
--- a/yaksh/fixtures/demo_fixtures.json
+++ b/yaksh/fixtures/demo_fixtures.json
@@ -1 +1,189 @@
-[{"pk": 1, "model": "contenttypes.contenttype", "fields": {"model": "logentry", "name": "log entry", "app_label": "admin"}}, {"pk": 2, "model": "contenttypes.contenttype", "fields": {"model": "permission", "name": "permission", "app_label": "auth"}}, {"pk": 3, "model": "contenttypes.contenttype", "fields": {"model": "group", "name": "group", "app_label": "auth"}}, {"pk": 4, "model": "contenttypes.contenttype", "fields": {"model": "user", "name": "user", "app_label": "auth"}}, {"pk": 5, "model": "contenttypes.contenttype", "fields": {"model": "contenttype", "name": "content type", "app_label": "contenttypes"}}, {"pk": 6, "model": "contenttypes.contenttype", "fields": {"model": "session", "name": "session", "app_label": "sessions"}}, {"pk": 7, "model": "contenttypes.contenttype", "fields": {"model": "profile", "name": "profile", "app_label": "yaksh"}}, {"pk": 8, "model": "contenttypes.contenttype", "fields": {"model": "question", "name": "question", "app_label": "yaksh"}}, {"pk": 9, "model": "contenttypes.contenttype", "fields": {"model": "answer", "name": "answer", "app_label": "yaksh"}}, {"pk": 10, "model": "contenttypes.contenttype", "fields": {"model": "quiz", "name": "quiz", "app_label": "yaksh"}}, {"pk": 11, "model": "contenttypes.contenttype", "fields": {"model": "questionpaper", "name": "question paper", "app_label": "yaksh"}}, {"pk": 12, "model": "contenttypes.contenttype", "fields": {"model": "questionset", "name": "question set", "app_label": "yaksh"}}, {"pk": 13, "model": "contenttypes.contenttype", "fields": {"model": "answerpaper", "name": "answer paper", "app_label": "yaksh"}}, {"pk": 14, "model": "contenttypes.contenttype", "fields": {"model": "assignmentupload", "name": "assignment upload", "app_label": "yaksh"}}, {"pk": 15, "model": "contenttypes.contenttype", "fields": {"model": "testcase", "name": "test case", "app_label": "yaksh"}}, {"pk": 16, "model": "contenttypes.contenttype", "fields": {"model": "tag", "name": "Tag", "app_label": "taggit"}}, {"pk": 17, "model": "contenttypes.contenttype", "fields": {"model": "taggeditem", "name": "Tagged Item", "app_label": "taggit"}}, {"pk": "kib1c35u44b8wleb8nltl5rkqwcsyrhr", "model": "sessions.session", "fields": {"expire_date": "2015-10-22T06:05:31.984", "session_data": "YjY4Mjg0Yjg4ZGI3OTIxYjc5NTY5NmNkMTgyMTQ4MGRmOTViNDRmOTp7fQ=="}}, {"pk": 1, "model": "yaksh.question", "fields": {"ref_code_path": "", "description": "\nWrite a function called <code>fact</code> which takes a single integer argument\n(say <code>n</code>) and returns the factorial of the number. \nFor example:<br/>\n<code>fact(3) -> 6</code>\n", "language": "python", "summary": "Factorial", "snippet": "def fact(num):", "active": true, "points": 2.0, "test": "\nassert fact(0) == 1\nassert fact(5) == 120\n", "type": "code", "options": ""}}, {"pk": 2, "model": "yaksh.question", "fields": {"ref_code_path": "", "description": "Create a simple function called <code>sqr</code> which takes a single \nargument and returns the square of the argument. For example: <br/>\n<code>sqr(3) -> 9</code>.", "language": "python", "summary": "Simple function", "snippet": "def sqr(num):", "active": true, "points": 1.0, "test": "\nimport math\nassert sqr(3) == 9\nassert abs(sqr(math.sqrt(2)) - 2.0) < 1e-14 \n ", "type": "code", "options": ""}}, {"pk": 3, "model": "yaksh.question", "fields": {"ref_code_path": "", "description": "Write a shell script which takes two arguments on the\n command line and prints the sum of the two on the output.", "language": "bash", "summary": "Bash addition", "snippet": "#!/bin/bash", "active": true, "points": 2.0, "test": "docs/sample.sh\ndocs/sample.args\n", "type": "code", "options": ""}}, {"pk": 4, "model": "yaksh.question", "fields": {"ref_code_path": "", "description": "What is the largest integer value that can be represented\nin Python?", "language": "python", "summary": "Size of integer in Python", "snippet": "", "active": true, "points": 0.5, "test": "No Limit", "type": "mcq", "options": "No Limit\n2**32\n2**32 - 1\nNone of the above\n"}}, {"pk": 1, "model": "yaksh.quiz", "fields": {"start_date_time": "2015-10-08T05:54:38", "description": "Demo Quiz", "language": "python", "time_between_attempts": 0, "attempts_allowed": -1, "pass_criteria": 40.0, "active": true, "end_date_time": "2199-01-01T00:00:00", "duration": 20, "prerequisite": null}}, {"pk": 1, "model": "yaksh.questionpaper", "fields": {"shuffle_questions": false, "total_marks": 5.5, "fixed_questions": [1, 2, 3, 4], "random_questions": [], "quiz": 1}}, {"pk": 1, "model": "taggit.tag", "fields": {"name": "Python", "slug": "python"}}, {"pk": 2, "model": "taggit.tag", "fields": {"name": "function", "slug": "function"}}, {"pk": 3, "model": "taggit.tag", "fields": {"name": "factorial", "slug": "factorial"}}, {"pk": 4, "model": "taggit.tag", "fields": {"name": "", "slug": ""}}, {"pk": 5, "model": "taggit.tag", "fields": {"name": "mcq", "slug": "mcq"}}, {"pk": 1, "model": "taggit.taggeditem", "fields": {"tag": 1, "content_type": 8, "object_id": 1}}, {"pk": 2, "model": "taggit.taggeditem", "fields": {"tag": 2, "content_type": 8, "object_id": 1}}, {"pk": 3, "model": "taggit.taggeditem", "fields": {"tag": 3, "content_type": 8, "object_id": 1}}, {"pk": 4, "model": "taggit.taggeditem", "fields": {"tag": 1, "content_type": 8, "object_id": 2}}, {"pk": 5, "model": "taggit.taggeditem", "fields": {"tag": 2, "content_type": 8, "object_id": 2}}, {"pk": 6, "model": "taggit.taggeditem", "fields": {"tag": 4, "content_type": 8, "object_id": 3}}, {"pk": 7, "model": "taggit.taggeditem", "fields": {"tag": 5, "content_type": 8, "object_id": 4}}, {"pk": 1, "model": "auth.permission", "fields": {"codename": "add_logentry", "name": "Can add log entry", "content_type": 1}}, {"pk": 2, "model": "auth.permission", "fields": {"codename": "change_logentry", "name": "Can change log entry", "content_type": 1}}, {"pk": 3, "model": "auth.permission", "fields": {"codename": "delete_logentry", "name": "Can delete log entry", "content_type": 1}}, {"pk": 4, "model": "auth.permission", "fields": {"codename": "add_permission", "name": "Can add permission", "content_type": 2}}, {"pk": 5, "model": "auth.permission", "fields": {"codename": "change_permission", "name": "Can change permission", "content_type": 2}}, {"pk": 6, "model": "auth.permission", "fields": {"codename": "delete_permission", "name": "Can delete permission", "content_type": 2}}, {"pk": 7, "model": "auth.permission", "fields": {"codename": "add_group", "name": "Can add group", "content_type": 3}}, {"pk": 8, "model": "auth.permission", "fields": {"codename": "change_group", "name": "Can change group", "content_type": 3}}, {"pk": 9, "model": "auth.permission", "fields": {"codename": "delete_group", "name": "Can delete group", "content_type": 3}}, {"pk": 10, "model": "auth.permission", "fields": {"codename": "add_user", "name": "Can add user", "content_type": 4}}, {"pk": 11, "model": "auth.permission", "fields": {"codename": "change_user", "name": "Can change user", "content_type": 4}}, {"pk": 12, "model": "auth.permission", "fields": {"codename": "delete_user", "name": "Can delete user", "content_type": 4}}, {"pk": 13, "model": "auth.permission", "fields": {"codename": "add_contenttype", "name": "Can add content type", "content_type": 5}}, {"pk": 14, "model": "auth.permission", "fields": {"codename": "change_contenttype", "name": "Can change content type", "content_type": 5}}, {"pk": 15, "model": "auth.permission", "fields": {"codename": "delete_contenttype", "name": "Can delete content type", "content_type": 5}}, {"pk": 16, "model": "auth.permission", "fields": {"codename": "add_session", "name": "Can add session", "content_type": 6}}, {"pk": 17, "model": "auth.permission", "fields": {"codename": "change_session", "name": "Can change session", "content_type": 6}}, {"pk": 18, "model": "auth.permission", "fields": {"codename": "delete_session", "name": "Can delete session", "content_type": 6}}, {"pk": 19, "model": "auth.permission", "fields": {"codename": "add_profile", "name": "Can add profile", "content_type": 7}}, {"pk": 20, "model": "auth.permission", "fields": {"codename": "change_profile", "name": "Can change profile", "content_type": 7}}, {"pk": 21, "model": "auth.permission", "fields": {"codename": "delete_profile", "name": "Can delete profile", "content_type": 7}}, {"pk": 22, "model": "auth.permission", "fields": {"codename": "add_question", "name": "Can add question", "content_type": 8}}, {"pk": 23, "model": "auth.permission", "fields": {"codename": "change_question", "name": "Can change question", "content_type": 8}}, {"pk": 24, "model": "auth.permission", "fields": {"codename": "delete_question", "name": "Can delete question", "content_type": 8}}, {"pk": 25, "model": "auth.permission", "fields": {"codename": "add_answer", "name": "Can add answer", "content_type": 9}}, {"pk": 26, "model": "auth.permission", "fields": {"codename": "change_answer", "name": "Can change answer", "content_type": 9}}, {"pk": 27, "model": "auth.permission", "fields": {"codename": "delete_answer", "name": "Can delete answer", "content_type": 9}}, {"pk": 28, "model": "auth.permission", "fields": {"codename": "add_quiz", "name": "Can add quiz", "content_type": 10}}, {"pk": 29, "model": "auth.permission", "fields": {"codename": "change_quiz", "name": "Can change quiz", "content_type": 10}}, {"pk": 30, "model": "auth.permission", "fields": {"codename": "delete_quiz", "name": "Can delete quiz", "content_type": 10}}, {"pk": 31, "model": "auth.permission", "fields": {"codename": "add_questionpaper", "name": "Can add question paper", "content_type": 11}}, {"pk": 32, "model": "auth.permission", "fields": {"codename": "change_questionpaper", "name": "Can change question paper", "content_type": 11}}, {"pk": 33, "model": "auth.permission", "fields": {"codename": "delete_questionpaper", "name": "Can delete question paper", "content_type": 11}}, {"pk": 34, "model": "auth.permission", "fields": {"codename": "add_questionset", "name": "Can add question set", "content_type": 12}}, {"pk": 35, "model": "auth.permission", "fields": {"codename": "change_questionset", "name": "Can change question set", "content_type": 12}}, {"pk": 36, "model": "auth.permission", "fields": {"codename": "delete_questionset", "name": "Can delete question set", "content_type": 12}}, {"pk": 37, "model": "auth.permission", "fields": {"codename": "add_answerpaper", "name": "Can add answer paper", "content_type": 13}}, {"pk": 38, "model": "auth.permission", "fields": {"codename": "change_answerpaper", "name": "Can change answer paper", "content_type": 13}}, {"pk": 39, "model": "auth.permission", "fields": {"codename": "delete_answerpaper", "name": "Can delete answer paper", "content_type": 13}}, {"pk": 40, "model": "auth.permission", "fields": {"codename": "add_assignmentupload", "name": "Can add assignment upload", "content_type": 14}}, {"pk": 41, "model": "auth.permission", "fields": {"codename": "change_assignmentupload", "name": "Can change assignment upload", "content_type": 14}}, {"pk": 42, "model": "auth.permission", "fields": {"codename": "delete_assignmentupload", "name": "Can delete assignment upload", "content_type": 14}}, {"pk": 43, "model": "auth.permission", "fields": {"codename": "add_testcase", "name": "Can add test case", "content_type": 15}}, {"pk": 44, "model": "auth.permission", "fields": {"codename": "change_testcase", "name": "Can change test case", "content_type": 15}}, {"pk": 45, "model": "auth.permission", "fields": {"codename": "delete_testcase", "name": "Can delete test case", "content_type": 15}}, {"pk": 46, "model": "auth.permission", "fields": {"codename": "add_tag", "name": "Can add Tag", "content_type": 16}}, {"pk": 47, "model": "auth.permission", "fields": {"codename": "change_tag", "name": "Can change Tag", "content_type": 16}}, {"pk": 48, "model": "auth.permission", "fields": {"codename": "delete_tag", "name": "Can delete Tag", "content_type": 16}}, {"pk": 49, "model": "auth.permission", "fields": {"codename": "add_taggeditem", "name": "Can add Tagged Item", "content_type": 17}}, {"pk": 50, "model": "auth.permission", "fields": {"codename": "change_taggeditem", "name": "Can change Tagged Item", "content_type": 17}}, {"pk": 51, "model": "auth.permission", "fields": {"codename": "delete_taggeditem", "name": "Can delete Tagged Item", "content_type": 17}}, {"pk": 1, "model": "auth.group", "fields": {"name": "moderator", "permissions": [25, 26, 27, 37, 38, 39, 40, 41, 42, 19, 20, 21, 22, 23, 24, 31, 32, 33, 34, 35, 36, 28, 29, 30, 43, 44, 45]}}, {"pk": 1, "model": "auth.user", "fields": {"username": "admin", "first_name": "", "last_name": "", "is_active": true, "is_superuser": true, "is_staff": true, "last_login": "2015-10-08T06:01:15.263", "groups": [1], "user_permissions": [], "password": "pbkdf2_sha256$12000$qartiEZkS5K0$wP0uhbdYEcuvRXAveA2geMUcknL11UzwL+TS2r2pL2I=", "email": "admin@admin.com", "date_joined": "2015-10-08T05:51:13"}}, {"pk": 2, "model": "auth.user", "fields": {"username": "student", "first_name": "Student", "last_name": "Yaksh", "is_active": true, "is_superuser": false, "is_staff": false, "last_login": "2015-10-08T06:05:29.551", "groups": [], "user_permissions": [], "password": "pbkdf2_sha256$12000$iRBPv5gvit8o$xKheYrWGGL4c3WN/F7j5UlRkmjAy9hx1BWWhdhd9uh0=", "email": "student@yaksh.xyz", "date_joined": "2015-10-08T05:55:56.956"}}, {"pk": 3, "model": "auth.user", "fields": {"username": "teacher", "first_name": "Teacher", "last_name": "Yaksh", "is_active": true, "is_superuser": false, "is_staff": false, "last_login": "2015-10-08T06:01:51.536", "groups": [1], "user_permissions": [], "password": "pbkdf2_sha256$12000$J6BkGV3t0Ajz$Q0LPr2588cYNBjWsw+PyY+Kzc4FcrWojtZek5iZ4LA8=", "email": "teacher@yaksh.xyz", "date_joined": "2015-10-08T05:57:52"}}, {"pk": 1, "model": "yaksh.profile", "fields": {"institute": "Demo Institute", "department": "Demo Department", "roll_number": "1", "user": 2, "position": "Student"}}, {"pk": 2, "model": "yaksh.profile", "fields": {"institute": "Demo Institute", "department": "Demo Department", "roll_number": "2", "user": 3, "position": "Teacher"}}, {"pk": 1, "model": "admin.logentry", "fields": {"action_flag": 1, "action_time": "2015-10-08T05:58:41.912", "object_repr": "moderators", "object_id": "1", "change_message": "", "user": 1, "content_type": 3}}, {"pk": 2, "model": "admin.logentry", "fields": {"action_flag": 2, "action_time": "2015-10-08T05:59:22.724", "object_repr": "admin", "object_id": "1", "change_message": "Changed groups.", "user": 1, "content_type": 4}}, {"pk": 3, "model": "admin.logentry", "fields": {"action_flag": 2, "action_time": "2015-10-08T05:59:30.335", "object_repr": "teacher", "object_id": "3", "change_message": "Changed groups.", "user": 1, "content_type": 4}}, {"pk": 4, "model": "admin.logentry", "fields": {"action_flag": 2, "action_time": "2015-10-08T06:01:24.134", "object_repr": "moderator", "object_id": "1", "change_message": "Changed name.", "user": 1, "content_type": 3}}] \ No newline at end of file
+[
+{
+ "model": "auth.group",
+ "pk": 1,
+ "fields": {
+ "name": "moderator",
+ "permissions": [
+ 34,
+ 35,
+ 36,
+ 46,
+ 47,
+ 48,
+ 49,
+ 50,
+ 51,
+ 22,
+ 23,
+ 24,
+ 19,
+ 20,
+ 21,
+ 31,
+ 32,
+ 33,
+ 64,
+ 65,
+ 66,
+ 61,
+ 62,
+ 63,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 37,
+ 38,
+ 39,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 52,
+ 53,
+ 54
+ ]
+ }
+},
+{
+ "model": "auth.user",
+ "pk": 1,
+ "fields": {
+ "password": "pbkdf2_sha256$24000$MmYQQZFeXKAZ$yA6tEVLCd7HFwN5Zho6yUBxxm4ZpT04Rvo1/+K2lvlk=",
+ "last_login": "2017-01-09T05:55:35.578Z",
+ "is_superuser": true,
+ "username": "admin",
+ "first_name": "",
+ "last_name": "",
+ "email": "admin@mail.com",
+ "is_staff": true,
+ "is_active": true,
+ "date_joined": "2017-01-09T05:53:08.743Z",
+ "groups": [],
+ "user_permissions": []
+ }
+},
+{
+ "model": "auth.user",
+ "pk": 2,
+ "fields": {
+ "password": "pbkdf2_sha256$24000$VG6rNbuxpOht$epkN0rKp63rr14/0eHZPxB424CM62VZqiiB5SuEaZaQ=",
+ "last_login": "2017-01-09T05:58:13.210Z",
+ "is_superuser": false,
+ "username": "teacher",
+ "first_name": "Teacher",
+ "last_name": "Teacher",
+ "email": "teacher@mail.com",
+ "is_staff": false,
+ "is_active": true,
+ "date_joined": "2017-01-09T05:55:19Z",
+ "groups": [
+ 1
+ ],
+ "user_permissions": []
+ }
+},
+{
+ "model": "auth.user",
+ "pk": 3,
+ "fields": {
+ "password": "pbkdf2_sha256$24000$uvLwahv56EeP$2PVrFFhzeM+7VFwygYIg3eXRefBpaUhf+xXUuzs6w8E=",
+ "last_login": "2017-01-09T05:58:29.755Z",
+ "is_superuser": false,
+ "username": "student",
+ "first_name": "Student",
+ "last_name": "Student",
+ "email": "student@mail.com",
+ "is_staff": false,
+ "is_active": true,
+ "date_joined": "2017-01-09T05:57:02.954Z",
+ "groups": [],
+ "user_permissions": []
+ }
+},
+{
+ "model": "yaksh.concurrentuser",
+ "pk": 1,
+ "fields": {
+ "concurrent_user": 1,
+ "session_key": "jxiiylbd89c89o4gdym3pv99ctdbiykg"
+ }
+},
+{
+ "model": "yaksh.concurrentuser",
+ "pk": 2,
+ "fields": {
+ "concurrent_user": 2,
+ "session_key": "usatgwitj5kqz7pehukgujjtwqpr6j3f"
+ }
+},
+{
+ "model": "yaksh.concurrentuser",
+ "pk": 3,
+ "fields": {
+ "concurrent_user": 3,
+ "session_key": "s4iw5uwdutojr2a8bgu36x5qda1ctb69"
+ }
+},
+{
+ "model": "yaksh.profile",
+ "pk": 1,
+ "fields": {
+ "user": 2,
+ "roll_number": "12345",
+ "institute": "Demo",
+ "department": "Demo",
+ "position": "Faculty",
+ "timezone": "UTC"
+ }
+},
+{
+ "model": "yaksh.profile",
+ "pk": 2,
+ "fields": {
+ "user": 3,
+ "roll_number": "1234",
+ "institute": "demo",
+ "department": "demo",
+ "position": "Student",
+ "timezone": "UTC"
+ }
+},
+{
+ "model": "admin.logentry",
+ "pk": 1,
+ "fields": {
+ "action_time": "2017-01-09T05:53:57.249Z",
+ "user": 1,
+ "content_type": 3,
+ "object_id": "1",
+ "object_repr": "moderator",
+ "action_flag": 2,
+ "change_message": "No fields changed."
+ }
+},
+{
+ "model": "admin.logentry",
+ "pk": 2,
+ "fields": {
+ "action_time": "2017-01-09T05:55:45.973Z",
+ "user": 1,
+ "content_type": 4,
+ "object_id": "2",
+ "object_repr": "teacher",
+ "action_flag": 2,
+ "change_message": "Changed groups."
+ }
+}
+]
diff --git a/yaksh/fixtures/demo_questions.zip b/yaksh/fixtures/demo_questions.zip
index 562bf8a..c68e7ef 100644
--- a/yaksh/fixtures/demo_questions.zip
+++ b/yaksh/fixtures/demo_questions.zip
Binary files differ
diff --git a/yaksh/models.py b/yaksh/models.py
index d65970b..ca41885 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -1,3 +1,4 @@
+from __future__ import unicode_literals
from datetime import datetime, timedelta
import json
from random import sample, shuffle
@@ -22,6 +23,7 @@ from os.path import join, abspath, dirname, exists
import shutil
import zipfile
import tempfile
+from textwrap import dedent
from .file_utils import extract_files, delete_files
from yaksh.xmlrpc_clients import code_server
from django.conf import settings
@@ -65,6 +67,43 @@ test_status = (
('completed', 'Completed'),
)
+instructions_data = dedent("""\
+ <p>
+ This examination system has been developed with the intention of
+ making you learn programming and be assessed in an interactive and
+ fun manner. You will be presented with a series of programming questions
+ and problems that you will answer online and get immediate
+ feedback for.
+ </p>
+ <p>
+ Here are some important instructions and rules that you should
+ understand carefully.</p>
+ <ul>
+ <li>For any programming questions, you can submit solutions as many
+ times as you want without a penalty. You may skip questions
+ and solve them later.</li>
+ <li> You <strong>may</strong> use your computer's Python/IPython
+ shell or an editor to solve the problem and cut/paste the
+ solution to the web interface.
+ </li>
+ <li> <strong>You are not allowed to use any internet resources,
+ i.e. no google etc.</strong>
+ </li>
+ <li> Do not copy or share the questions or answers with anyone
+ until the exam is complete <strong>for everyone</strong>.
+ </li>
+ <li> <strong>All</strong> your attempts at the questions are logged.
+ Do not try to outsmart and break the testing system.
+ If you do, we know who you are and we will expel you from the
+ course. You have been warned.
+ </li>
+ </ul>
+ <p>
+ We hope you enjoy taking this
+ exam !!!
+ </p>
+ """)
+
def get_assignment_dir(instance, filename):
return '%s/%s/%s' % (instance.user.user, instance.assignmentQuestion.id, filename)
@@ -351,7 +390,7 @@ class Question(models.Model):
for file_name, extract in file_names:
q_file = os.path.join(path, file_name)
if os.path.exists(q_file):
- que_file = open(q_file, 'r')
+ que_file = open(q_file, 'rb')
# Converting to Python file object with
# some Django-specific additions
django_file = File(que_file)
@@ -378,8 +417,7 @@ class Question(models.Model):
self.load_questions(questions_list, user, file_path, files)
def create_demo_questions(self, user):
- zip_file_path = os.path.join(os.getcwd(), 'yaksh',
- 'fixtures', 'demo_questions.zip')
+ zip_file_path = os.path.join(settings.FIXTURE_DIRS, 'demo_questions.zip')
files, extract_path = extract_files(zip_file_path)
self.read_json(extract_path, user, files)
@@ -564,6 +602,7 @@ class Quiz(models.Model):
end_date_time=timezone.now() + timedelta(176590),
duration=30, active=True,
attempts_allowed=-1,
+ instructions=instructions_data,
time_between_attempts=0,
description='Yaksh Demo quiz', pass_criteria=0,
language='Python', prerequisite=None,
@@ -702,7 +741,7 @@ class QuestionPaper(models.Model):
def create_demo_quiz_ppr(self, demo_quiz, user):
question_paper = QuestionPaper.objects.create(quiz=demo_quiz,
- total_marks=7.0,
+ total_marks=6.0,
shuffle_questions=True
)
questions = Question.objects.filter(active=True,
@@ -1059,28 +1098,25 @@ class AnswerPaper(models.Model):
For code questions success is True only if the answer is correct.
"""
- result = {'success': True, 'error': ['Incorrect answer'], 'weight': 0.0}
- correct = False
+ result = {'success': False, 'error': ['Incorrect answer'], 'weight': 0.0}
if user_answer is not None:
if question.type == 'mcq':
expected_answer = question.get_test_case(correct=True).options
if user_answer.strip() == expected_answer.strip():
- correct = True
+ result['success'] = True
result['error'] = ['Correct answer']
elif question.type == 'mcc':
expected_answers = []
for opt in question.get_test_cases(correct=True):
expected_answers.append(opt.options)
if set(user_answer) == set(expected_answers):
+ result['success'] = True
result['error'] = ['Correct answer']
- correct = True
elif question.type == 'code':
user_dir = self.user.profile.get_user_dir()
json_result = code_server.run_code(question.language, json_data, user_dir)
result = json.loads(json_result)
- if result.get('success'):
- correct = True
- return correct, result
+ return result
def regrade(self, question_id):
try:
@@ -1105,10 +1141,10 @@ class AnswerPaper(models.Model):
answer = user_answer.answer
json_data = question.consolidate_answer_data(answer) \
if question.type == 'code' else None
- correct, result = self.validate_answer(answer, question, json_data)
- user_answer.correct = correct
+ result = self.validate_answer(answer, question, json_data)
+ user_answer.correct = result.get('success')
user_answer.error = result.get('error')
- if correct:
+ if result.get('success'):
user_answer.marks = (question.points * result['weight'] /
question.get_maximum_test_case_weight()) \
if question.partial_grading and question.type == 'code' else question.points
diff --git a/yaksh/python_assertion_evaluator.py b/yaksh/python_assertion_evaluator.py
index ae86c46..daf1afe 100644
--- a/yaksh/python_assertion_evaluator.py
+++ b/yaksh/python_assertion_evaluator.py
@@ -77,7 +77,7 @@ class PythonAssertionEvaluator(BaseEvaluator):
fname, lineno, func, text = info[-1]
text = str(self.test_case)
err = "Expected Test Case:\n{0}\n" \
- "Error - {1} {2} in: {3}\n".format(
+ "Error - {1} {2} in:\n {3}\n".format(
self.test_case,
type.__name__,
str(value),
diff --git a/yaksh/scripts/cli.py b/yaksh/scripts/cli.py
index 79523f9..1489af7 100644
--- a/yaksh/scripts/cli.py
+++ b/yaksh/scripts/cli.py
@@ -1,5 +1,6 @@
from __future__ import print_function
+import django
import subprocess
import contextlib
import os
@@ -8,15 +9,18 @@ import argparse
from importlib import import_module
from django.conf import settings
from django.core import management
-from django.template import Template, Context, loader
+from django.template import Template, Context
-from project_detail import NAME, PATH
+from .project_detail import NAME, PATH
CUR_DIR = os.getcwd()
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
PARENT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir))
TEMPLATE_DIR = path.join(PARENT_DIR, 'demo_templates')
+settings.configure()
+django.setup()
+
def main():
#Parse command-line to obtain the arguments and/or options
# create top-level parser object
@@ -72,7 +76,7 @@ def create_demo(project_name='yaksh_demo', project_dir=CUR_DIR):
management.call_command('startproject', project_name, project_dir)
print("Demo Django project '{0}' created at '{1}'".format(project_name,
project_dir))
- except Exception, e:
+ except Exception as e:
print("Error: {0}\nExiting yaksh Installer".format(e))
if project_dir is None:
@@ -95,7 +99,7 @@ def create_demo(project_name='yaksh_demo', project_dir=CUR_DIR):
'fixture_dir': fixture_dir})
urls_template_path = path.join(TEMPLATE_DIR, 'demo_urls.py')
urls_target_path = path.join(project_path, 'demo_urls.py')
- command = ("python ../manage.py syncdb "
+ command = ("python ../manage.py migrate --run-syncdb "
"--noinput --settings={0}.demo_settings").format(project_name)
loaddata_command = ("python ../manage.py loaddata "
@@ -132,10 +136,8 @@ def _render_demo_files(template_path, output_path, context=None):
with open(template_path, 'r') as template_file:
content = template_file.read()
if context:
- content = content.decode('utf-8')
template = Template(content)
content = template.render(context)
- content = content.encode('utf-8')
with open(output_path, 'w') as new_file:
new_file.write(content)
diff --git a/yaksh/static/yaksh/css/dashboard.css b/yaksh/static/yaksh/css/dashboard.css
new file mode 100644
index 0000000..28040c4
--- /dev/null
+++ b/yaksh/static/yaksh/css/dashboard.css
@@ -0,0 +1,89 @@
+/*
+ * Base structure
+ */
+
+/* Move down content because we have a fixed navbar that is 50px tall */
+body {
+ padding-top:30px;
+}
+
+
+/*
+ * Global add-ons
+ */
+
+.sub-header {
+ padding-bottom: 10px;
+ border-bottom: 1px solid #eee;
+}
+
+/*
+ * Top navigation
+ * Hide default border to remove 1px line.
+ */
+.navbar-fixed-top {
+ border: 0;
+}
+
+/*
+ * Sidebar
+ */
+
+@media (min-width: 768px) {
+ .sidebar {
+ position: fixed;
+ top: 51px;
+ bottom: 0;
+ left: 0;
+ z-index: 1000;
+ display: block;
+ padding: 20px;
+ overflow-x: hidden;
+ overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
+ background-color: #f5f5f5;
+ border-right: 1px solid #eee;
+ }
+}
+
+/* Sidebar navigation */
+.nav-sidebar {
+ margin-right: -21px; /* 20px padding + 1px border */
+ margin-bottom: 20px;
+ margin-left: -20px;
+}
+.nav-sidebar > li > a {
+ padding-right: 20px;
+ padding-left: 20px;
+}
+.nav-sidebar > .active > a,
+.nav-sidebar > .active > a:hover,
+.nav-sidebar > .active > a:focus {
+ color: #fff;
+ background-color: #428bca;
+}
+
+
+/*
+ * Main content
+ */
+
+.main {
+ padding: 0px;
+}
+@media (min-width: 768px) {
+ .main {
+ padding-right: 0px;
+ padding-left: 0px;
+ }
+}
+.main {
+ margin-top: 0;
+}
+.header {
+ margin: 30px 0 15px;
+ border-bottom: 1px solid #eee;
+}
+
+.sidebar-right {
+ float: right;
+}
diff --git a/yaksh/templates/base.html b/yaksh/templates/base.html
index 1609121..35c6976 100644
--- a/yaksh/templates/base.html
+++ b/yaksh/templates/base.html
@@ -18,14 +18,9 @@
<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/font-awesome.css" type="text/css" />
<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/theme.css" type="text/css" />
<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/sticky-footer.css" type="text/css" />
+ <link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/dashboard.css" type="text/css" />
- <style>
- body {
- padding-top: 20px;
- padding-bottom: 20px;
- }
- </style>
{% block meta %}
<meta charset="utf-8">
<meta name="description" content="">
@@ -45,15 +40,11 @@
{% endblock %}
<div class="container">
<div class="content">
- <div class="page-header">
+ <div class="header">
<h3><center>{% block pagetitle %}{% endblock pagetitle %}</center></h2>
</div>
- <div class=row>
- <div class=col-md-12>
{% block content %}
{% endblock %}
- </div>
- </div>
</div>
<footer class="footer">
<div class="container">
diff --git a/yaksh/templates/exam.html b/yaksh/templates/exam.html
new file mode 100644
index 0000000..19d760a
--- /dev/null
+++ b/yaksh/templates/exam.html
@@ -0,0 +1,78 @@
+{% extends "base.html" %}
+{% block css%}
+ <link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/dashboard.css" type="text/css" />
+{% endblock %}
+{% block nav %}
+ <nav class="navbar navbar-fixed-top navbar-inverse">
+ <div class="container">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="#"> Yaksh </a>
+ </div>
+ <div class= "collapse navbar-collapse" id="navbar">
+ <form id="logout" action="{{URL_ROOT}}/exam/quit/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/" method="post" class="pull-right">
+ {% csrf_token %}
+ <ul class="nav navbar-nav navbar">
+ <li style="padding: 10px"><button class="btn btn-danger btn-sm" type="submit" name="quit">Quit Exam <span class="glyphicon glyphicon-off"></span></button></li>
+ </ul>
+ </form>
+ <div class="time-div" id="time_left"></div>
+ </div><!-- /.navbar -->
+ </div><!-- /.container -->
+ </nav><!-- /.navbar -->
+{% endblock %}
+{% block content %}
+<div class="row">
+ <div class="col-sm-3 col-md-2 sidebar">
+ <p> Question Navigator </p>
+ <ul class="pagination pagination-sm">
+ {% for qid in paper.questions.all %}
+ {% if qid in paper.questions_unanswered.all %}
+ {% if qid.id == question.id %}
+ <li class="active"><a style="width:25%" href="#"data-toggle="tooltip"
+ title="{{ qid.description|striptags }}"
+ onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ qid.id }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')">{{ forloop.counter }}</a></li>
+ {% else %}
+ <li><a style="width:25%" href="#" data-toggle="tooltip" title="{{ qid.description|striptags }}"
+ onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ qid.id }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')">{{ forloop.counter }}</a></li>
+ {% endif %}
+ {% endif %}
+ {% if qid in paper.questions_answered.all %}
+ <li class="disabled"><a style="background-color:#B4B8BA; width:25%" href="#" data-toggle="tooltip" title="{{ qid.description }}" >{{ forloop.counter }}</a></li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ <p>Question(s) left: <b>{{ paper.questions_left }}</b></p>
+ </div>
+ <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
+ <div class="row">
+ <div class="col-md-8">
+ {% block main %}
+ {% endblock %}
+ </div>
+ {% if question.type == 'code' %}
+ {% if error_message %}
+ <div class="col-md-4">
+ {% for error in error_message %}
+ {% if error == "Correct answer" %}
+ <div class="panel panel-success">
+ {% else %}
+ <div class="panel panel-danger">
+ {% endif %}
+ <div class="panel-heading">Testcase No. {{ forloop.counter }}</div>
+ <div class="panel-body"><pre><code>{{ error }}</code></pre></div>
+ </div>
+ {% endfor %}
+
+ </div>
+ {% endif %}
+ {% endif %}
+ </div>
+ </div>
+ </div>
+</div>
+{% endblock %}
diff --git a/yaksh/templates/manage.html b/yaksh/templates/manage.html
index eae4dd4..9744594 100644
--- a/yaksh/templates/manage.html
+++ b/yaksh/templates/manage.html
@@ -3,9 +3,14 @@
<nav class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
<a class="navbar-brand" href="{{ URL_ROOT }}/exam/manage/"> Online Test </a>
</div>
- <div id="navbar">
+ <div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="{{ URL_ROOT }}/exam/manage/questions">Questions</a></li>
<li><a href="{{ URL_ROOT }}/exam/manage/gradeuser">Grade User</a></li>
@@ -20,3 +25,7 @@
</div><!-- /.container -->
</nav><!-- /.navbar -->
{% endblock %}
+{% block content %}
+ {% block main %}
+ {% endblock %}
+{% endblock %}
diff --git a/yaksh/templates/user.html b/yaksh/templates/user.html
index c18013b..6f2137d 100644
--- a/yaksh/templates/user.html
+++ b/yaksh/templates/user.html
@@ -1,19 +1,42 @@
{% extends "base.html" %}
+{% block css%}
+ <link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/dashboard.css" type="text/css" />
+{% endblock %}
{% block nav %}
<nav class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<div class="navbar-header">
- <a class="navbar-brand" href="{{ URL_ROOT }}/exam/manage/"> Online Test </a>
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="{{ URL_ROOT }}/exam/manage/"> Yaksh </a>
</div>
- <div id="navbar">
- <ul class="nav navbar-nav">
- <li><a href="{{ URL_ROOT }}/exam/quizzes">Quizzes</a></li>
- <li><a href="{{ URL_ROOT }}/exam/results">Results</a></li>
- <li><a href="{{ URL_ROOT }}/exam/viewprofile">My Profile</a></li>
- <li><a href="{{ URL_ROOT }}/exam/changepassword">Change Password</a></li>
- <li><a style='cursor:pointer' onClick='location.replace("{{URL_ROOT}}/exam/complete/");'>Log out</a></li>
+ <div class= "collapse navbar-collapse" id="navbar">
+ <ul class="nav navbar-nav navbar-right">
+ <li><a href="{{ URL_ROOT }}/exam/viewprofile"> {{ user.get_full_name.title }} </a></li>
+ <li><a style='cursor:pointer' onClick='location.replace("{{URL_ROOT}}/exam/complete/");'> <span class="glyphicon glyphicon-log-out">Logout </span></a></li>
</ul>
</div><!-- /.navbar -->
</div><!-- /.container -->
</nav><!-- /.navbar -->
{% endblock %}
+{% block content %}
+<div class="row">
+ <div class="col-sm-3 col-md-2 sidebar">
+ <ul class="nav nav-sidebar">
+ <li><a href="{{ URL_ROOT }}/exam/quizzes"> Home </a></li>
+ <li><a href="{{ URL_ROOT }}/exam/quizzes/enrolled/"> Enrolled Courses </a></li>
+ <li><a href="{{ URL_ROOT }}/exam/viewprofile"> Profile </a></li>
+ <li><a href="{{ URL_ROOT }}/exam/changepassword"> Change Password </a></li>
+ </ul>
+ </div>
+ <div class="col-sm-8 col-sm-offset-3 col-md-9 col-md-offset-2 main">
+ <div class="row">
+ {% block main %}
+ {% endblock %}
+ </div>
+ </div>
+</div>
+{% endblock %}
diff --git a/yaksh/templates/yaksh/editprofile.html b/yaksh/templates/yaksh/editprofile.html
index fc8539d..e5191ad 100644
--- a/yaksh/templates/yaksh/editprofile.html
+++ b/yaksh/templates/yaksh/editprofile.html
@@ -1,20 +1,13 @@
-{% extends "manage.html" %}
-
-{% block formtitle %} Please fill in the following details {% endblock %}
-
-{% block content %}
-{% if msg %}
-<center><h5>Your profile does not exist. Please fill in your details</h5></center>
-{% endif %}
+{% extends template %}
+{% block pagetitle %} Please fill in the following details {% endblock %}
+{% block main %}
<form action="{{URL_ROOT}}/exam/editprofile/" method="post" >
{% csrf_token %}
<center>
- <table class="span1">
+ <table class="table table-bordered">
{{ form.as_table }}
</table>
</center>
- <center><button class="btn" type="submit">Save</button>
- <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/");'>Cancel</button> </center>
+ <button class="btn btn-primary pull-right" type="submit">Save Profile</button>
</form>
-
-{% endblock content %}
+{% endblock main %}
diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html
index 7cbca57..41634f2 100644
--- a/yaksh/templates/yaksh/question.html
+++ b/yaksh/templates/yaksh/question.html
@@ -1,23 +1,16 @@
-{% extends "base.html" %}
+{% extends "exam.html" %}
{% load custom_filters %}
-{% block nav %}
-<nav class="navbar navbar-fixed-top">
- <div class="container">
- <div class="navbar-header">
- </div>
-
- <div id="navbar">
- <div class="time-div" id="time_left"></div>
- </ul>
- </div><!-- /.navbar -->
- </div><!-- /.container -->
- </nav><!-- /.navbar -->
-{% endblock %}
-{% block pagetitle %} Yaksh Online Test {% endblock pagetitle %}
+{% block pagetitle %} {{ paper.question_paper.quiz.description }} {% endblock pagetitle %}
{% block css %}
+<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/dashboard.css" type="text/css" />
<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/question.css" type="text/css" />
<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/codemirror/lib/codemirror.css" type="text/css" />
+<style>
+ .CodeMirror{
+ border-style: groove;
+ }
+</style>
{% endblock %}
{% block script %}
@@ -95,43 +88,52 @@ function call_skip(url)
form.submit();
}
</script>
+<script>
+ $(document).ready(function(){
+ // Codemirror object, language modes and initial content
+ // Get the textarea node
+ var textarea_node = document.querySelector('#answer');
+
+ var lang = "{{ question.language }}"
+ var mode_dict = {
+ 'python': 'python',
+ 'c': 'text/x-csrc',
+ 'cpp': 'text/x-c++src',
+ 'java': 'text/x-java',
+ 'bash': 'text/x-sh',
+ 'scilab': 'text/x-csrc'
+ }
+
+ // Code mirror Options
+ var options = {
+ mode: mode_dict[lang],
+ gutter: true,
+ lineNumbers: true,
+ onChange: function (instance, changes) {
+ render();
+ }
+ };
+
+ // Initialize the codemirror editor
+ var editor = CodeMirror.fromTextArea(textarea_node, options);
+
+ // Setting code editors initial content
+ editor.setValue('{{ last_attempt|escape_quotes|safe }}')
+
+ function reset_editor() {
+ editor.setValue('{{ last_attempt|escape_quotes|safe }}');
+ editor.clearHistory();
+ }
+
+
+ });
+</script>
{% endblock script %}
{% block onload %} onload="updateTime();" {% endblock %}
-{% block content %}
-<div class="row">
- <div class="col-md-6">
- <ul class="pagination pagination-lg">
- {% for qid in paper.questions.all %}
- {% if qid in paper.questions_unanswered.all %}
- {% if qid.id == question.id %}
- <li class="active"><a href="#"data-toggle="tooltip"
- title="{{ qid.description|striptags }}"
- onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ qid.id }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')">{{ forloop.counter }}</a></li>
- {% else %}
- <li><a href="#" data-toggle="tooltip" title="{{ qid.description|striptags }}"
- onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ qid.id }}/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')">{{ forloop.counter }}</a></li>
- {% endif %}
- {% endif %}
- {% if qid in paper.questions_answered.all %}
- <li class="disabled"><a style="background-color:#B4B8BA" href="#" data-toggle="tooltip" title="{{ qid.description }}" >{{ forloop.counter }}</a></li>
- {% endif %}
- {% endfor %}
- </ul>
- </div>
- <div class="col-md-5">
- <h3>{{ paper.questions_left }} question(s) left in {{ paper.question_paper.quiz.description }}</h3>
- </div>
- <div class="col-md-1">
-<form id="logout" action="{{URL_ROOT}}/exam/quit/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/" method="post" class="pull-right">
- {% csrf_token %}
- <button class="btn btn-danger " type="submit" name="quit">Quit Exam <span class="glyphicon glyphicon-off"></span></button>
-</form>
- </div>
-
-</div>
+{% block main %}
<p id="status"></p>
<form id="code" action="{{URL_ROOT}}/exam/{{ question.id }}/check/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/" method="post" enctype="multipart/form-data">
{% csrf_token %}
@@ -161,33 +163,11 @@ function call_skip(url)
</div>
<div class="panel-body">
{% if question.type == "mcq" %}
- {% if error_message %}
- <p>
- <div class="panel panel-danger">
- <div class="panel-heading">
- {% for err in error_message %}
- {{ err }}
- {% endfor %}
- </div>
- </div>
- </p>
- {% endif %}
{% for test_case in test_cases %}
<input name="answer" type="radio" value="{{ test_case.options }}" />{{ test_case.options|safe }} <br/>
{% endfor %}
{% endif %}
{% if question.type == "mcc" %}
- {% if error_message %}
- <p>
- <div class="panel panel-danger">
- <div class="panel-heading">
- {% for err in error_message %}
- {{ err }}
- {% endfor %}
- </div>
- </div>
- </p>
- {% endif %}
{% for test_case in test_cases %}
<input name="answer" type="checkbox" value="{{ test_case.options }}"> {{ test_case.options|safe }}
<br>
@@ -200,13 +180,13 @@ function call_skip(url)
{% endif %}
{% if question.type == "code" %}
<div class="row">
- <div class="col-md-2">
- <h4>Program:</h4>
+ <div class="col-md-9">
+ <h4>Write your program below:</h4>
</div>
- <div class="col-md-10">
+ <div class="col-md-3">
<a href="#" class="pull-right" onclick="reset_editor()" name="reset" id="reset">Reset Answer <span class="glyphicon glyphicon-refresh"></span></a>
</div>
- </div>
+ </div>
<textarea name="answer" id="answer"></textarea>
<br>
{% endif %}
@@ -223,28 +203,10 @@ function call_skip(url)
<button class="btn btn-primary" onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/')" name="skip" id="skip">Attempt Later <span class="glyphicon glyphicon-arrow-right"></span></button>
{% endif %}
</div>
- {% if question.type == "code" %}
- <div class="panel-footer">
-
- {% if error_message %}
- {% for error in error_message %}
- {% if error == "Correct answer" %}
- <div class="panel panel-success">
- {% else %}
- <div class="panel panel-danger">
- {% endif %}
- <div class="panel-heading">Testcase No. {{ forloop.counter }}</div>
- <div class="panel-body"><pre><code>{{ error }}</code></pre></div>
- </div>
- {% endfor %}
-
- {% endif %}
- </div>
- {% endif %}
</div>
</div>
- </form>
+ </form>
<!-- Modal -->
<div class="modal fade " id="upload_alert" >
<div class="modal-dialog">
@@ -261,44 +223,7 @@ function call_skip(url)
</div>
</div>
</div>
+{% endblock main %}
-<script>
- // Codemirror object, language modes and initial content
- // Get the textarea node
- var textarea_node = document.querySelector('#answer');
-
- var lang = "{{ question.language }}"
- var mode_dict = {
- 'python': 'python',
- 'c': 'text/x-csrc',
- 'cpp': 'text/x-c++src',
- 'java': 'text/x-java',
- 'bash': 'text/x-sh',
- 'scilab': 'text/x-csrc'
- }
-
- // Code mirror Options
- var options = {
- mode: mode_dict[lang],
- gutter: true,
- lineNumbers: true,
-
- onChange: function (instance, changes) {
- render();
- }
- };
- // Initialize the codemirror editor
- var editor = CodeMirror.fromTextArea(textarea_node, options);
-
- // Setting code editors initial content
- editor.setValue('{{ last_attempt|escape_quotes|safe }}')
-
- function reset_editor() {
- editor.setValue('{{ last_attempt|escape_quotes|safe }}');
- editor.clearHistory();
- }
-
-</script>
-{% endblock content %}
diff --git a/yaksh/templates/yaksh/quizzes_user.html b/yaksh/templates/yaksh/quizzes_user.html
index 4ccbbc0..524d76f 100644
--- a/yaksh/templates/yaksh/quizzes_user.html
+++ b/yaksh/templates/yaksh/quizzes_user.html
@@ -1,14 +1,6 @@
{% extends "user.html" %}
-
-
-{% block subtitle %}Hello {{ user.first_name }}, welcome to your dashboard !{% endblock %}
-
-
-{% block pagetitle %} Student's Dashboard {% endblock pagetitle %}
-{% block content %}
-
-<div class="row">
-<center><b><u>Available Courses</u></b></center><br>
+{% block pagetitle %} {{ title }} {% endblock %}
+{% block main %}
{% for course in courses %}
<div class="row well">
<div class="col-md-12">
@@ -31,93 +23,47 @@
</div>
<div class="row">
- {% if user in course.students.all %}
- {% if cannot_attempt %}
- <p>You have not passed the prerequisite & hence you cannot take the quiz.</p>
- {% endif %}
- <h4>List of quizzes available for you</h4>
- {% if not quizzes %}
- <h5>No active quizzes for you</h5>
- {% endif %}
- <table class="table table-bordered">
- <th>Quiz</th>
- <th>View Answer Paper</th>
- <th>Pre requisite quiz</th>
- {% for quiz in quizzes %}
- {% if quiz.course_id == course.id %}
- <tr>
- {% if not quiz.is_expired %}
- <td>
- <a href="{{ URL_ROOT }}/exam/start/{{quiz.questionpaper_set.get.id}}">{{ quiz.description }}</a><br>
- </td>
- {% else %}
- <td>
- <a href="{{ URL_ROOT }}/exam/start/{{quiz.questionpaper_set.get.id}}">{{ quiz.description }}</a><br>
- {{ quiz.description }} <span class="label label-danger">INACTIVE</span><br>
- </td>
- {% endif %}
+ {% if user in course.students.all %}
+ <table class="table table-bordered">
+ <th>Quiz</th>
+ <th>View Answer Paper</th>
+ <th>Pre requisite quiz</th>
+ {% for quiz in course.get_quizzes %}
+ {% if quiz.active and quiz.course_id == course.id %}
+ <tr>
+ {% if not quiz.is_expired %}
<td>
- {% if quiz.view_answerpaper %}
- <a href="{{ URL_ROOT }}/exam/view_answerpaper/{{ quiz.questionpaper_set.get.id }}/"><i class="fa fa-eye" aria-hidden="true"></i> Can View </a>
- {% else%}
- <a><i class="fa fa-eye-slash" aria-hidden="true"></i> Cannot view now </a>
- {% endif %}
+ <a href="{{ URL_ROOT }}/exam/start/{{quiz.questionpaper_set.get.id}}">{{ quiz.description }}</a><br>
</td>
+ {% else %}
<td>
- {% if quiz.prerequisite %}
- You have to pass {{ quiz.prerequisite.description }} for taking {{ paper.quiz.description }}
- {% else %}
- No pre requisites for {{ quiz.description }}
- {% endif %}
+ {{ quiz.description }} <span class="label label-danger">INACTIVE</span><br>
</td>
- </tr>
{% endif %}
- {% endfor %}
- </table>
- {% endif %}
+ <td>
+ {% if quiz.view_answerpaper %}
+ <a href="{{ URL_ROOT }}/exam/view_answerpaper/{{ quiz.questionpaper_set.get.id }}/"><i class="fa fa-eye" aria-hidden="true"></i> Can View </a>
+ {% else %}
+ <a><i class="fa fa-eye-slash" aria-hidden="true"></i> Cannot view now </a>
+ {% endif %}
+ </td>
+ <td>
+ {% if quiz.prerequisite %}
+ You have to pass {{ quiz.prerequisite.description }} for taking {{ paper.quiz.description }}
+ {% else %}
+ No pre requisites for {{ quiz.description }}
+ {% endif %}
+ </td>
+ </tr>
+ {% endif %}
+ {% endfor %}
+ </table>
+ {% endif %}
</div>
</div>
</div><!--/row-->
</br>
{% endfor %}
-</div>
-<hr>
-<h4>List of quizzes taken by you so far</h4>
-{% if quizzes_taken %}
- <table class="table table-bordered">
- <th>Quiz</th>
- <th>Result</th>
- <th>Marks Obtained</th>
- <th>Total Marks</th>
- <th>Percentage</th>
- {% for paper in quizzes_taken %}
- <tr>
- <td>
- {{ paper.question_paper.quiz.description }}
- </td>
- <td>
- {% if paper.passed %}
- <p>Pass</p>
- {% else %}
- <p>Fail</p>
- {% endif %}
- </td>
- <td>
- {{ paper.marks_obtained }}
- </td>
- <td>
- {{ paper.question_paper.total_marks }}
- </td>
- <td>
- {{ paper.percent }}
- </td>
- </tr>
- {% endfor %}
- </table>
-{% else %}
- <p>You have not taken any quiz yet !!</p>
-{% endif %}
-
{% endblock %}
diff --git a/yaksh/templates/yaksh/view_answerpaper.html b/yaksh/templates/yaksh/view_answerpaper.html
index 8dec5b3..cd607dd 100644
--- a/yaksh/templates/yaksh/view_answerpaper.html
+++ b/yaksh/templates/yaksh/view_answerpaper.html
@@ -2,7 +2,7 @@
{% block pagetitle %} Answer Paper for {{ quiz.description }}{% endblock pagetitle %}
-{% block content %}
+{% block main %}
{% if not data.papers %}
<p><b> You have not attempted the quiz {{ quiz.description }} </b></p>
diff --git a/yaksh/templates/yaksh/view_profile.html b/yaksh/templates/yaksh/view_profile.html
index becd205..5f06135 100644
--- a/yaksh/templates/yaksh/view_profile.html
+++ b/yaksh/templates/yaksh/view_profile.html
@@ -1,12 +1,8 @@
-{% extends "base.html" %}
+{% extends template %}
-{% block pagetitle %} My Profile {% endblock %}
-{% block content %}
-<ul>
- <a class="btn btn-primary" href="{{ URL_ROOT }}/exam/editprofile/">Edit Profile</a>
- <a class="btn btn-primary" href="{{ URL_ROOT }}/exam/">Back to Home</a>
-</ul>
- <table class="table">
+{% block pagetitle %} Profile {% endblock %}
+{% block main %}
+ <table class="table table-bordered">
<tr>
<th><label for="id_first_name"><h5>First name:</h5></label></th>
<th><label for="id_first_name"><h5>{{ user.first_name }}</h5></label></th>
@@ -36,4 +32,5 @@
<th><label for="id_position"><h5>{{ user.profile.position }}</h5></label></th>
</tr>
</table>
+ <a class="btn btn-primary pull-right" href="{{ URL_ROOT }}/exam/editprofile/">Edit Profile</a>
{% endblock %}
diff --git a/yaksh/test_models.py b/yaksh/test_models.py
index 48dfb8e..91d8806 100644
--- a/yaksh/test_models.py
+++ b/yaksh/test_models.py
@@ -533,22 +533,22 @@ class AnswerPaperTestCases(unittest.TestCase):
)
self.mcc_based_testcase.save()
- def test_validate_and_regrade_mcc_question(self):
+ def test_validate_and_regrade_mcc_correct_answer(self):
# Given
mcc_answer = ['a']
self.answer = Answer(question=self.question3,
- answer=mcc_answer,
- )
+ answer=mcc_answer,
+ )
self.answer.save()
self.answerpaper.answers.add(self.answer)
# When
json_data = None
- correct, result = self.answerpaper.validate_answer(mcc_answer,
- self.question3, json_data)
+ result = self.answerpaper.validate_answer(mcc_answer,
+ self.question3, json_data
+ )
# Then
- self.assertTrue(correct)
self.assertTrue(result['success'])
self.assertEqual(result['error'], ['Correct answer'])
self.answer.correct = True
@@ -570,7 +570,7 @@ class AnswerPaperTestCases(unittest.TestCase):
self.assertEqual(self.answer.marks, 0)
self.assertFalse(self.answer.correct)
- def test_validate_and_regrade_mcq_question(self):
+ def test_validate_and_regrade_mcq_correct_answer(self):
# Given
mcq_answer = 'a'
self.answer = Answer(question=self.question2,
@@ -581,11 +581,11 @@ class AnswerPaperTestCases(unittest.TestCase):
# When
json_data = None
- correct, result = self.answerpaper.validate_answer(mcq_answer,
- self.question2, json_data)
+ result = self.answerpaper.validate_answer(mcq_answer,
+ self.question2, json_data
+ )
# Then
- self.assertTrue(correct)
self.assertTrue(result['success'])
self.answer.correct = True
self.answer.marks = 1
@@ -607,6 +607,42 @@ class AnswerPaperTestCases(unittest.TestCase):
self.assertFalse(self.answer.correct)
+ def test_mcq_incorrect_answer(self):
+ # Given
+ mcq_answer = 'b'
+ self.answer = Answer(question=self.question2,
+ answer=mcq_answer,
+ )
+ self.answer.save()
+ self.answerpaper.answers.add(self.answer)
+
+ # When
+ json_data = None
+ result = self.answerpaper.validate_answer(mcq_answer,
+ self.question2, json_data
+ )
+
+ # Then
+ self.assertFalse(result['success'])
+
+ def test_mcc_incorrect_answer(self):
+ # Given
+ mcc_answer = ['b']
+ self.answer = Answer(question=self.question3,
+ answer=mcc_answer,
+ )
+ self.answer.save()
+ self.answerpaper.answers.add(self.answer)
+
+ # When
+ json_data = None
+ result = self.answerpaper.validate_answer(mcc_answer,
+ self.question3, json_data
+ )
+
+ # Then
+ self.assertFalse(result['success'])
+
def test_answerpaper(self):
""" Test Answer Paper"""
self.assertEqual(self.answerpaper.user.username, 'demo_user')
diff --git a/yaksh/urls.py b/yaksh/urls.py
index 036c6a3..bdf14ea 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -31,6 +31,7 @@ urlpatterns += [
url(r'^$', views.index),
url(r'^login/$', views.user_login, name='login'),
url(r'^quizzes/$', views.quizlist_user, name='quizlist_user'),
+ url(r'^quizzes/(?P<enrolled>\w+)/$', views.quizlist_user, name='quizlist_user'),
url(r'^results/$', views.results_user),
url(r'^start/$', views.start),
url(r'^start/(?P<questionpaper_id>\d+)/$', views.start),
diff --git a/yaksh/views.py b/yaksh/views.py
index 7ecf6aa..4be2fc1 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -105,18 +105,16 @@ def user_register(request):
@login_required
-def quizlist_user(request):
+def quizlist_user(request, enrolled=None):
"""Show All Quizzes that is available to logged-in user."""
user = request.user
- avail_quizzes = Quiz.objects.get_active_quizzes()
- user_answerpapers = AnswerPaper.objects.filter(user=user)
- courses = Course.objects.filter(active=True, is_trial=False)
-
- context = { 'quizzes': avail_quizzes,
- 'user': user,
- 'courses': courses,
- 'quizzes_taken': user_answerpapers,
- }
+ if enrolled is not None:
+ courses = user.students.all()
+ title = 'Enrolled Courses'
+ else:
+ courses = Course.objects.filter(active=True, is_trial=False)
+ title = 'All Courses'
+ context = {'user': user, 'courses': courses, 'title': title}
return my_render_to_response("yaksh/quizzes_user.html", context)
@@ -269,7 +267,7 @@ def show_all_questionpapers(request, questionpaper_id=None):
@login_required
-def prof_manage(request):
+def prof_manage(request, msg=None):
"""Take credentials of the user with professor/moderator
rights/permissions and log in."""
user = request.user
@@ -303,7 +301,7 @@ def prof_manage(request):
temp = paper, answer_papers, users_passed, users_failed
users_per_paper.append(temp)
context = {'user': user, 'users_per_paper': users_per_paper,
- 'trial_paper': trial_paper
+ 'trial_paper': trial_paper, 'msg': msg
}
return my_render_to_response('yaksh/moderator_dashboard.html', context, context_instance=ci)
return my_redirect('/exam/login/')
@@ -488,12 +486,12 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None):
# safely in a separate process (the code_server.py) running as nobody.
json_data = current_question.consolidate_answer_data(user_answer) \
if current_question.type == 'code' else None
- correct, result = paper.validate_answer(user_answer, current_question, json_data)
- if correct or result.get('success'):
+ result = paper.validate_answer(user_answer, current_question, json_data)
+ if result.get('success'):
new_answer.marks = (current_question.points * result['weight'] /
current_question.get_maximum_test_case_weight()) \
if current_question.partial_grading and current_question.type == 'code' else current_question.points
- new_answer.correct = correct
+ new_answer.correct = result.get('success')
error_message = None
new_answer.error = json.dumps(result.get('error'))
next_question = paper.completed_question(current_question.id)
@@ -1065,10 +1063,14 @@ def view_profile(request):
""" view moderators and users profile """
user = request.user
ci = RequestContext(request)
-
- context = {}
+ if is_moderator(user):
+ template = 'manage.html'
+ else:
+ template = 'user.html'
+ context = {'template': template}
if has_profile(user):
- return my_render_to_response('yaksh/view_profile.html', {'user':user})
+ context['user'] = user
+ return my_render_to_response('yaksh/view_profile.html', context)
else:
form = ProfileForm(user=user)
msg = True
@@ -1082,10 +1084,13 @@ def view_profile(request):
def edit_profile(request):
""" edit profile details facility for moderator and students """
- context = {}
user = request.user
ci = RequestContext(request)
-
+ if is_moderator(user):
+ template = 'manage.html'
+ else:
+ template = 'user.html'
+ context = {'template': template}
if has_profile(user):
profile = Profile.objects.get(user_id=user.id)
else:
@@ -1241,8 +1246,7 @@ def create_demo_course(request):
msg = "Created Demo course successfully"
else:
msg = "Demo course already created"
- context = {'msg': msg}
- return my_render_to_response('manage.html', context, context_instance=ci)
+ return prof_manage(request, msg)
@login_required