diff options
26 files changed, 721 insertions, 329 deletions
@@ -42,4 +42,4 @@ migrations wsgi.log *.sqlite3 data/ -documentation/_build/ +_build/ diff --git a/.travis.yml b/.travis.yml index c47785e..5b8ff2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ python: - "3.5" env: - - DJANGO=1.8.13 - DJANGO=1.9.5 # command to install dependencies diff --git a/online_test/settings.py b/online_test/settings.py index 5df0410..0b604ca 100644 --- a/online_test/settings.py +++ b/online_test/settings.py @@ -32,6 +32,8 @@ URL_ROOT = '' # Application definition +FIXTURE_DIRS = os.path.join(BASE_DIR, "yaksh", "fixtures") + INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', @@ -97,7 +99,7 @@ SOCIAL_AUTH_LOGIN_ERROR_URL = '/exam/login/' MEDIA_URL = "/data/" -MEDIA_ROOT = os.path.join(BASE_DIR+"/yaksh/", "data") +MEDIA_ROOT = os.path.join(BASE_DIR, "yaksh", "data") EMAIL_USE_TLS = False @@ -27,7 +27,7 @@ setup( name='yaksh', author='Python Team at FOSSEE, IIT Bombay', author_email='python@fossee.in', - version='0.1.1', + version='0.1.3', packages=find_packages(), include_package_data=True, url = 'https://pypi.python.org/pypi/yaksh/', @@ -53,6 +53,7 @@ setup( 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.5', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], ) diff --git a/yaksh/admin.py b/yaksh/admin.py index c525ba3..72496b2 100644 --- a/yaksh/admin.py +++ b/yaksh/admin.py @@ -2,6 +2,13 @@ from yaksh.models import Question, Quiz, QuestionPaper from yaksh.models import TestCase, StandardTestCase, StdIOBasedTestCase, Course, AnswerPaper from django.contrib import admin + + +class AnswerPaperAdmin(admin.ModelAdmin): + search_fields = ['user__first_name', 'user__last_name','user__username', + "question_paper__quiz__description" ] + + admin.site.register(Question) admin.site.register(TestCase) admin.site.register(StandardTestCase) @@ -9,4 +16,4 @@ admin.site.register(StdIOBasedTestCase) admin.site.register(Course) admin.site.register(Quiz) admin.site.register(QuestionPaper) -admin.site.register(AnswerPaper)
\ No newline at end of file +admin.site.register(AnswerPaper, AnswerPaperAdmin) 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 ef155e8..1fba73e 100644 --- a/yaksh/evaluator_tests/test_python_evaluation.py +++ b/yaksh/evaluator_tests/test_python_evaluation.py @@ -74,13 +74,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') ) @@ -108,10 +108,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 Binary files differindex 562bf8a..c68e7ef 100644 --- a/yaksh/fixtures/demo_questions.zip +++ b/yaksh/fixtures/demo_questions.zip diff --git a/yaksh/models.py b/yaksh/models.py index 7787e17..ad61872 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 @@ -23,6 +24,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 @@ -66,6 +68,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) @@ -352,7 +391,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) @@ -379,8 +418,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) @@ -565,6 +603,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, @@ -703,7 +742,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, @@ -1060,28 +1099,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: @@ -1106,10 +1142,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 |